提交 f95bf7a1 编写于 作者: VK1688's avatar VK1688

uni-pay新增苹果虚拟支付

上级 30b082d9
...@@ -1018,6 +1018,13 @@ ...@@ -1018,6 +1018,13 @@
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, },
{
"path": "pages/API/virtual-payment/virtual-payment-uni-pay",
"style": {
"navigationBarTitleText": "苹果虚拟支付(uni-pay)",
"enablePullDownRefresh": false
}
},
// #endif // #endif
{ {
"path": "pages/API/request-payment/request-payment/order-detail", "path": "pages/API/request-payment/request-payment/order-detail",
...@@ -1744,14 +1751,14 @@ ...@@ -1744,14 +1751,14 @@
{ {
"path": "uni_modules/uni-pay-x/pages/ad-interactive-webview/ad-interactive-webview", "path": "uni_modules/uni-pay-x/pages/ad-interactive-webview/ad-interactive-webview",
"style": { "style": {
"navigationBarTitleText": "收银台", "navigationBarTitleText": "ad",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8"
} }
}, },
{ {
"path": "uni_modules/uni-pay-x/pages/pay-desk/pay-desk", "path": "uni_modules/uni-pay-x/pages/pay-desk/pay-desk",
"style": { "style": {
"navigationBarTitleText": "ad", "navigationBarTitleText": "收银台",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8"
} }
}, },
......
...@@ -27,9 +27,12 @@ ...@@ -27,9 +27,12 @@
<!-- #endif --> <!-- #endif -->
<button class="button" @click="getOrderPopup(true)">查询支付状态</button> <button class="button" @click="getOrderPopup(true)">查询支付状态</button>
<button class="button" @click="pageTo('/uni_modules/uni-pay-x/pages/success/success?out_trade_no=test2024030501-1&order_no=test2024030501&total_fee=1&adpid=1000000001&return_url=/pages/API/request-payment-uni-pay/order-detail')">支付成功页面示例</button> <button class="button" @click="pageTo('/uni_modules/uni-pay-x/pages/success/success?out_trade_no=test2024030501-1&order_no=test2024030501&total_fee=1&adpid=1000000001&return_url=/pages/API/request-payment/request-payment/order-detail')">支付成功页面示例</button>
<!-- #ifdef APP-IOS -->
<button class="button" @click="pageTo('/pages/API/virtual-payment/virtual-payment-uni-pay')">苹果虚拟支付示例(iOS内购)</button>
<!-- #endif -->
<!-- 查询支付的弹窗 --> <!-- 查询支付的弹窗 -->
<uni-pay-popup ref="getOrderPopupRef" type="bottom"> <uni-pay-popup ref="getOrderPopupRef" type="center">
<scroll-view direction="vertical" class="get-order-popup"> <scroll-view direction="vertical" class="get-order-popup">
<view class="label">插件支付单号:</view> <view class="label">插件支付单号:</view>
<view class="mt20"> <view class="mt20">
...@@ -90,7 +93,7 @@ ...@@ -90,7 +93,7 @@
<button class="button" v-if="h5Env === 'h5-weixin'" @click="getWeiXinJsCode('snsapi_base')">公众号获取openid示例</button> <button class="button" v-if="h5Env === 'h5-weixin'" @click="getWeiXinJsCode('snsapi_base')">公众号获取openid示例</button>
<!-- #endif --> <!-- #endif -->
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" --> <!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="payRef" :adpid="adpid" height="900rpx" return-url="/pages/API/request-payment-uni-pay/order-detail" logo="/static/logo.png" @success="onSuccess" @create="onCreate" <uni-pay ref="payRef" :adpid="adpid" height="900rpx" return-url="/pages/API/request-payment/request-payment/order-detail" logo="/static/logo.png" @success="onSuccess" @create="onCreate"
@fail="onFail" @cancel="onCancel"></uni-pay> @fail="onFail" @cancel="onCancel"></uni-pay>
</view> </view>
</template> </template>
...@@ -251,7 +254,7 @@ ...@@ -251,7 +254,7 @@
getOrderData['out_trade_no'] = this.out_trade_no; getOrderData['out_trade_no'] = this.out_trade_no;
} }
let res = await payInstance.getOrder(getOrderData); let res = await payInstance.getOrder(getOrderData);
if (res != null && res['errCode'] == 0) { if (res['errCode'] == 0) {
this.getOrderRes = res.getJSON('pay_order') as UTSJSONObject; this.getOrderRes = res.getJSON('pay_order') as UTSJSONObject;
let obj = { let obj = {
"-1": "已关闭", "-1": "已关闭",
...@@ -275,7 +278,7 @@ ...@@ -275,7 +278,7 @@
let res = await payInstance.refund({ let res = await payInstance.refund({
out_trade_no: this.out_trade_no, // 插件支付单号 out_trade_no: this.out_trade_no, // 插件支付单号
}); });
if (res != null && res['errCode'] == 0) { if (res['errCode'] == 0) {
uni.showToast({ uni.showToast({
title: res['errMsg'] as string, title: res['errMsg'] as string,
icon: "none" icon: "none"
...@@ -288,7 +291,7 @@ ...@@ -288,7 +291,7 @@
let res = await payInstance.getRefund({ let res = await payInstance.getRefund({
out_trade_no: this.out_trade_no, // 插件支付单号 out_trade_no: this.out_trade_no, // 插件支付单号
}); });
if (res != null && res['errCode'] == 0) { if (res['errCode'] == 0) {
uni.showModal({ uni.showModal({
content: res['errMsg'] as string, content: res['errMsg'] as string,
showCancel: false showCancel: false
...@@ -301,7 +304,7 @@ ...@@ -301,7 +304,7 @@
let res = await payInstance.closeOrder({ let res = await payInstance.closeOrder({
out_trade_no: this.out_trade_no, // 插件支付单号 out_trade_no: this.out_trade_no, // 插件支付单号
}); });
if (res != null && res['errCode'] == 0) { if (res['errCode'] == 0) {
uni.showModal({ uni.showModal({
content: res['errMsg'] as string, content: res['errMsg'] as string,
showCancel: false showCancel: false
...@@ -316,7 +319,7 @@ ...@@ -316,7 +319,7 @@
provider: "wxpay", provider: "wxpay",
provider_pay_type: "jsapi" provider_pay_type: "jsapi"
}); });
if (res != null && res['appid'] != null && res['appid'] != "") { if (res['appid'] != null && res['appid'] != "") {
let appid = res['appid'] as string; let appid = res['appid'] as string;
let redirect_uri = window.location.href.split("?")[0]; let redirect_uri = window.location.href.split("?")[0];
let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`; let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`;
...@@ -327,7 +330,7 @@ ...@@ -327,7 +330,7 @@
async getOpenid(data:UTSJSONObject) : Promise<void> { async getOpenid(data:UTSJSONObject) : Promise<void> {
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance; const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let res = await payInstance.getOpenid(data); let res = await payInstance.getOpenid(data);
if (res != null && res['openid'] != null && res['openid'] != "") { if (res['openid'] != null && res['openid'] != "") {
let openid = res['openid'] as string; let openid = res['openid'] as string;
let code = data['code'] as string; let code = data['code'] as string;
this.openid = openid; this.openid = openid;
......
<template>
<view class="content">
<view class="uni-list">
<radio-group @change="applePriceChange">
<view class="uni-list-cell" v-for="(item, index) in productList" :key="index">
<radio :value="item['product_id']" :checked="product_id == item['product_id']"/>
<view class="price" @click="applePriceClick(item)">{{item['title']}} {{item['goods_price']}}元</view>
</view>
</radio-group>
</view>
<view class="uni-padding-wrap">
<button class="button btn-pay" @click="createOrder" :loading="loading" :disabled="disabled">立即支付</button>
</view>
<!-- 统一支付组件 -->
<uni-pay ref="payRef" :debug="true" :adpid="adpid" return-url="/pages/API/request-payment/request-payment/order-detail" @mounted="onMounted" @success="onSuccess" @fail="onFail" @cancel="onCancel"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
adpid: "1000000001", // uni-ad的广告位id
loading: false, // 支付按钮是否在loading中
disabled: true, // 支付按钮是否禁用
product_id: "", // 用户选择的商品id
// 出售的苹果虚拟商品列表
productList: [
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(消耗性类型: 数量默认是1,最大值是10)
"product_id": "uniappx.consumable.sponsor_1",
"title": "消耗性产品:赞助"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 5, // 单价(元)
"buy_quantity": 1, // 数量(消耗性类型: 数量默认是1,最大值是10)
"product_id": "uniappx.consumable.sponsor_50",
"title": "消耗性产品:赞助"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(非消耗性: 数量只能是1,且一个该类型产品一个appleId只能购买一次)
"product_id": "uniappx.nonconsumable.sponsorskin_1",
"title": "非消耗性产品: 赞助"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(自动续期订阅产品: 数量只能是1)
"product_id": "uniappx.autorenewable.monthly_1",
"title": "自动续期订阅产品:每月定期赞助", // 注意自动续期订阅产品在沙盒模式下,实际周期会缩短到几分钟续期一次(即现实世界几分钟 = 沙盒世界1个月)
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(非自动续期订阅产品: 数量只能是1)
"product_id": "uniappx.nonrenewable.monthly_1",
"title": "非自动续期订阅产品:月赞助",
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量
"product_id": "uniappx.nonrenewable.none",
"title": "测试不存在的产品"
}
] as Array<UTSJSONObject>,
}
},
onLoad: function() {
},
onShow() {
},
onUnload() {},
methods: {
// 支付组件加载完毕后执行
onMounted(insideData: any){
this.init();
},
// 初始化
init() {
this.product_id = this.productList[0]["product_id"] as string;
this.disabled = false;
let payRef = this.$refs['payRef'] as UniPayComponentPublicInstance;
// 苹果虚拟支付未完成订单检测
payRef.appleiapRestore();
},
/**
* 发起支付
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(){
this.order_no = `test`+Date.now();
this.out_trade_no = this.order_no;
let productInfo: UTSJSONObject = this.productList.find((item: UTSJSONObject) : boolean => {
return item['product_id'] == this.product_id;
});
let buy_quantity = productInfo.getNumber('buy_quantity') || 1;
let goods_price = productInfo.getNumber('goods_price');
// 发起支付
this.$refs.payRef.createOrder({
provider: "appleiap", // 支付供应商(这里固定为appleiap,代表苹果虚拟支付)
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
description: productInfo.description,
total_fee: parseInt((goods_price * 100 * buy_quantity).toFixed(0)), // 插件是以分为单位,故这里需要乘以100
// apple_virtual字段仅苹果虚拟支付生效
apple_virtual: {
product_id: this.product_id, // 产品id
goods_price: goods_price, // 单价
buy_quantity: buy_quantity, // 购买数量
},
// 自定义数据
custom: {}
});
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
onFail(err){
uni.showModal({
content: `${err.errSubject} : ${err.errCode} : ${err.errMsg}`,
showCancel: false,
title: `发起支付失败`,
});
},
onCancel(err){
uni.showToast({
title: "用户取消了支付",
icon: 'none'
});
},
// 监听-多选框选中的值改变
applePriceChange(e) {
this.product_id = e.detail.value;
},
applePriceClick(item: any){
this.product_id = item['product_id'] as string;
}
}
}
</script>
<style>
.content {
padding: 15px;
}
.button {
background-color: #007aff;
color: #ffffff;
}
.uni-list-cell {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.price {
margin-left: 10px;
}
.btn-pay {
margin-top: 30px;
}
</style>
...@@ -109,12 +109,15 @@ module.exports = { ...@@ -109,12 +109,15 @@ module.exports = {
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径 "alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
} }
}, },
// ios内购相关(uniapp-x暂不支持) // 苹果虚拟支付相关
"appleiap": { "appleiap": {
// ios内购支付 // 苹果虚拟支付支付,参数获取地址:https://appstoreconnect.apple.com/access/integrations/api/subs
"app": { "app": {
"password": "", // App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数 "appId": "", // 密钥ID
"timeout": 10000, // 请求超时时间,单位:毫秒 "issuerId": "", // Issuer ID
"bundleId": "", // 正式包名(如果dev包名和正式包名一致,则只填bundleId即可)
"devBundleId": "", // dev包名(如果dev包名和正式包名一致,则devBundleId可不填)
"appCertPath": path.join(__dirname, 'appleiap/apiclient_cert.p8'), // 证书路径
"sandbox": true, // 是否是沙箱环境 "sandbox": true, // 是否是沙箱环境
}, },
} }
......
<template> <template>
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask"> <view class="popup-root" :class="'popup-'+type" v-if="isOpen" v-show="isShow" @click="clickMask">
<view @click.stop> <view @click.stop>
<slot></slot> <slot></slot>
</view> </view>
...@@ -20,6 +20,10 @@ ...@@ -20,6 +20,10 @@
type: Boolean, type: Boolean,
default: true default: true
}, },
type: {
type: String,
default: "center"
}
}, },
watch: { watch: {
// 设置show = true 时,如果没有 open 需要设置为 open // 设置show = true 时,如果没有 open 需要设置为 open
...@@ -78,4 +82,7 @@ ...@@ -78,4 +82,7 @@
align-items: center; align-items: center;
z-index: 99; z-index: 99;
} }
.popup-bottom {
justify-content: flex-end;
}
</style> </style>
\ No newline at end of file
...@@ -264,21 +264,17 @@ ...@@ -264,21 +264,17 @@
options['qr_code'] = false; options['qr_code'] = false;
options = objectAssign(options, data); options = objectAssign(options, data);
if (options['provider'] == "appleiap") { if (options['provider'] == "appleiap") {
// #ifndef APP // 苹果虚拟支付走特殊逻辑
uni.showModal({ // #ifdef APP-IOS
title: "提示", return this._appleiapCreateOrder(options);
content: "苹果内购只支持app发起",
showCancel: false
})
// #endif // #endif
// #ifdef APP // #ifndef APP-IOS
uni.showModal({ uni.showModal({
title: "提示", title: "提示",
content: "uni-app x 暂不支持苹果内购", content: "请在iOS系统中执行",
showCancel: false showCancel: false
}) })
// #endif // #endif
return;
} }
// #ifdef APP // #ifdef APP
if (options['provider'] == "wxpay") { if (options['provider'] == "wxpay") {
...@@ -376,15 +372,16 @@ ...@@ -376,15 +372,16 @@
// #ifndef H5 // #ifndef H5
let _order = res.get('order'); let _order = res.get('order');
let orderStr = typeof _order == "string" ? _order as string : JSON.stringify(_order) as string; let orderStr = typeof _order == "string" ? _order as string : JSON.stringify(_order) as string;
console.log('orderStr: ', orderStr)
uni.requestPayment({ uni.requestPayment({
provider: res['provider'] as string, provider: res['provider'] as string,
orderInfo: orderStr, orderInfo: orderStr,
success: (res : RequestPaymentSuccess) => { success: (res) => {
console.log(JSON.stringify(res)) console.log("requestPaymentSuccess", JSON.stringify(res))
this._getOrder(); this._getOrder();
}, },
fail: (err : RequestPaymentFail) => { fail: (err) => {
console.log("RequestPaymentFail", JSON.stringify(err)) console.log("requestPaymentFail", JSON.stringify(err))
let errCode = err.errCode; let errCode = err.errCode;
let errMsg = err.errMsg; let errMsg = err.errMsg;
if (errCode == 700713) { if (errCode == 700713) {
...@@ -399,7 +396,7 @@ ...@@ -399,7 +396,7 @@
this.$emit("fail", err); this.$emit("fail", err);
} }
} }
} as RequestPaymentOptions); });
// #endif // #endif
}, },
// 打开弹窗 // 打开弹窗
...@@ -413,66 +410,99 @@ ...@@ -413,66 +410,99 @@
popupRef.close(); popupRef.close();
}, },
// 查询订单(查询支付情况) // 查询订单(查询支付情况)
async getOrder(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async getOrder(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.getOrder(data); let res = await uniPayCo.getOrder(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 发起退款(此接口需要admin角色才可以访问) // 发起退款(此接口需要admin角色才可以访问)
async refund(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async refund(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.refund(data); let res = await uniPayCo.refund(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 查询退款(查询退款情况) // 查询退款(查询退款情况)
async getRefund(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async getRefund(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.getRefund(data); let res = await uniPayCo.getRefund(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 关闭订单 // 关闭订单
async closeOrder(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async closeOrder(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.closeOrder(data); let res = await uniPayCo.closeOrder(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 获取支持的支付供应商 // 获取支持的支付供应商
async getPayProviderFromCloud(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async getPayProviderFromCloud(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.getPayProviderFromCloud(data); let res = await uniPayCo.getPayProviderFromCloud(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 获取支付配置内的appid(主要用于获取获取微信公众号的appid,用以获取code) // 获取支付配置内的appid(主要用于获取获取微信公众号的appid,用以获取code)
async getProviderAppId(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async getProviderAppId(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.getProviderAppId(data); let res = await uniPayCo.getProviderAppId(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 根据code获取openid // 根据code获取openid
async getOpenid(data : UTSJSONObject) : Promise<UTSJSONObject | null> { async getOpenid(data : UTSJSONObject) : Promise<UTSJSONObject> {
try { try {
let res = await uniPayCo.getOpenid(data); let res = await uniPayCo.getOpenid(data);
return res; return res;
} catch (err) { } catch (err) {
return null return {
errCode: -1,
errMsg: (err as Error).message
}
}
},
// 验证iosIap苹果内购支付凭据
async verifyReceiptFromAppleiap(data : UTSJSONObject) : Promise<UTSJSONObject> {
try {
let res = await uniPayCo.verifyReceiptFromAppleiap(data);
return res;
} catch (err) {
return {
errCode: -1,
errMsg: (err as Error).message
}
} }
}, },
// 支付成功后的逻辑 // 支付成功后的逻辑
...@@ -526,7 +556,7 @@ ...@@ -526,7 +556,7 @@
out_trade_no, out_trade_no,
await_notify: true await_notify: true
}); });
if (res != null) { if (res['errCode'] == 0) {
let has_paid = res.getBoolean('has_paid'); let has_paid = res.getBoolean('has_paid');
if (has_paid != null && has_paid == true) { if (has_paid != null && has_paid == true) {
this.closePopup("qrcodePopup"); this.closePopup("qrcodePopup");
...@@ -544,6 +574,173 @@ ...@@ -544,6 +574,173 @@
if (provider != _provider) { if (provider != _provider) {
this.createOrder({ provider: provider }) this.createOrder({ provider: provider })
} }
},
// 苹果虚拟支付支付逻辑
async _appleiapCreateOrder(options : UTSJSONObject) : Promise<void>{
// #ifndef APP-IOS
uni.showToast({
title: "请在iOS系统中打开",
icon: "none"
})
// #endif
// #ifdef APP-IOS
const virtualPaymentManager = uni.getVirtualPaymentManager();
let createOrderData = {
provider: options.provider,
total_fee: options.total_fee,
order_no: options.order_no,
out_trade_no: options.out_trade_no,
description: options.description,
type: options.type,
apple_virtual: options.apple_virtual,
custom: options.custom,
} as UTSJSONObject;
let res = await uniPayCo.createOrder(createOrderData);
if (res.errCode === 0) {
this.$emit("create", res);
this.res = res;
uni.showLoading({
title: '支付请求中...'
});
try {
// 请求苹果支付
if (this.debug) console.log("正在请求苹果服务器", res.out_trade_no);
uni.requestVirtualPayment({
apple: {
productId: options.getJSON('apple_virtual')!.getString('product_id')!,
appAccountToken: res.appleiap_account_token,
quantity: options.getJSON('apple_virtual')!.getNumber('buy_quantity')! || 1,
},
success: async (requestPaymentRes) => {
uni.hideLoading()
let transaction = requestPaymentRes?.apple;
if (this.debug) console.log('用户支付成功', transaction);
let transactionIdentifier : string = transaction.transactionIdentifier;
let transactionDate : string = transaction.transactionDate;
let outTradeNo : string = res.out_trade_no;
uni.showLoading({
title: '正在处理支付结果...'
});
// 云端请求苹果服务器验证票据
let verifyRes = await this.verifyReceiptFromAppleiap({
out_trade_no: outTradeNo,
transaction_receipt: transaction.jsonRepresentation,
transaction_identifier: transactionIdentifier
});
if (verifyRes.errCode === 0) {
if (verifyRes.repeat) {
uni.showModal({
title: "提示",
content: `当前道具只能购买一次`,
showCancel: false,
confirmText: "好的"
});
} else {
//经过开发者server验证成功后请结束该交易
virtualPaymentManager.finishTransaction({
transaction: transaction,
success: (r) => {
if (this.debug) console.log("关单成功, 该productId= " + transaction.productId)
},
fail: (e) => {
if (this.debug) console.log("关单失败, 该productId= " + transaction.roductId)
}
});
uni.hideLoading();
this.paySuccess(verifyRes);
}
} else {
if (this.debug) console.log('verifyRes: ', verifyRes)
}
},
fail: (err) => {
uni.hideLoading();
if (this.debug) console.log("购买失败:errSubject= " + err.errSubject + ", errCode= " + err.errCode + ", errMsg= " + err.errMsg);
if ([700601].indexOf(err.errCode) > -1 || err.errMsg.indexOf("cancel") > -1) {
this.$emit("cancel", err);
} else {
this.$emit("fail", err);
}
}
});
} catch (err) {
let code = err.errCode || err.code;
if (code === 2) {
// 用户取消支付
if (this.debug) console.log("用户取消支付");
this.$emit("cancel", err);
} else {
// 发起支付失败
console.error("appleiapCreateOrder:fail", err);
this.$emit("fail", err);
}
uni.hideLoading();
}
}
// #endif
},
// 苹果虚拟支付未完成订单检测
appleiapRestore() {
// #ifdef APP-IOS
uni.showLoading({
title: "",
mask: true
});
try {
const virtualPaymentManager = uni.getVirtualPaymentManager();
virtualPaymentManager.getUnfinishedTransactions({
success: async (res) => {
uni.hideLoading()
console.log("获取未结束的订单列表个数:" + res.transactions.length)
res.transactions.forEach(async transaction => {
console.log("getUnfinishedTransactions成功的交易productId= " + transaction.productId);
let appAccountToken : string = transaction.appAccountToken;
let transactionIdentifier : string = transaction.transactionIdentifier;
//let originalTransactionIdentifier : string = transaction.originalTransactionIdentifier;
let transactionDate : string = transaction.transactionDate;
// 云端请求苹果服务器验证票据
let verifyRes = await this.verifyReceiptFromAppleiap({
appleiap_account_token: appAccountToken,
transaction_receipt: transaction.jsonRepresentation,
transaction_identifier: transactionIdentifier,
});
if (verifyRes.errCode === 0 || !appAccountToken) {
// 经过开发者server验证成功后请结束该交易
virtualPaymentManager.finishTransaction({
transaction: transaction,
success: (r) => {
if (this.debug) console.log("关单成功, 该productId= " + transaction.productId)
},
fail: (e) => {
if (this.debug) console.log("关单失败, 该productId= " + transaction.productId)
}
});
uni.hideLoading();
// 如果是自动续期,则不跳页面
if (!verifyRes.is_subscribe && verifyRes.pay_order) {
this.paySuccess(verifyRes);
}
} else {
if (this.debug) console.log('verifyRes: ', verifyRes)
}
})
},
fail: (e) => {
uni.hideLoading()
console.log("获取未结束的订单列表失败:errSubject= " + e.errSubject + ", errCode= " + e.errCode + ", errMsg= " + e.errMsg)
uni.showToast({
title: "获取未结束的订单列表失败:errCode= " + e.errCode,
icon: 'error'
});
}
})
} catch(err){
console.error('err: ', err)
uni.hideLoading()
}
// #endif
} }
}, },
watch: { watch: {
...@@ -839,7 +1036,7 @@ ...@@ -839,7 +1036,7 @@
margin-bottom: 6rpx; margin-bottom: 6rpx;
.qrcode-popup-info-fee { .qrcode-popup-info-fee {
.text{ .text {
color: red; color: red;
font-size: 60rpx; font-size: 60rpx;
font-weight: bold; font-weight: bold;
......
...@@ -15,6 +15,8 @@ const ERROR = { ...@@ -15,6 +15,8 @@ const ERROR = {
51009: 51009, 51009: 51009,
51010: 51010, 51010: 51010,
51011: 51011, 51011: 51011,
51012: 51012,
51013: 51013,
// 数据不存在 // 数据不存在
52001: 52001, 52001: 52001,
52002: 52002, 52002: 52002,
...@@ -24,6 +26,8 @@ const ERROR = { ...@@ -24,6 +26,8 @@ const ERROR = {
53003: 53003, 53003: 53003,
53004: 53004, 53004: 53004,
53005: 53005, 53005: 53005,
54001: 54001,
54002: 54002,
} }
const errSubject = "uni-pay"; const errSubject = "uni-pay";
......
...@@ -95,6 +95,7 @@ module.exports = { ...@@ -95,6 +95,7 @@ module.exports = {
clientInfo, // 兼容云对象调用云对象模式 clientInfo, // 兼容云对象调用云对象模式
cloudInfo, // 兼容云对象调用云对象模式 cloudInfo, // 兼容云对象调用云对象模式
wxpay_virtual, // 仅用于微信虚拟支付 wxpay_virtual, // 仅用于微信虚拟支付
apple_virtual, // 仅用于苹果虚拟支付
} = data; } = data;
if (!clientInfo) clientInfo = this.getClientInfo(); if (!clientInfo) clientInfo = this.getClientInfo();
...@@ -118,6 +119,7 @@ module.exports = { ...@@ -118,6 +119,7 @@ module.exports = {
clientInfo, clientInfo,
cloudInfo, cloudInfo,
wxpay_virtual, wxpay_virtual,
apple_virtual,
}); });
// uniappx-特殊处理 // uniappx-特殊处理
if (typeof res.order === "object" && typeof res.order["timestamp"] === "string") { if (typeof res.order === "object" && typeof res.order["timestamp"] === "string") {
...@@ -266,13 +268,17 @@ module.exports = { ...@@ -266,13 +268,17 @@ module.exports = {
async verifyReceiptFromAppleiap(data) { async verifyReceiptFromAppleiap(data) {
let { let {
out_trade_no, out_trade_no,
appleiap_account_token,
transaction_receipt, transaction_receipt,
transaction_identifier, transaction_identifier,
} = data; } = data;
const clientInfo = this.getClientInfo();
return await service.pay.verifyReceiptFromAppleiap({ return await service.pay.verifyReceiptFromAppleiap({
out_trade_no, out_trade_no,
appleiap_account_token,
transaction_receipt, transaction_receipt,
transaction_identifier transaction_identifier,
clientInfo,
}); });
}, },
...@@ -289,5 +295,26 @@ module.exports = { ...@@ -289,5 +295,26 @@ module.exports = {
cloudInfo cloudInfo
}); });
}, },
/**
* 请求微信小程序虚拟支付API
*/
async requestWxpayVirtualApi(data) {
const clientInfo = this.getClientInfo();
if (clientInfo.source !== "function") {
throw new Error("requestWxpayVirtualApi只能通过云端调云端的方式调用");
}
let res = await service.pay.requestWxpayVirtualApi(data);
return res;
},
/**
* 测试请求,仅为了确保是否请求能调通
*/
async test(data) {
return {
errCode: 0,
errMsg: "ok"
};
},
} }
\ No newline at end of file
...@@ -16,6 +16,7 @@ const sentence = { ...@@ -16,6 +16,7 @@ const sentence = {
51010: 'Invalid out_trade_no or transaction_id', 51010: 'Invalid out_trade_no or transaction_id',
51011: 'Invalid wxpay_virtual', 51011: 'Invalid wxpay_virtual',
51012: 'Invalid buy_quantity', 51012: 'Invalid buy_quantity',
51013: 'Invalid apple_virtual',
52001: 'NotExist payOrder', 52001: 'NotExist payOrder',
52002: 'NotExist notifyUrl', 52002: 'NotExist notifyUrl',
53001: 'Create payment error', 53001: 'Create payment error',
......
...@@ -15,7 +15,8 @@ const sentence = { ...@@ -15,7 +15,8 @@ const sentence = {
51009: 'cloudInfo不能为空', 51009: 'cloudInfo不能为空',
51010: '支付单号或第三方交易单号不能同时为空', 51010: '支付单号或第三方交易单号不能同时为空',
51011: '微信虚拟支付参数(wxpay_virtual)不能为空', 51011: '微信虚拟支付参数(wxpay_virtual)不能为空',
51012: '代币购买数量(buy_quantity)不能为空', 51012: '购买数量(buy_quantity)不能为空',
51013: '苹果虚拟支付参数(apple_virtual)不能为空',
52001: '支付订单不存在', 52001: '支付订单不存在',
52002: '请先配置正确的异步回调URL', 52002: '请先配置正确的异步回调URL',
53001: '获取支付信息失败,请稍后再试', 53001: '获取支付信息失败,请稍后再试',
...@@ -23,7 +24,7 @@ const sentence = { ...@@ -23,7 +24,7 @@ const sentence = {
53003: '查询退款信息失败,请稍后再试', 53003: '查询退款信息失败,请稍后再试',
53004: '关闭订单失败,请稍后再试', 53004: '关闭订单失败,请稍后再试',
53005: '证书错误,请检查支付证书', 53005: '证书错误,请检查支付证书',
54001: 'ios内购凭据校验不通过', 54001: '苹果虚拟支付凭据校验不通过',
54002: '订单未支付' 54002: '订单未支付'
}; };
......
...@@ -82,6 +82,19 @@ util.aes.decrypt = function(obj) { ...@@ -82,6 +82,19 @@ util.aes.decrypt = function(obj) {
return decrypted; return decrypted;
}; };
util.generateUUID = function() {
// 获取当前时间戳
let timestamp = Date.now().toString(16);
while (timestamp.length < 16) {
timestamp = timestamp + "0";
}
// 生成随机数部分
const randomHex = crypto.randomBytes(10).toString('hex');
// 结合时间戳和随机数,并按照UUID格式排列
const uuid = `${timestamp.slice(0, 8)}-${timestamp.slice(8, 12)}-${randomHex.slice(0, 4)}-${randomHex.slice(4, 8)}-${randomHex.slice(8)}`;
return uuid.toLowerCase();
};
module.exports = util; module.exports = util;
// aes192算法 - 加密 // aes192算法 - 加密
......
...@@ -3,11 +3,13 @@ const alipay = require('./alipay'); ...@@ -3,11 +3,13 @@ const alipay = require('./alipay');
const common = require('./common'); const common = require('./common');
const qrcode = require('./qrcode'); // 此源码为npm i qrcode的压缩版本 const qrcode = require('./qrcode'); // 此源码为npm i qrcode的压缩版本
const crypto = require('./crypto'); const crypto = require('./crypto');
const jsonwebtoken = require('./jsonwebtoken');
module.exports = { module.exports = {
wxpay, wxpay,
alipay, alipay,
common, common,
qrcode, qrcode,
crypto crypto,
jsonwebtoken
}; };
...@@ -12,6 +12,6 @@ ...@@ -12,6 +12,6 @@
"path": "/uni-pay-co", "path": "/uni-pay-co",
"timeout": 60, "timeout": 60,
"triggers": [], "triggers": [],
"runtime": "Nodejs8" "runtime": "Nodejs18"
} }
} }
...@@ -187,6 +187,7 @@ class service { ...@@ -187,6 +187,7 @@ class service {
clientInfo, // 客户端信息 clientInfo, // 客户端信息
cloudInfo, // 云端信息 cloudInfo, // 云端信息
wxpay_virtual, // 仅用于微信虚拟支付 wxpay_virtual, // 仅用于微信虚拟支付
apple_virtual, // 仅用于苹果虚拟支付
} = data; } = data;
let subject = description; let subject = description;
let body = description; let body = description;
...@@ -204,6 +205,13 @@ class service { ...@@ -204,6 +205,13 @@ class service {
if (typeof wxpay_virtual.buy_quantity !== "number" || wxpay_virtual.buy_quantity <= 0) { if (typeof wxpay_virtual.buy_quantity !== "number" || wxpay_virtual.buy_quantity <= 0) {
throw { errCode: ERROR[51012] }; throw { errCode: ERROR[51012] };
} }
} else if (provider === "appleiap") {
if (typeof apple_virtual !== "object") {
throw { errCode: ERROR[51013] };
}
if (typeof apple_virtual.buy_quantity !== "number" || apple_virtual.buy_quantity <= 0) {
throw { errCode: ERROR[51012] };
}
} else { } else {
if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) { if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) {
throw { errCode: ERROR[51005] }; throw { errCode: ERROR[51005] };
...@@ -397,6 +405,11 @@ class service { ...@@ -397,6 +405,11 @@ class service {
let userInfo = await dao.uniIdUsers.findById(user_id); let userInfo = await dao.uniIdUsers.findById(user_id);
if (userInfo) nickname = userInfo.nickname; if (userInfo) nickname = userInfo.nickname;
} }
let appleiap_account_token;
if (provider === "appleiap") {
appleiap_account_token = libs.crypto.generateUUID();
res.appleiap_account_token = appleiap_account_token;
}
await dao.uniPayOrders.add({ await dao.uniPayOrders.add({
provider, provider,
provider_pay_type, provider_pay_type,
...@@ -419,6 +432,7 @@ class service { ...@@ -419,6 +432,7 @@ class service {
custom, custom,
create_date, create_date,
expand_data, expand_data,
appleiap_account_token, // 苹果虚拟支付专用字段
stat_data: { stat_data: {
platform: stat_platform, platform: stat_platform,
app_version: clientInfo.appVersion, app_version: clientInfo.appVersion,
...@@ -487,7 +501,7 @@ class service { ...@@ -487,7 +501,7 @@ class service {
console.log('queryRes: ', queryRes) console.log('queryRes: ', queryRes)
} else { } else {
// 无uniPayInstance.orderQuery函数时的兼容处理 // 无uniPayInstance.orderQuery函数时的兼容处理
if ([1,2].indexOf(payOrderInfo.status) > -1) { if ([1, 2].indexOf(payOrderInfo.status) > -1) {
queryRes = { queryRes = {
tradeState: "SUCCESS", tradeState: "SUCCESS",
tradeStateDesc: "订单已支付" tradeStateDesc: "订单已支付"
...@@ -806,7 +820,7 @@ class service { ...@@ -806,7 +820,7 @@ class service {
uniPayConifg = wxpayVirtualPayConifg; uniPayConifg = wxpayVirtualPayConifg;
needCacheSessionKey = true; needCacheSessionKey = true;
} }
} catch(err){} } catch (err) {}
let res = await libs.wxpay.getOpenid({ let res = await libs.wxpay.getOpenid({
config: uniPayConifg, config: uniPayConifg,
...@@ -860,62 +874,207 @@ class service { ...@@ -860,62 +874,207 @@ class service {
async verifyReceiptFromAppleiap(data) { async verifyReceiptFromAppleiap(data) {
let { let {
out_trade_no, out_trade_no,
appleiap_account_token,
transaction_receipt, transaction_receipt,
transaction_identifier, transaction_identifier,
clientInfo,
} = data; } = data;
if (!out_trade_no) { if (!out_trade_no) {
throw { errCode: ERROR[51001] }; if (!appleiap_account_token) {
return {
errCode: 0,
errMsg: "Invalid out_trade_no"
} }
// 初始化uniPayInstance }
let uniPayInstance = await this.initUniPayInstance({ provider: "appleiap", provider_pay_type: "app" }); appleiap_account_token = appleiap_account_token.toLowerCase(); // 转小写
let verifyReceiptRes = await uniPayInstance.verifyReceipt({ let payOrderInfo = await dao.uniPayOrders.find({
receiptData: transaction_receipt provider: "appleiap",
appleiap_account_token
});
if (!payOrderInfo || !payOrderInfo.out_trade_no) {
return {
errCode: 0,
errMsg: "Invalid out_trade_no"
}
}
out_trade_no = payOrderInfo.out_trade_no;
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no,
}); });
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
const verifyReceipt = async (uniPayConifg) => {
const jwt = libs.jsonwebtoken;
const fs = require('fs');
const privateKey = fs.readFileSync(uniPayConifg.appCertPath, 'utf8');
const header = {
alg: 'ES256',
kid: uniPayConifg.appId, // 替换为您的密钥ID
typ: "JWT"
};
const nowTime = Date.now();
const bundleId = uniPayConifg.sandbox ? uniPayConifg.devBundleId || uniPayConifg.bundleId : uniPayConifg.bundleId;
const payload = {
iss: uniPayConifg.issuerId, // 替换为您的团队ID
iat: Math.floor(nowTime / 1000), // 当前时间戳
exp: Math.floor(nowTime / 1000) + 3600, // 当前时间戳加1小时
aud: 'appstoreconnect-v1',
bid: bundleId
};
const iapToken = jwt.sign(payload, privateKey, {
algorithm: 'ES256',
header: header
});
const serviceUrl = uniPayConifg.sandbox ? "https://api.storekit-sandbox.itunes.apple.com" : "https://api.appstoreconnect.apple.com";
const url = `${serviceUrl}/inApps/v1/transactions/${transaction_identifier}`;
let requestRes;
// 如果请求苹果服务器失败,则重试5次
for (let i = 0; i <= 5; i++) {
try {
requestRes = await uniCloud.request({
method: "GET",
header: {
'Authorization': `Bearer ${iapToken}`,
'Content-Type': 'application/json'
},
url
});
break;
} catch (err) {
// console.log('errCode: ', err.code || err.errCode, 'errMsg: ', err.message || err.errMsg)
}
}
if (requestRes.statusCode !== 200) {
return {};
}
const signedInfoTokenArr = requestRes.data.signedTransactionInfo.split('.');
const signedInfoString = Buffer.from(signedInfoTokenArr[1], 'base64').toString('utf8');
const verifyReceiptRes = JSON.parse(signedInfoString);
const appAccountToken = verifyReceiptRes.appAccountToken.toLowerCase();
verifyReceiptRes.tradeState = verifyReceiptRes.inAppOwnershipType === "PURCHASED" && payOrderInfo.appleiap_account_token === appAccountToken ? "SUCCESS" : "fail";
return verifyReceiptRes;
};
let uniPayConifg = await this.getUniPayConfig({ provider: "appleiap", provider_pay_type: "app" });
let verifyReceiptRes = await verifyReceipt(uniPayConifg);
let userOrderSuccess = false; let userOrderSuccess = false;
let pay_date; let pay_date;
if (verifyReceiptRes.tradeState !== "SUCCESS") { if (verifyReceiptRes.tradeState !== "SUCCESS") {
throw { errCode: ERROR[54002] }; // 尝试使用相反的环境再次验证
} console.log('尝试使用相反的环境再次验证: ');
// 支付成功 verifyReceiptRes = await verifyReceipt({
pay_date = Number(verifyReceiptRes.receipt.receipt_creation_date_ms); ...uniPayConifg,
let inAppList = verifyReceiptRes.receipt.in_app; sandbox: !uniPayConifg.sandbox
let inApp = inAppList.find((item) => {
return item.transaction_id === transaction_identifier;
}); });
if (!inApp) { if (verifyReceiptRes.tradeState !== "SUCCESS") {
// 校验不通过 // 如果还是不成功,则校验不通过
throw { errCode: ERROR[54002] }; throw { errCode: ERROR[54002] };
} }
let quantity = inApp.quantity; // 购买数量 }
let product_id = inApp.product_id; // 对应的内购产品id
let transaction_id = inApp.transaction_id; // 本次交易id //console.log('verifyReceiptRes: ', verifyReceiptRes)
let isSubscribe = false;
if (["Auto-Renewable Subscription"].indexOf(verifyReceiptRes.type) > -1) {
isSubscribe = true; // 标记为自动订阅订单
}
if ((Date.now() - 1000 * 3600 * 24) > pay_date) { // 支付成功
// 订单已超24小时,不做处理,通知前端直接关闭订单。 pay_date = Number(verifyReceiptRes.purchaseDate);
let quantity = verifyReceiptRes.quantity; // 购买数量
let product_id = verifyReceiptRes.productId; // 对应的内购产品id
let transaction_id = verifyReceiptRes.transactionId; // 本次交易id
let original_transaction_id = verifyReceiptRes.originalTransactionId; // 原始交易id
if ((Date.now() - 1000 * 3600 * 72) > pay_date && !isSubscribe) {
// 非自动订阅订单,若超72小时,不做处理,通知前端直接关闭订单。
return { return {
errCode: 0, errCode: 0,
errMsg: "ok" errMsg: "ok"
}; };
} }
if (isSubscribe && original_transaction_id !== transaction_id) {
let findOrderInfo = await dao.uniPayOrders.find({
appleiap_account_token: payOrderInfo.appleiap_account_token,
user_order_success: _.exists(true)
});
if (findOrderInfo) {
// 自动订阅产品自动续期时需要创建新的支付订单
let quantity = verifyReceiptRes.quantity;
let goods_price = parseFloat((verifyReceiptRes.price / 1000).toFixed(2));
let total_fee = parseFloat((goods_price * 100 * quantity).toFixed(2));
let description = "[自动续期]" + payOrderInfo.description.replace(/\[自动续期\]/g, '');
// 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
let stat_platform = clientInfo.platform;
if (stat_platform === "app") {
stat_platform = clientInfo.os;
}
// 创建新的支付订单
let addId = await dao.uniPayOrders.add({
provider: payOrderInfo.provider,
provider_pay_type: payOrderInfo.provider_pay_type,
uni_platform: clientInfo.platform,
status: 0,
type: payOrderInfo.type,
order_no: payOrderInfo.order_no,
out_trade_no: transaction_id,
user_id: payOrderInfo.user_id,
nickname: payOrderInfo.nickname,
device_id: clientInfo.deviceId,
client_ip: clientInfo.client_ip,
openid: payOrderInfo.openid,
description,
total_fee,
refund_fee: 0,
refund_count: 0,
provider_appid: uniPayConifg.appId,
appid: clientInfo.appId,
custom: payOrderInfo.custom,
create_date: Date.now(),
expand_data: payOrderInfo.expand_data,
appleiap_account_token, // 苹果虚拟支付专用字段
stat_data: {
platform: stat_platform,
app_version: clientInfo.appVersion,
app_version_code: clientInfo.appVersionCode,
app_wgt_version: clientInfo.appWgtVersion,
os: clientInfo.os,
ua: clientInfo.ua,
channel: clientInfo.channel ? clientInfo.channel : String(clientInfo.scene),
scene: clientInfo.scene
}
});
payOrderInfo = await dao.uniPayOrders.find({
_id: addId,
});
out_trade_no = transaction_id;
}
}
// 查询该transaction_id是否使用过,如果已使用,则不做处理,通知前端直接关闭订单。 // 查询该transaction_id是否使用过,如果已使用,则不做处理,通知前端直接关闭订单。
let findOrderInfo = await dao.uniPayOrders.find({ let findOrderInfo = await dao.uniPayOrders.find({
transaction_id, transaction_id,
}); });
if (findOrderInfo) { const repeatReceipt = () => {
return { return {
errCode: 0, errCode: 0,
errMsg: "ok" errMsg: "ok",
repeat: true, // 代表重复通知了
};
}; };
if (findOrderInfo) {
// 不允许重复通知
return repeatReceipt();
} }
// 否则,执行用户回调 // 否则,执行用户回调
// 用户自己的逻辑处理 开始----------------------------------------------------------- // 用户自己的逻辑处理 开始-----------------------------------------------------------
let orderPaySuccess; let orderPaySuccess;
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no,
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
try { try {
// 加载自定义异步回调函数 // 加载自定义异步回调函数
orderPaySuccess = require(`../notify/${payOrderInfo.type}`); orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
...@@ -923,7 +1082,7 @@ class service { ...@@ -923,7 +1082,7 @@ class service {
console.log(err); console.log(err);
} }
if (typeof orderPaySuccess === "function") { if (typeof orderPaySuccess === "function") {
payOrderInfo = await dao.uniPayOrders.updateAndReturn({ let newPayOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: { whereJson: {
status: 0, // status:0 为必须条件,防止重复推送时的错误 status: 0, // status:0 为必须条件,防止重复推送时的错误
out_trade_no: out_trade_no, // 商户订单号 out_trade_no: out_trade_no, // 商户订单号
...@@ -936,6 +1095,11 @@ class service { ...@@ -936,6 +1095,11 @@ class service {
original_data: verifyReceiptRes original_data: verifyReceiptRes
} }
}); });
if (!newPayOrderInfo) {
// 不允许重复通知
return repeatReceipt();
}
payOrderInfo = newPayOrderInfo;
console.log('用户自己的回调逻辑 - 开始执行'); console.log('用户自己的回调逻辑 - 开始执行');
userOrderSuccess = await orderPaySuccess({ userOrderSuccess = await orderPaySuccess({
verifyResult: verifyReceiptRes, verifyResult: verifyReceiptRes,
...@@ -969,6 +1133,7 @@ class service { ...@@ -969,6 +1133,7 @@ class service {
status: payOrderInfo.status, // 标记当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款 status: payOrderInfo.status, // 标记当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款
user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功) user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功)
pay_order: payOrderInfo, pay_order: payOrderInfo,
is_subscribe: isSubscribe
}; };
} }
...@@ -1007,7 +1172,7 @@ class service { ...@@ -1007,7 +1172,7 @@ class service {
if (uniPayConifg.version === 3) { if (uniPayConifg.version === 3) {
try { try {
uniPayInstance = uniPay.initWeixinV3(uniPayConifg); uniPayInstance = uniPay.initWeixinV3(uniPayConifg);
} catch(err){ } catch (err) {
console.error(err); console.error(err);
let errMsg = err.message; let errMsg = err.message;
if (errMsg && errMsg.indexOf("invalid base64 body") > -1) { if (errMsg && errMsg.indexOf("invalid base64 body") > -1) {
...@@ -1022,11 +1187,25 @@ class service { ...@@ -1022,11 +1187,25 @@ class service {
// 支付宝 // 支付宝
uniPayInstance = uniPay.initAlipay(uniPayConifg); uniPayInstance = uniPay.initAlipay(uniPayConifg);
} else if (provider === "appleiap") { } else if (provider === "appleiap") {
// ios内购 // 苹果虚拟支付
uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg); uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
} else if (provider === "wxpay-virtual") { } else if (provider === "wxpay-virtual") {
// 微信虚拟支付 // 微信虚拟支付
// 还需要额外传accessToken // 还需要额外传accessToken
uniPayConifg.accessToken = await this.getAccessToken(data);
uniPayInstance = uniPay.initWeixinVirtualPayment(uniPayConifg);
} else {
throw new Error(`${provider} : 不支持的支付方式`);
}
return uniPayInstance;
}
/**
* 获取accessToken
* let uniPayInstance = await service.pay.getAccessToken({ provider, provider_pay_type });
*/
async getAccessToken(data = {}) {
let uniPayConifg = await this.getUniPayConfig(data);
let cacheKey = { let cacheKey = {
appId: uniPayConifg.appId, appId: uniPayConifg.appId,
platform: "weixin-mp" platform: "weixin-mp"
...@@ -1034,21 +1213,61 @@ class service { ...@@ -1034,21 +1213,61 @@ class service {
let cacheInfo = await dao.opendbOpenData.getAccessToken(cacheKey); let cacheInfo = await dao.opendbOpenData.getAccessToken(cacheKey);
if (cacheInfo) { if (cacheInfo) {
// 缓存有值 // 缓存有值
uniPayConifg.accessToken = cacheInfo.access_token; return cacheInfo.access_token;
} else { } else {
// 缓存无值 // 缓存无值
let getAccessTokenRes = await libs.wxpay.getAccessToken(uniPayConifg); let getAccessTokenRes = await libs.wxpay.getAccessToken(uniPayConifg);
uniPayConifg.accessToken = getAccessTokenRes.accessToken; let accessToken = getAccessTokenRes.accessToken;
// 缓存accessToken // 缓存accessToken
await dao.opendbOpenData.setAccessToken(cacheKey, { await dao.opendbOpenData.setAccessToken(cacheKey, {
access_token: getAccessTokenRes.accessToken, access_token: getAccessTokenRes.accessToken,
}, getAccessTokenRes.expiresIn); }, getAccessTokenRes.expiresIn);
return accessToken;
} }
uniPayInstance = uniPay.initWeixinVirtualPayment(uniPayConifg);
} else {
throw new Error(`${provider} : 不支持的支付方式`);
} }
return uniPayInstance;
/**
* 获取sessionKey
* let sessionKey = await service.pay.getSessionKey({ provider, provider_pay_type, openid });
*/
async getSessionKey(data = {}) {
let {
openid,
} = data;
// 获取用户的sessionKey
let uniPayConifg = await this.getUniPayConfig(data);
let { session_key } = await dao.opendbOpenData.getSessionKey({
appId: uniPayConifg.appId,
platform: "weixin-mp",
openid
});
return session_key;
}
/**
* 请求微信小程序虚拟支付API
* let res = await service.pay.requestWxpayVirtualApi(data);
*/
async requestWxpayVirtualApi(options = {}) {
let {
method,
data = {}
} = options;
// 微信虚拟支付固定参数
let provider = "wxpay-virtual";
let provider_pay_type = "mp";
// 获得微信小程序虚拟支付实例
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
// 调用微信小程序虚拟支付云端API
if (["currencyPay"].indexOf(method) > -1) {
if (!data.sessionKey) {
data.sessionKey = await this.getSessionKey({ ...data, provider, provider_pay_type });
}
}
let res = await uniPayInstance[method](data);
return res;
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册