前言
微信、快手、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 : '', // 分销员id
couponId: this.detailsObj.receiveCouponId ? this.detailsObj.receiveCouponId : '', // 优惠卷id
}
// 如果一个项目里有多个平台支付,可以用传值来区分
// #ifdef MP-WEIXIN
data.platform = 1
// #endif
// #ifdef MP-KUAISHOU
data.platform = 2
// #endif
//创建订单
createWendaoOrder(data).then(res => {
// 返回密钥
createOrder({
orderId: res.data.orderId, // 订单id
openid: this.openId, // 用户id
}).then(res1 => {
// #ifdef MP-WEIXIN
let twoData = res1.data
// 微信支付api
// 参数向后端要 要确保每个值就有
uni.requestPayment({
appId: twoData.appId,
timeStamp: twoData.timeStamp,
nonceStr: twoData.nonceStr,
package: twoData.packageValue,
signType: twoData.signType,
paySign: twoData.paySign,
su***ess(result) {
// 调起支付密码
if (result.errMsg == "requestPayment:ok") {
// 支付成功
uni.showLoading({
title: '获取订单状态..',
mask: true,
})
orderSu***ess({
openid: that.openId, // 用户id
orderId: res.data.orderId // 订单id
}).then(res=>{
uni.hideLoading();
uni.showToast({
title: '支付成功',
icon: 'none'
});
// 重新请求下商品详情
that.detailFn()
})
} else {
uni.showModal({
title: '',
content: '支付失败',
showCancel: false,
icon: 'none',
su***ess(res) {}
})
}
},
fail(result) {
console.log(result)
uni.showModal({
title: '',
content: '支付失败',
showCancel: false,
icon: 'none',
su***ess(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 : '', // 分销员id
couponId: this.detailsObj.receiveCouponId ? this.detailsObj.receiveCouponId : '', // 优惠卷id
}
// 如果一个项目里有多个平台支付,可以用传值来区分
// #ifdef MP-WEIXIN
data.platform = 1
// #endif
// #ifdef MP-KUAISHOU
data.platform = 2
// #endif
//创建订单
createWendaoOrder(data).then(res => {
// 返回密钥
createOrder({
orderId: res.data.orderId, // 订单id
openid: this.openId, // 用户id
}).then(res1 => {
// 后端返回的是这些数据
// res1.data = {
// order_no: "1231xxxxxxxxxxxxxxx450"
// order_info_token: "ChJrc01wUGF5LmxxxxxxxxxxxxxxxWYYeulijTrRyDdowh6Lvtp2MIm-t5nlq4s3xxxxxxxxxxxxxxxxxxxuh217_-giIIHDQ8yTqZqghjVraGC_NjxxxxxxxxxxxxxxKAUwAQ"
// {
// 快手支付api
// #ifdef MP-KUAISHOU
ks.pay({
serviceId: '1',
orderInfo: res1.data,
su***ess: function su***ess(res2) {
// 调起支付密码
// 支付成功
uni.showLoading({
title: '获取订单状态..',
mask: true,
})
orderSu***ess({
openid: that.openId, // 用户id
orderId: 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.新建tt***ponents文件夹,创建完后在tt***ponents下面再新建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.***/douyin'
},
/**
* desc: 获取手机号
* params:加密数据
* su***ess:成功回调
* fail: 失败回调
*/
// 这个是抖音下单页获取手机号调用的函数
getPhoneNumber({
params,
su***ess,
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,
}
// 回调前端模板
su***ess(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();
***ponent({
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 = this
return new Promise(resolve => {
// 定时器是为解决 优惠卷id获取不到问题
setTimeout(()=>{
tt.getSystemInfo({
su***ess: (resPlatform)=> {
let pay = that.data.detailsObj.price * 100 // 价格单位是分
let promoterShareId = that.data.promoterShareId // 分销员id
let 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.detail
return new Promise((resolve, reject) => {
tt.login({
su***ess(e) {
// 用户登录成功并获取信息,则调用 resolve 函数,跳转至提单页
resolve();
},
fail() {
// 用户登录失败,则跳转提单页失败
_this.showTost("登录失败")
}
});
});
},
payError(event) {
this.showTost(event.errMsg)
},
// 继续支付
handleContinutePay(event) {
const { status, outOrderNo, result } = event.detail;
if (status === 'su***ess') {
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 === 'su***ess') {
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_su***ess",
method: 'POST',
data,
su***ess: (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 === 'su***ess') {
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
{
"***ponent": true,
"using***ponents": {}
}
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
// 这个是需要加的,否则显示不出来
"using***ponents": {
"zijie-pay-button": "/tt***ponents/DyPayButton/index"
},
//#endif
"navigationBarTitleText": "xxxx",
"enablePullDownRefresh": false
}
}]
3.在商品详情页面中使用组件
***ponents:{
// #ifdef MP-TOUTIAO
"zijie-pay-button": "../../tt***ponents/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: '', // 用户id
h5PayData:{}, // h5提交的参数
h5OrderId: '' // 订单id
}
},
onLoad(options) {
_this = this
// 页面一进来就调用支付 带上路径传的值
this.payTap(options)
},
methods:{
payTap(queryObj) {
let that = this
that.openid = queryObj.openid
let data = {
openid: queryObj.openid, // 用户id
courseId: queryObj.courseId, // 课程id
promoterId: queryObj.promoterId ? queryObj.promoterId : 0, // 推广员id
couponId: queryObj.couponId ? queryObj.couponId : 0, // 优惠卷id
appNameType: queryObj.appNameType, // 区分哪个小程序
platform : queryObj.platform // 区分平台
}
createWendaoOrder1(data).then(res => {
createOrder1({
orderId: res.data.orderId,
openid: queryObj.openid,
}).then(res1 => {
// #ifdef H5
that.h5PayData = res1.data
that.h5OrderId = res.data.orderId
that.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 = this
console.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_su***ess",
method: 'POST',
data: {
openid: that.openId,
orderId: that.h5OrderId
},
su***ess: (res) => {
uni.hideLoading();
uni.showModal({
title: '',
content: '支付成功,请返回小程序查看课程',
showCancel: false,
icon: 'none'
})
}
})
} else {
uni.showModal({
title: '',
content: '支付失败',
showCancel: false,
icon: 'none',
su***ess(res) {}
})
}
}
);
},
}
}