refactor: 确立独立子工程架构 (backend, admin, mpuniapp) 并同步更新 AGENTS.md 规范
这个提交包含在:
@@ -0,0 +1,231 @@
|
||||
<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 setup lang="uts">
|
||||
|
||||
const order_no = ref("") // 业务系统订单号(即你自己业务系统的订单表的订单号)
|
||||
const out_trade_no = ref("") // 插件支付单号
|
||||
const adpid = ref("1000000001") // uni-ad的广告位id
|
||||
const loading = ref(false) // 支付按钮是否在loading中
|
||||
const disabled = ref(true) // 支付按钮是否禁用
|
||||
const product_id = ref("") // 用户选择的商品id
|
||||
|
||||
function getPackageName() : string {
|
||||
const res = uni.getAppBaseInfo();
|
||||
let packageName : string = ""
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
packageName = res.packageName
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-IOS
|
||||
packageName = res.bundleId
|
||||
// #endif
|
||||
|
||||
return packageName
|
||||
}
|
||||
|
||||
function isDebug() : boolean {
|
||||
if (getPackageName() == 'io.dcloud.uniappx') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 出售的苹果虚拟商品列表
|
||||
const productList = computed(() => {
|
||||
return [
|
||||
{
|
||||
"description": "为DCloud提供的免费软件进行赞助",
|
||||
"goods_price": 1, // 单价(元)
|
||||
"buy_quantity": 1, // 数量(消耗性类型: 数量默认是1,最大值是10)
|
||||
"product_id": isDebug() ? "uniappx.consumable.sponsor_1" : "uniappx.consumable.sponsor1",
|
||||
"product_type": "consumable", // 消耗性类型
|
||||
"title": "消耗性产品:赞助"
|
||||
},
|
||||
{
|
||||
"description": "为DCloud提供的免费软件进行赞助",
|
||||
"goods_price": 5, // 单价(元)
|
||||
"buy_quantity": 1, // 数量(消耗性类型: 数量默认是1,最大值是10)
|
||||
"product_id": isDebug() ? "uniappx.consumable.sponsor_50" : "uniappx.consumable.sponsor50",
|
||||
"product_type": "consumable", // 消耗性类型
|
||||
"title": "消耗性产品:赞助"
|
||||
},
|
||||
{
|
||||
"description": "为DCloud提供的免费软件进行赞助",
|
||||
"goods_price": 1, // 单价(元)
|
||||
"buy_quantity": 1, // 数量(非消耗性: 数量只能是1,且一个该类型产品一个appleId只能购买一次)
|
||||
"product_id": isDebug() ? "uniappx.nonconsumable.sponsorskin_1" : "uniappx.nonconsumable.sponsorskin1",
|
||||
"product_type": "nonconsumable", // 非消耗性类型
|
||||
"title": "非消耗性产品: 赞助"
|
||||
},
|
||||
// {
|
||||
// "description": "为DCloud提供的免费软件进行赞助",
|
||||
// "goods_price": 1, // 单价(元)
|
||||
// "buy_quantity": 1, // 数量(自动续期订阅产品: 数量只能是1)
|
||||
// "product_id": isDebug() ? "uniappx.autorenewable.monthly_1" : "uniappx.autorenewable.monthly1",
|
||||
// "product_type": "autorenewable", // 自动续期订阅产品
|
||||
// "title": "自动续期订阅产品:每月定期赞助", // 注意自动续期订阅产品在沙盒模式下,实际周期会缩短到几分钟续期一次(即现实世界几分钟 = 沙盒世界1个月)
|
||||
// },
|
||||
{
|
||||
"description": "为DCloud提供的免费软件进行赞助",
|
||||
"goods_price": 1, // 单价(元)
|
||||
"buy_quantity": 1, // 数量(非自动续期订阅产品: 数量只能是1)
|
||||
"product_id": isDebug() ? "uniappx.nonrenewable.monthly_1" : "uniappx.nonrenewable.monthly1",
|
||||
"product_type": "nonrenewable", // 非自动续期订阅产品
|
||||
"title": "非自动续期订阅产品:月赞助",
|
||||
},
|
||||
// {
|
||||
// "description": "为DCloud提供的免费软件进行赞助",
|
||||
// "goods_price": 1, // 单价(元)
|
||||
// "buy_quantity": 1, // 数量
|
||||
// "product_id": "uniappx.nonrenewable.none",
|
||||
// "product_type": "consumable", // 消耗性类型
|
||||
// "title": "测试不存在的产品"
|
||||
// }
|
||||
] as Array<UTSJSONObject>
|
||||
})
|
||||
|
||||
const payRef = ref<UniPayComponentPublicInstance | null>(null)
|
||||
|
||||
// 支付组件加载完毕后执行
|
||||
function onMounted(insideData : any) {
|
||||
init();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
product_id.value = productList.value[0]["product_id"] as string;
|
||||
disabled.value = false;
|
||||
if (payRef.value != null) {
|
||||
// 苹果虚拟支付未完成订单检测
|
||||
payRef.value!.appleiapRestore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起支付
|
||||
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
|
||||
*/
|
||||
function createOrder() {
|
||||
order_no.value = `test` + Date.now();
|
||||
out_trade_no.value = order_no.value;
|
||||
let productInfo : UTSJSONObject = productList.value.find((item : UTSJSONObject) : boolean => {
|
||||
return item['product_id'] == product_id.value;
|
||||
}) || {};
|
||||
let buy_quantity = productInfo.getNumber('buy_quantity') || 1;
|
||||
let goods_price = productInfo.getNumber('goods_price') || 1;
|
||||
let product_type = productInfo['product_type'];
|
||||
// 发起支付
|
||||
if (payRef.value != null) {
|
||||
payRef.value!.createOrder({
|
||||
provider: "appleiap", // 支付供应商(这里固定为appleiap,代表苹果虚拟支付)
|
||||
order_no: order_no.value, // 业务系统订单号(即你自己业务系统的订单表的订单号)
|
||||
out_trade_no: out_trade_no.value, // 插件支付单号
|
||||
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
|
||||
description: productInfo.description,
|
||||
total_fee: parseInt((goods_price * 100 * buy_quantity).toFixed(0)), // 插件是以分为单位,故这里需要乘以100
|
||||
// apple_virtual字段仅苹果虚拟支付生效
|
||||
apple_virtual: {
|
||||
product_id: product_id.value, // 产品id
|
||||
goods_price: goods_price, // 单价
|
||||
buy_quantity: buy_quantity, // 购买数量
|
||||
product_type: product_type // 产品类型
|
||||
},
|
||||
// 自定义数据
|
||||
custom: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 监听事件 - 支付成功
|
||||
function onSuccess(res: any) {
|
||||
console.log('success: ', res);
|
||||
if (res.user_order_success) {
|
||||
// 代表用户已付款,且你自己写的回调成功并正确执行了
|
||||
|
||||
} else {
|
||||
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function onFail(err: any) {
|
||||
uni.showModal({
|
||||
content: `${err.errSubject} : ${err.errCode} : ${err.errMsg}`,
|
||||
showCancel: false,
|
||||
title: `发起支付失败`,
|
||||
});
|
||||
}
|
||||
|
||||
function onCancel(err: any) {
|
||||
uni.showToast({
|
||||
title: "用户取消了支付",
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
// 监听-多选框选中的值改变
|
||||
function applePriceChange(e: any) {
|
||||
product_id.value = e.detail.value;
|
||||
}
|
||||
|
||||
function applePriceClick(item : any) {
|
||||
product_id.value = item['product_id'] as string;
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
})
|
||||
|
||||
onPageShow(() => {
|
||||
|
||||
})
|
||||
|
||||
onUnload(() => { })
|
||||
</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>
|
||||
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<!-- #ifdef APP && !VUE3-VAPOR -->
|
||||
<scroll-view style="flex: 1;">
|
||||
<!-- #endif -->
|
||||
<page-head title="虚拟支付"></page-head>
|
||||
<view style="padding-left: 20px; padding-right: 20px;">
|
||||
<text>
|
||||
requestVirtualPayment api 适用于消耗性类型、非消耗性类型、自动续期订阅类型、非自动续期订阅类型产品的购买。\n\n
|
||||
1. 消耗性类型:该类型的产品可以设置购买数量,默认是1,最大值是10; \n
|
||||
2. 非消耗性类型、自动续期订阅类型、非自动续期订阅类型: 这些类型的产品购买数量设置无效,数量只能是1; \n
|
||||
3. 非消耗性类型:该类型产品一个appleId只能购买一次,可以在任何登陆该appleId账号的设备上restore获取。
|
||||
</text>
|
||||
<button style="margin-top: 20px;" type="primary" v-for="(item,index) in productList" :key="index"
|
||||
@click="requestVirtualPayment(item)">{{item.name}}</button>
|
||||
|
||||
<text>
|
||||
\nrestoreTransactions api 适用于非消耗性类型、自动续期订阅类型、非自动续期订阅类型产品的购买。\n\n
|
||||
1. 非消耗性类型: 返回每个已购买的非消耗性类型产品的购买记录;\n
|
||||
2. 自动续期订阅类型: 返回每个已购买的自动续期订阅类型产品的最新购买记录,沙盒账号最多可自动续订 12 次;\n
|
||||
3. 非自动续期订阅类型: 返回每个已购买的非自动续期订阅类型产品的最新购买记录, 该类型订阅可以连续多次购买,开发者需要自己的后台计算产品过期的时间。\n
|
||||
Note: 不能用来恢复消耗性类型的购买记录。
|
||||
</text>
|
||||
<button style="margin-top: 20px;" type="primary" @click="restoreTransactions">恢复购买订单列表</button>
|
||||
|
||||
<text>
|
||||
\ngetUnfinishedTransactions api 适用于获取未完成的各种类型产品的购买记录(用来防止丢单)。\n\n
|
||||
1. 比如用户点击购买已经付款成功,但因网络、手机没电关机等特殊情况,Apple IAP
|
||||
没有返回客户端对应的购买凭证,从而导致无法finish该交易导致的丢单,可以在需要的地方调用该api获得此类未finished的交易记录; \n
|
||||
2. 针对消耗性类型产品交易,只能通过该api获取未finished的交易,防止丢单;\n
|
||||
3. 对于其他类型产品未finished交易, 不仅可以通过该api获取,也可以通过restoreTransactions api (也可获取已经finished的交易)获取;
|
||||
</text>
|
||||
<button style="margin-top: 20px; margin-bottom: 50px;" type="primary"
|
||||
@click="getUnfinishedTransactions">获取未结束的订单列表</button>
|
||||
</view>
|
||||
<!-- #ifdef APP && !VUE3-VAPOR -->
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="uts">
|
||||
export type PayItem = { id : string, name : string, quantity ?: number }
|
||||
|
||||
const productList = ref<PayItem[]>([])
|
||||
const virtualPaymentManager = ref<Object>(Object)
|
||||
|
||||
const getPackageName = (): string => {
|
||||
const res = uni.getAppBaseInfo();
|
||||
let packageName : string = ""
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
packageName = res.packageName
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-IOS
|
||||
packageName = res.bundleId
|
||||
// #endif
|
||||
|
||||
return packageName
|
||||
}
|
||||
|
||||
const isDebug = (): boolean => {
|
||||
if (getPackageName() == 'io.dcloud.uniappx') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isProd = (): boolean => {
|
||||
if (getPackageName() == 'io.dcloud.hellouniappx') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isCustom = (): boolean => {
|
||||
if (isDebug() == false && isProd() == false) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const initProductList = () => {
|
||||
productList.value.push({
|
||||
name: '消耗性产品:个人赞助1元',
|
||||
id: isDebug() ? "uniappx.consumable.sponsor_1" : "uniappx.consumable.sponsor1",
|
||||
quantity: 1
|
||||
} as PayItem);
|
||||
|
||||
productList.value.push({
|
||||
name: '消耗性产品:金牌赞助5元, 数量=2',
|
||||
id: isDebug() ? "uniappx.consumable.sponsor_50" : "uniappx.consumable.sponsor50",
|
||||
quantity: 2
|
||||
} as PayItem);
|
||||
|
||||
productList.value.push({
|
||||
name: '非消耗性产品: 赞助特效1元',
|
||||
id: isDebug() ? "uniappx.nonconsumable.sponsorskin_1" : "uniappx.nonconsumable.sponsorskin1"
|
||||
} as PayItem);
|
||||
|
||||
// productList.value.push({
|
||||
// name: '自动续期订阅产品:每月定期赞助1元',
|
||||
// id: isDebug() ? "uniappx.autorenewable.monthly_1" : "uniappx.autorenewable.monthly1"
|
||||
// } as PayItem);
|
||||
|
||||
productList.value.push({
|
||||
name: '非自动续期订阅产品:月赞助1元',
|
||||
id: isDebug() ? "uniappx.nonrenewable.monthly_1" : "uniappx.nonrenewable.monthly1"
|
||||
} as PayItem);
|
||||
|
||||
// productList.value.push({
|
||||
// name: '测试不存在的产品',
|
||||
// id: "uniappx.nonrenewable.none"
|
||||
// } as PayItem);
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
virtualPaymentManager.value = uni.getVirtualPaymentManager()
|
||||
initProductList()
|
||||
})
|
||||
|
||||
const requestVirtualPayment = (e: PayItem) => {
|
||||
uni.showLoading({
|
||||
title: "",
|
||||
mask: true
|
||||
});
|
||||
|
||||
uni.requestVirtualPayment({
|
||||
//需要将orderId转换为如下符合UUID规则的字符串,然后赋值给参数appAccountToken, 传入不符合UUID规则的字符串无效
|
||||
apple: {
|
||||
productId: e.id,
|
||||
appAccountToken: "123eaaaa-e89b-12d3-a456-42661417400b",
|
||||
quantity: e.quantity ?? 1,
|
||||
},
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
console.log("购买成功:该productId= " + res.apple?.productId)
|
||||
//TODO: 开发者server验证逻辑
|
||||
|
||||
//经过开发者server验证成功后请结束该交易
|
||||
uni.showToast({
|
||||
title: "购买成功:" + res.apple?.productId,
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
virtualPaymentManager.value.finishTransaction({
|
||||
transaction: res.apple,
|
||||
success: (r) => {
|
||||
console.log("关单成功, 该productId= " + res.apple?.productId)
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log("关单失败, 该productId= " + res.apple?.productId)
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (e) => {
|
||||
uni.hideLoading()
|
||||
console.log("购买失败:errSubject= " + e.errSubject + ", errCode= " + e.errCode + ", errMsg= " + e.errMsg)
|
||||
uni.showToast({
|
||||
title: "购买失败错误码:" + e.errCode,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const restoreTransactions = () => {
|
||||
uni.showLoading({
|
||||
title: "",
|
||||
mask: true
|
||||
});
|
||||
|
||||
virtualPaymentManager.value.restoreTransactions({
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
console.log("restore成功的交易个数:" + res.transactions.length)
|
||||
res.transactions.forEach(transaction => {
|
||||
|
||||
//TODO: 开发者server验证逻辑
|
||||
|
||||
console.log("restore成功的交易productId= " + transaction.productId)
|
||||
})
|
||||
uni.showToast({
|
||||
title: "restore成功的交易个数:" + res.transactions.length,
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: (e) => {
|
||||
uni.hideLoading()
|
||||
console.log("restore失败:errSubject= " + e.errSubject + ", errCode= " + e.errCode + ", errMsg= " + e.errMsg)
|
||||
uni.showToast({
|
||||
title: "restore失败错误码:" + e.errCode,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getUnfinishedTransactions = () => {
|
||||
uni.showLoading({
|
||||
title: "",
|
||||
mask: true
|
||||
});
|
||||
|
||||
virtualPaymentManager.value.getUnfinishedTransactions({
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
console.log("获取未结束的订单列表个数:" + res.transactions.length)
|
||||
uni.showToast({
|
||||
title: "获取未结束的订单列表个数:" + res.transactions.length,
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
res.transactions.forEach(transaction => {
|
||||
|
||||
console.log("getUnfinishedTransactions成功的交易productId= " + transaction.productId)
|
||||
//TODO: 开发者server验证逻辑
|
||||
|
||||
//经过开发者server验证成功后请结束该交易
|
||||
virtualPaymentManager.value.finishTransaction({
|
||||
transaction: transaction,
|
||||
success: (r) => {
|
||||
console.log("关单成功, 该productId= " + transaction.productId)
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log("关单失败, 该productId= " + transaction.productId)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
fail: (e) => {
|
||||
uni.hideLoading()
|
||||
console.log("获取未结束的订单列表失败:errSubject= " + e.errSubject + ", errCode= " + e.errCode + ", errMsg= " + e.errMsg)
|
||||
uni.showToast({
|
||||
title: "获取未结束的订单列表失败错误码" + e.errCode,
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
在新工单中引用
屏蔽一个用户