前言
微信、快手、h5支付步骤大致相同,只有抖音是有自己的支付组件
项目同时支持多个(微信、快手、h5)平台支付,后端那边代码可以封装的
各平台支付大致流程都是相同的,总结了一下分为五个步骤
- 点击支付
- 创建订单
- 生成密钥和支付所需要的参数
- 支付成功
- 查询订单状态
一、微信支付
1.支付按钮
<button @click="payTap">立即抢购</button>
2.支付事件
payTap() {let that = this// 这些参数后端一般要的let data = {openid: this.openId, //用户id 必需courseId: this.detailsObj.id, //课程id(商品id)必需promoterId: this.promoterShareId ? this.promoterShareId : '', // 分销员idcouponId: this.detailsObj.receiveCouponId ? this.detailsObj.receiveCouponId : '', // 优惠卷id}// 如果一个项目里有多个平台支付,可以用传值来区分// #ifdef MP-WEIXINdata.platform = 1// #endif// #ifdef MP-KUAISHOUdata.platform = 2// #endif//创建订单createWendaoOrder(data).then(res => {// 返回密钥createOrder({orderId: res.data.orderId, // 订单idopenid: this.openId, // 用户id}).then(res1 => {// #ifdef MP-WEIXINlet twoData = res1.data// 微信支付api// 参数向后端要 要确保每个值就有uni.requestPayment({appId: twoData.appId,timeStamp: twoData.timeStamp,nonceStr: twoData.nonceStr,package: twoData.packageValue,signType: twoData.signType,paySign: twoData.paySign,success(result) {// 调起支付密码if (result.errMsg == "requestPayment:ok") {// 支付成功uni.showLoading({title: '获取订单状态..',mask: true,})orderSuccess({openid: that.openId, // 用户idorderId: res.data.orderId // 订单id}).then(res=>{uni.hideLoading();uni.showToast({title: '支付成功',icon: 'none'});// 重新请求下商品详情that.detailFn()})} else {uni.showModal({title: '',content: '支付失败',showCancel: false,icon: 'none',success(res) {}})}},fail(result) {console.log(result)uni.showModal({title: '',content: '支付失败',showCancel: false,icon: 'none',success(res) {}})},})// #endif}).catch(res => {console.log(res)})})
}
二、快手支付
1.支付按钮
<button @click="payTap">立即抢购</button>
2.支付事件
payTap() {let that = this// 这些参数后端一般要的let data = {openid: this.openId, //用户id 必需courseId: this.detailsObj.id, //课程id(商品id)必需promoterId: this.promoterShareId ? this.promoterShareId : '', // 分销员idcouponId: this.detailsObj.receiveCouponId ? this.detailsObj.receiveCouponId : '', // 优惠卷id}// 如果一个项目里有多个平台支付,可以用传值来区分// #ifdef MP-WEIXINdata.platform = 1// #endif// #ifdef MP-KUAISHOUdata.platform = 2// #endif//创建订单createWendaoOrder(data).then(res => {// 返回密钥createOrder({orderId: res.data.orderId, // 订单idopenid: this.openId, // 用户id}).then(res1 => {// 后端返回的是这些数据// res1.data = {// order_no: "1231xxxxxxxxxxxxxxx450" // order_info_token: "ChJrc01wUGF5LmxxxxxxxxxxxxxxxWYYeulijTrRyDdowh6Lvtp2MIm-t5nlq4s3xxxxxxxxxxxxxxxxxxxuh217_-giIIHDQ8yTqZqghjVraGC_NjxxxxxxxxxxxxxxKAUwAQ"// {// 快手支付api// #ifdef MP-KUAISHOUks.pay({serviceId: '1',orderInfo: res1.data,success: function success(res2) {// 调起支付密码// 支付成功uni.showLoading({title: '获取订单状态..',mask: true,})orderSuccess({openid: that.openId, // 用户idorderId: res.data.orderId // 订单id}).then(res=>{uni.hideLoading();uni.showToast({title: '支付成功',icon: 'none'});// 重新请求下商品详情that.detailFn()})},fail: function fail(res) {uni.showToast({title: '支付失败',icon: 'none'})}})// #endif}).catch(res => {console.log(res)})})
}
三、抖音支付
抖音有自己的支付组件和自己的下单页面,所以需要创建一个专属于抖音的组件,并把需要的参数进行传递
1.创建组件
1.新建ttcomponents文件夹,创建完后在ttcomponents下面再新建DyPayButton文件夹,然后在DyPayButton下面创建四个文件,分别为index.js、index.json、index.ttml、index.ttss
2.要创建在App.vue同级
先在App.vue 写baseUrl和getPhoneNumber函数
<script>export default {onLaunch: function() {},onShow: function() {console.log('App Show')},onHide: function() {console.log('App Hide')},methods: {// 这个是接口基地址baseUrl(){return 'https://kspaycallback.wendao101.com/douyin'},/*** desc: 获取手机号* params:加密数据* success:成功回调* fail: 失败回调*/// 这个是抖音下单页获取手机号调用的函数getPhoneNumber({params,success,fail}) {const {iv,encryptedData} = params;miniLogin().then(data=>{getTtOpen(data).then(data1 => {// 获取手机号函数savePhone({openid: data1.data.data.openId,iv,encryptedData}).then(data4 => {miniLogin().then(data6=>{getTtOpen(data6).then(data5 => {const result = {phoneNumber: data5.data.data.telNumber,}// 回调前端模板success(result)})})})})})},},}
</script><style lang="scss">@import "@/uni_modules/uview-ui/index.scss";@import '@/utlis/index.scss';/*每个页面公共css */
</style>
下面是那四个文件的内容
index.js
// 可以调用到app.vue里的方法
const app = getApp();Component({properties: {mode: Number,openId: {type: [String, Number],},orderStatus:{type: [String, Number],},detailsObj: {type: Object,},goodsId: {type: String,value: "",},promoterShareId: Number,},data: {},methods: {// 提交商品信息 这个函数一进页面就会调用的getGoodsInfo(event) {const that = thisreturn new Promise(resolve => {// 定时器是为解决 优惠卷id获取不到问题setTimeout(()=>{tt.getSystemInfo({success: (resPlatform)=> {let pay = that.data.detailsObj.price * 100 // 价格单位是分let promoterShareId = that.data.promoterShareId // 分销员idlet required = that.data.detailsObj.giveEntityIsNeedPhone // 是否强制获取手机号let CouponId = that.data.detailsObj.receiveCouponId // 优惠卷id// 用不到的值就不用传let data = {currentPrice: pay,GoodsLabel: [{type: 'NON_REFUNDABLE'}],minLimits: 1,maxLimits: 1,dateRule: '周一至周日可用',extra: {promoterId: promoterShareId,receiveCouponId: CouponId},validation: {phoneNumber: {required: required // 手机号是否必填}},marketingVersion: 1,}// im客服需要提前开通// 判断如果用户手机是ios就走客服支付, 把imId传上就自动跳转im客服页面了,安卓不要传这个id,否则可能会导致支付不了if(resPlatform.platform == 'ios'){data.imId = '3xxxxxxxx42'} // 然后将商品信息传入 resolve 函数resolve(data);}});},600)})},onError(e) {const {errNo,errMsg} = e.detail;if (errNo === 21514) {tt.showToast({title: "失败", // 内容icon: "none", // 图标});} else if (errNo === 21513) {tt.showToast({title: "获取中", // 内容icon: "none", // 图标});}},userLogin(event) {const {goodsId,goodsType} = event.detailreturn new Promise((resolve, reject) => {tt.login({success(e) {// 用户登录成功并获取信息,则调用 resolve 函数,跳转至提单页resolve();},fail() {// 用户登录失败,则跳转提单页失败_this.showTost("登录失败")}});});},payError(event) {this.showTost(event.errMsg)},// 继续支付handleContinutePay(event) {const { status, outOrderNo, result } = event.detail;if (status === 'success') {const { code } = result;if (code === 0) {// 继续支付成功// 刷新页面this.triggerEvent("refreshData")tt.showToast({title: "支付成功",});}} else {// 继续支付失败tt.showToast({title: "继续支付失败",icon: "none"});}},// 正式支付newButtonPay(event) {const {status,orderId,outOrderNo,result} = event.detail;if (status === 'success') {const {code} = result;if (code === 0) {tt.showLoading({title: "订单确认中...",}); this.getOrderIsHaveData(outOrderNo)} else {// 支付失败(超时、取消、关闭)this.showTost('支付失败(超时、取消、关闭)')}} else {const {errMsg} = result;this.showTost(errMsg)}},showTost(tit, timeMs) {let time = timeMs > 0 ? timeMs : 1500;tt.showToast({title: tit,icon: "none",duration: time,});},// 重新订单getOrderIsHaveData(orderId) {let data = {openid: this.data.openId,orderId,}tt.request({url: app.baseUrl() + "/order/order_success",method: 'POST',data,success: (res) => {this.setOrderIsHaveData(res.data.orderStatus, orderId)}})},setOrderIsHaveData(data, orderId) {if (data == 0) {setTimeout(() => {_this.getOrderIsHaveData(orderId)}, 1000);} else {tt.hideLoading();tt.navigateBack(-1);this.triggerEvent("refreshData")}},// 退款onApplyrefund(event) {console.log(event)const {orderId} = event.detail;const extra = {orderId}; // 开发者需要透传的参数,可自定义内容return new Promise(resolve => {resolve(extra);});},onRefund(event) {console.log(event)const {status,result} = event.detail;if (status === 'success') {const {refundId,outRefundNo} = result;} else {const {errMsg} = result;tt.showToast({title: e.detail.errMsg ? e.detail.errMsg : '失败',icon: "none"});}},refundError(e) {console.log(e)if (e.detail.errNo == 21531) {tt.showToast({title: "不符合退款要求",icon: "none"});} else {tt.showToast({title: e.detail.errMsg ? e.detail.errMsg : '失败',icon: "none"});}},},
});
index.json
{"component": true,"usingComponents": {}
}
index.ttml
<!-- 立即抢购 -->
<block tt:if="{{mode==2}}"> <pay-button class="{{classsname}}" mode="{{2}}" goods-id="{{detailsObj.productId}}" goods-type="{{1}}"biz-line="{{2}}" bind:getgoodsinfo="getGoodsInfo" bind:placeorder="userLogin" marketing-ready="{{true}}"bind:pay="newButtonPay" bind:error="onError"></pay-button>
</block><block tt:if="{{mode==1}}"><!-- 继续支付 --><pay-button tt:if="{{orderStatus==0}}" class="{{classsname}}" order-status="{{0}}" order-id="{{orderData.orderId}}"bind:pay="handleContinutePay"></pay-button><!-- 退款 --><pay-button class="order_ljzf" mode="{{1}}" goods-type="{{1}}" order-status="{{1}}" order-id="{{orderData.orderId}}":refund-total-amount="{{orderData.coursePrice}}" biz-line="{{2}}" marketing-ready="{{true}}"catch:applyrefund="onApplyrefund" catch:refund="onRefund" catch:error="refundError"tt:if="{{orderData.orderStatus==1}}"></pay-button><!-- 退款状态 --><pay-button class="order_tk" mode="{{1}}" goods-type="{{1}}"order-status="{{orderData.orderStatus=='4'?2:orderData.orderStatus=='2'?3:orderData.orderStatus=='5'?4:4 }}"refund-id="{{orderData.orderId}}" biz-line="{{2}}" marketing-ready="{{true}}" catch:applyrefund="onApplyrefund"catch:refund="onRefund" catch:error="refundError"tt:if="{{orderData.orderStatus!=1 && orderData.orderStatus!=0}}"></pay-button>
</block>
index.ttss
按钮样式根据自己的来
.save_one {width: 100%;height: 100%;
}.payButton {display: flex;align-items: center;justify-content: center;width: 310rpx;height: 90rpx;border-radius: 45rpx;box-sizing: border-box;background-color: #E10000;color: #fff;border: 2rpx solid #E10000;
}.payButtonItem {display: flex;align-items: center;justify-content: center;width: 183rpx;height: 57rpx;background: #E10000;border-radius: 29rpx;border: 1rpx solid #E10000;font-size: 26rpx;font-family: "Noto Sans SC";font-weight: 600;color: #fff;line-height: 37rpx;box-sizing: border-box;margin-right: 16rpx;
}
2.在pages.json给商品详情页面注册组件
"pages": [{"path": "details/details","style": {// #ifdef MP-TOUTIAO// 这个是需要加的,否则显示不出来"usingComponents": {"zijie-pay-button": "/ttcomponents/DyPayButton/index"},//#endif"navigationBarTitleText": "xxxx","enablePullDownRefresh": false}
}]
3.在商品详情页面中使用组件
components:{// #ifdef MP-TOUTIAO"zijie-pay-button": "../../ttcomponents/DyPayButton/index",//#endif
}
<zijie-pay-button v-if="detailsObj.productId":openId="openId":mode='2' :detailsObj="detailsObj" :classsname="'save_one'":promoterShareId="promoterShareId"@refreshData="refreshData"
></zijie-pay-button>
>
// v-if: 判断有没有商品id
// openId: 用户id
// mode: 商品类型
// detailsObj: 商品详情页的数据
// classsname: 按钮类名
// promoterShareId: 分销员id
// @refreshData: 用于支付成功回调刷新页面
4.h5支付
主要是为解决微信ios无法支付的问题,ios走h5支付渠道
1.创建按钮
// 这个按钮是uView组件
<u-button
text="联系老师"
v-if="isIosDouYin"
shape="circle"
color="#E10000"
:send-message-title="detailsObj.title?detailsObj.title:''"
:send-message-img="detailsObj.coverPicUrl"
:send-message-path="'/pages_details/details/details?courseId=' + detailsObj.id + '&promoterId=' + promoterShareId + '&couponId=' + detailsObj.receiveCouponId + '&appNameType=1&platform=1&openid=' + openId"
show-message-card="true"
open-type="contact"></u-button>// isIosDouYin: 判断是否为ios系统
// send-message-title: 商品标题
// :send-message-img: 商品封面
// :send-message-path: 跳转路径
// show-message-card: 发送卡片
2.创建页面
在详情页同级目录创建一个页面detailsContact.vue,后端可以重定向到这个页面
var _this;
export default {data() {return {openid: '', // 用户idh5PayData:{}, // h5提交的参数h5OrderId: '' // 订单id}},onLoad(options) {_this = this// 页面一进来就调用支付 带上路径传的值this.payTap(options)},methods:{payTap(queryObj) {let that = thisthat.openid = queryObj.openidlet data = {openid: queryObj.openid, // 用户idcourseId: queryObj.courseId, // 课程idpromoterId: queryObj.promoterId ? queryObj.promoterId : 0, // 推广员idcouponId: queryObj.couponId ? queryObj.couponId : 0, // 优惠卷idappNameType: queryObj.appNameType, // 区分哪个小程序platform : queryObj.platform // 区分平台}createWendaoOrder1(data).then(res => {createOrder1({orderId: res.data.orderId,openid: queryObj.openid,}).then(res1 => {// #ifdef H5that.h5PayData = res1.datathat.h5OrderId = res.data.orderIdthat.getWxOfficePay()// #endif}).catch(res => {console.log(res)})})},getWxOfficePay() {if (typeof WeixinJSBridge == "undefined") {if (document.addEventListener) {document.addEventListener('WeixinJSBridgeReady', _this.getWxOfficePayPage, false);} else if (document.attachEvent) {document.attachEvent('WeixinJSBridgeReady', _this.getWxOfficePayPage);document.attachEvent('onWeixinJSBridgeReady', _this.getWxOfficePayPage);}} else {_this.getWxOfficePayPage()}},getWxOfficePayPage() {const that = thisconsole.log("h5支付调起支付面板 ")WeixinJSBridge.invoke('getBrandWCPayRequest', {appId: _this.h5PayData.appId,timeStamp: _this.h5PayData.timeStamp,nonceStr: _this.h5PayData.nonceStr,package: _this.h5PayData.packageValue,signType: _this.h5PayData.signType,paySign: _this.h5PayData.paySign,},function(res) {if (res.err_msg == "get_brand_wcpay_request:ok") {uni.showLoading({title: '获取订单状态..',mask: true,})// 确认订单uni.request({url: app.baseUrl() + "/order/order_success",method: 'POST',data: {openid: that.openId,orderId: that.h5OrderId},success: (res) => {uni.hideLoading();uni.showModal({title: '',content: '支付成功,请返回小程序查看课程',showCancel: false,icon: 'none'})}})} else {uni.showModal({title: '',content: '支付失败',showCancel: false,icon: 'none',success(res) {}})}});},}}