前言
微信、快手、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) {}})}});},}}