diff --git a/api/modules/order.js b/api/modules/order.js index 06262c2..f3c1bb2 100644 --- a/api/modules/order.js +++ b/api/modules/order.js @@ -21,19 +21,26 @@ export const orderApi = { }); }, - create(identifier, package_ids) { + create(identifier, package_ids, payment_method) { + const data = { identifier, package_ids }; + if (payment_method === 'alipay') { + data.payment_method = payment_method; + } else { + data.app_type = APP_TYPE; + } + return request({ url: '/api/c/v1/orders/create', method: 'POST', - data: { identifier, package_ids, app_type: APP_TYPE } + data }); }, pay(order_id, payment_method, password) { - const data = { - payment_method, - app_type: APP_TYPE - }; + const data = { payment_method }; + if (payment_method === 'wechat') { + data.app_type = APP_TYPE; + } if (password) { data.password = password; } @@ -43,4 +50,4 @@ export const orderApi = { data }); } -}; \ No newline at end of file +}; diff --git a/api/modules/wallet.js b/api/modules/wallet.js index e03ba51..7e04478 100644 --- a/api/modules/wallet.js +++ b/api/modules/wallet.js @@ -11,10 +11,15 @@ export const walletApi = { }, recharge(identifier, amount, payment_method) { + const data = { identifier, amount, payment_method }; + if (payment_method === 'wechat') { + data.app_type = APP_TYPE; + } + return request({ url: '/api/c/v1/wallet/recharge', method: 'POST', - data: { identifier, amount, payment_method, app_type: APP_TYPE } + data }); }, @@ -59,4 +64,4 @@ export const walletApi = { data: { identifier, page, page_size, ...params } }); } -}; \ No newline at end of file +}; diff --git a/docs/新增支付宝支付.md b/docs/新增支付宝支付.md new file mode 100644 index 0000000..fc2de9a --- /dev/null +++ b/docs/新增支付宝支付.md @@ -0,0 +1,307 @@ +# 支付宝支付链接前端对接说明 + +## 1. 对接范围 + +本次新增的是 **C 端支付宝支付链接能力**。前端无需接入支付宝 SDK,后端会返回已签名的支付宝 WAP 支付链接。 + +覆盖场景: + +1. 套餐订单支付 +2. 钱包充值 +3. 强制充值购包场景 + +微信支付原逻辑保持不变:微信返回 `pay_config`,支付宝返回 `payment_link`。 + +具体接口路径、完整请求字段、响应结构、错误码请以前端接口文档 / OpenAPI 文档为准。 + +## 2. 支付方式取值 + +前端统一使用 `payment_method` 区分支付方式: + +| 值 | 含义 | 前端处理 | +| --- | --- | --- | +| `wallet` | 钱包支付 | 仅订单支付支持,无第三方跳转 | +| `wechat` | 微信支付 | 使用返回的 `pay_config` 调起微信支付 | +| `alipay` | 支付宝支付 | 使用返回的 `payment_link` 打开或展示支付链接 | + +注意事项: + +- `app_type` 只在微信支付时需要。 +- 支付宝支付时不需要传 `app_type`。 +- 支付宝支付返回的是支付链接,不是 JSAPI 参数。 + +## 3. 前端需要关注的响应字段 + +当选择支付宝支付时,接口响应中会返回 `payment_link`: + +```json +{ + "payment_link": { + "payment_no": "支付单号", + "qr_link": "支付宝支付链接,可用于生成二维码", + "copy_link": "支付宝支付链接,可复制或直接打开", + "pay_expire_at": "支付链接过期时间,RFC3339 格式" + } +} +``` + +字段说明: + +| 字段 | 用途 | +| --- | --- | +| `payment_no` | 支付单号,问题排查 / 客服查询使用 | +| `qr_link` | 前端可用该链接生成二维码 | +| `copy_link` | 用户复制或浏览器打开的支付链接,当前与 `qr_link` 一致 | +| `pay_expire_at` | 支付链接过期时间,前端可用于倒计时展示 | + +## 4. 推荐交互流程 + +### 4.1 支付宝支付流程 + +前端拿到 `payment_link` 后: + +1. H5 / 浏览器场景:直接跳转 `copy_link`。 + +支付成功后: + +- 不要只依赖支付宝返回页判断支付成功。 +- 最终支付状态以服务端订单 / 充值记录状态为准。 +- 用户返回页面后,前端应轮询订单详情、订单列表或充值记录接口。 + +## 5. 各业务场景说明 + +### 5.1 套餐订单支付 + +普通套餐下单后,如果需要支付,前端调用订单支付接口,并传: + +```json +{ + "payment_method": "alipay" +} +``` + +支付宝支付响应示例: + +```json +{ + "payment_method": "alipay", + "payment_link": { + "payment_no": "...", + "qr_link": "...", + "copy_link": "...", + "pay_expire_at": "..." + } +} +``` + +前端根据 `payment_link` 发起支付宝支付。 + +### 5.2 钱包充值 + +创建钱包充值单时传: + +```json +{ + "payment_method": "alipay" +} +``` + +支付宝场景返回 `payment_link`;微信场景返回 `pay_config`。 + +### 5.3 强制充值购包 + +创建订单时,如果后端判断需要强制充值,前端可指定: + +```json +{ + "payment_method": "alipay" +} +``` + +如果是支付宝强充,响应中会返回: + +```json +{ + "order_type": "recharge", + "recharge": {}, + "payment_link": {} +} +``` + +前端按支付宝支付链接流程处理。 + +## 6. 前端展示建议 + +支付宝支付页面建议展示: + +- 支付金额 +- 支付宝二维码或“去支付宝支付”按钮 +- 支付链接过期倒计时 +- “我已完成支付”按钮 +- “重新获取支付链接”按钮 + +点击“我已完成支付”时,不直接判定成功,应查询服务端状态。 + +## 7. 注意事项 + +1. 金额单位仍然是 **分**。 +2. 支付宝支付不需要 `app_type`。 +3. `pay_config` 只用于微信支付。 +4. `payment_link` 只用于支付宝支付。 +5. 支付状态以服务端为准,不以前端跳转结果为准。 +6. 支付链接过期后,前端应重新调用对应支付 / 充值接口获取新链接。 +7. 具体接口路径、完整字段、错误码请以前端接口文档 / OpenAPI 文档为准。 + +# 支付宝支付链接前端对接说明 + +## 1. 对接范围 + +本次新增的是 **C 端支付宝支付链接能力**。前端无需接入支付宝 SDK,后端会返回已签名的支付宝 WAP 支付链接。 + +覆盖场景: + +1. 套餐订单支付 +2. 钱包充值 +3. 强制充值购包场景 + +微信支付原逻辑保持不变:微信返回 `pay_config`,支付宝返回 `payment_link`。 + +具体接口路径、完整请求字段、响应结构、错误码请以前端接口文档 / OpenAPI 文档为准。 + +## 2. 支付方式取值 + +前端统一使用 `payment_method` 区分支付方式: + +| 值 | 含义 | 前端处理 | +| --- | --- | --- | +| `wallet` | 钱包支付 | 仅订单支付支持,无第三方跳转 | +| `wechat` | 微信支付 | 使用返回的 `pay_config` 调起微信支付 | +| `alipay` | 支付宝支付 | 使用返回的 `payment_link` 打开或展示支付链接 | + +注意事项: + +- `app_type` 只在微信支付时需要。 +- 支付宝支付时不需要传 `app_type`。 +- 支付宝支付返回的是支付链接,不是 JSAPI 参数。 + +## 3. 前端需要关注的响应字段 + +当选择支付宝支付时,接口响应中会返回 `payment_link`: + +```json +{ + "payment_link": { + "payment_no": "支付单号", + "qr_link": "支付宝支付链接,可用于生成二维码", + "copy_link": "支付宝支付链接,可复制或直接打开", + "pay_expire_at": "支付链接过期时间,RFC3339 格式" + } +} +``` + +字段说明: + +| 字段 | 用途 | +| --- | --- | +| `payment_no` | 支付单号,问题排查 / 客服查询使用 | +| `qr_link` | 前端可用该链接生成二维码 | +| `copy_link` | 用户复制或浏览器打开的支付链接,当前与 `qr_link` 一致 | +| `pay_expire_at` | 支付链接过期时间,前端可用于倒计时展示 | + +## 4. 推荐交互流程 + +### 4.1 支付宝支付流程 + +前端拿到 `payment_link` 后: + +1. H5 / 浏览器场景:直接跳转 `copy_link`。 + +支付成功后: + +- 不要只依赖支付宝返回页判断支付成功。 +- 最终支付状态以服务端订单 / 充值记录状态为准。 +- 用户返回页面后,前端应轮询订单详情、订单列表或充值记录接口。 + +## 5. 各业务场景说明 + +### 5.1 套餐订单支付 + +普通套餐下单后,如果需要支付,前端调用订单支付接口,并传: + +```json +{ + "payment_method": "alipay" +} +``` + +支付宝支付响应示例: + +```json +{ + "payment_method": "alipay", + "payment_link": { + "payment_no": "...", + "qr_link": "...", + "copy_link": "...", + "pay_expire_at": "..." + } +} +``` + +前端根据 `payment_link` 发起支付宝支付。 + +### 5.2 钱包充值 + +创建钱包充值单时传: + +```json +{ + "payment_method": "alipay" +} +``` + +支付宝场景返回 `payment_link`;微信场景返回 `pay_config`。 + +### 5.3 强制充值购包 + +创建订单时,如果后端判断需要强制充值,前端可指定: + +```json +{ + "payment_method": "alipay" +} +``` + +如果是支付宝强充,响应中会返回: + +```json +{ + "order_type": "recharge", + "recharge": {}, + "payment_link": {} +} +``` + +前端按支付宝支付链接流程处理。 + +## 6. 前端展示建议 + +支付宝支付页面建议展示: + +- 支付金额 +- 支付宝二维码或“去支付宝支付”按钮 +- 支付链接过期倒计时 +- “我已完成支付”按钮 +- “重新获取支付链接”按钮 + +点击“我已完成支付”时,不直接判定成功,应查询服务端状态。 + +## 7. 注意事项 + +1. 金额单位仍然是 **分**。 +2. 支付宝支付不需要 `app_type`。 +3. `pay_config` 只用于微信支付。 +4. `payment_link` 只用于支付宝支付。 +5. 支付状态以服务端为准,不以前端跳转结果为准。 +6. 支付链接过期后,前端应重新调用对应支付 / 充值接口获取新链接。 +7. 具体接口路径、完整字段、错误码请以前端接口文档 / OpenAPI 文档为准。 \ No newline at end of file diff --git a/openspec/changes/add-alipay-payment-link-flow/proposal.md b/openspec/changes/add-alipay-payment-link-flow/proposal.md new file mode 100644 index 0000000..b60c5b9 --- /dev/null +++ b/openspec/changes/add-alipay-payment-link-flow/proposal.md @@ -0,0 +1,25 @@ +## Why + +The front end currently supports WeChat payment and wallet balance payment, but it does not support the new backend Alipay WAP payment-link flow. This prevents users from completing package purchase and wallet recharge scenarios when Alipay is the selected method. + +## What Changes + +- Add Alipay as a supported payment method in package purchase, order list immediate payment, and wallet recharge flows +- Handle backend `payment_link` responses for Alipay while preserving existing WeChat `pay_config` handling +- Keep wallet balance payment behavior unchanged for package purchase +- Refresh payment-related page state from backend status after the user returns from the Alipay payment link flow +- Preserve current order creation, strong recharge, validation, toast, and list refresh behavior except where Alipay-specific branching is required + +## Capabilities + +### New Capabilities +- `alipay-payment-link`: Support backend-provided Alipay payment links across package, order, and wallet payment entry points + +### Modified Capabilities + +## Impact + +- Affected code: `pages/package-order/package-order.vue`, `pages/order-list/order-list.vue`, `pages/my-wallet/my-wallet.vue`, `utils/payment.js`, `api/modules/wallet.js` +- Affected UI flows: package payment method popup, order list immediate payment, wallet recharge popup, wallet recharge order immediate payment +- No backend API path changes expected +- Existing WeChat and wallet payment behavior must remain available diff --git a/openspec/changes/add-alipay-payment-link-flow/specs/alipay-payment-link/spec.md b/openspec/changes/add-alipay-payment-link-flow/specs/alipay-payment-link/spec.md new file mode 100644 index 0000000..37bd18e --- /dev/null +++ b/openspec/changes/add-alipay-payment-link-flow/specs/alipay-payment-link/spec.md @@ -0,0 +1,58 @@ +## ADDED Requirements + +### Requirement: Package payment SHALL support Alipay payment links +The system SHALL allow users to choose Alipay during package purchase and SHALL handle backend Alipay responses through `payment_link` while preserving the existing WeChat and wallet payment branches. + +#### Scenario: User pays a package order with Alipay +- **WHEN** the user selects `alipay` in the package payment popup and the backend returns `payment_method: "alipay"` with `payment_link` +- **THEN** the system SHALL open or present the returned Alipay payment link for the user +- **AND** the system SHALL NOT require WeChat `pay_config` fields for this branch + +#### Scenario: User pays a package order with wallet balance +- **WHEN** the user selects `wallet` and the backend completes the package payment without a third-party redirect +- **THEN** the system SHALL preserve the existing wallet payment success flow +- **AND** the system SHALL refresh relevant wallet-related page state after success + +#### Scenario: Strong recharge branch returns an Alipay payment link +- **WHEN** package order creation enters the strong recharge branch and the backend returns `order_type: "recharge"` with `payment_link` +- **THEN** the system SHALL use the Alipay payment-link flow for that recharge branch +- **AND** the system SHALL preserve the existing strong recharge messaging and follow-up refresh behavior + +### Requirement: Pending order payment SHALL support Alipay payment links +The system SHALL allow users to continue payment for pending package orders from the order list through an Alipay payment link flow. + +#### Scenario: User continues a pending order with Alipay +- **WHEN** the user starts payment for a pending order from the order list and the backend returns `payment_method: "alipay"` with `payment_link` +- **THEN** the system SHALL open or present the returned payment link +- **AND** the system SHALL refresh the order list from backend status after the user completes or retries the flow + +### Requirement: Wallet recharge flows SHALL support Alipay payment links +The system SHALL support Alipay both when creating a new wallet recharge order and when retrying payment for a pending recharge order. + +#### Scenario: User creates a new wallet recharge with Alipay +- **WHEN** the user confirms a wallet recharge with `payment_method: "alipay"` and the backend returns `payment_link` +- **THEN** the system SHALL open or present the returned payment link +- **AND** the system SHALL preserve the existing recharge validation rules and amount checks + +#### Scenario: User retries a pending recharge order with Alipay +- **WHEN** the user continues payment for a pending recharge order and the backend returns `payment_link` +- **THEN** the system SHALL open or present the returned Alipay payment link +- **AND** the system SHALL refresh wallet detail and recharge list state from backend data after the user completes or retries the flow + +### Requirement: Payment method branching SHALL distinguish WeChat and Alipay contracts +The system SHALL treat WeChat and Alipay payment preparation responses as different contracts and SHALL determine final payment success from backend order or recharge status instead of the redirect result alone. + +#### Scenario: WeChat payment returns pay_config +- **WHEN** the backend returns `payment_method: "wechat"` with `pay_config` +- **THEN** the system SHALL continue to use the existing WeChat H5 payment invocation flow +- **AND** the system SHALL preserve the current `app_type` behavior for WeChat requests + +#### Scenario: Alipay payment returns payment_link +- **WHEN** the backend returns `payment_method: "alipay"` with `payment_link` +- **THEN** the system SHALL use the returned link fields instead of expecting `pay_config` +- **AND** the system SHALL NOT depend on `app_type` for the Alipay branch + +#### Scenario: User returns after Alipay jump +- **WHEN** the user comes back to the application after opening the Alipay payment link +- **THEN** the system SHALL refresh the relevant order or recharge data from backend APIs +- **AND** the system SHALL determine whether the payment succeeded from refreshed backend status diff --git a/openspec/changes/add-alipay-payment-link-flow/tasks.md b/openspec/changes/add-alipay-payment-link-flow/tasks.md new file mode 100644 index 0000000..d38dda1 --- /dev/null +++ b/openspec/changes/add-alipay-payment-link-flow/tasks.md @@ -0,0 +1,29 @@ +## 1. Shared Payment Branching + +- [ ] 1.1 Add shared front-end handling for Alipay `payment_link` responses alongside the existing WeChat `pay_config` flow +- [ ] 1.2 Ensure Alipay requests do not depend on `app_type` and existing WeChat requests continue to work unchanged + +## 2. Package Purchase Flow + +- [ ] 2.1 Add Alipay as a selectable payment method in `pages/package-order/package-order.vue` +- [ ] 2.2 Handle normal package order payment responses for `wechat`, `wallet`, and `alipay` +- [ ] 2.3 Handle strong recharge branches that return `payment_link` for Alipay + +## 3. Order List Flow + +- [ ] 3.1 Add an order-list payment entry that lets the user continue pending orders with Alipay +- [ ] 3.2 Refresh order list state from backend data after the user completes or retries the Alipay flow + +## 4. Wallet Flow + +- [ ] 4.1 Add Alipay as a selectable payment method in the wallet recharge popup +- [ ] 4.2 Handle Alipay recharge creation responses in `confirmRecharge` +- [ ] 4.3 Handle Alipay payment retries for pending recharge orders in `handleRechargePayment` +- [ ] 4.4 Refresh wallet detail and recharge list state from backend data after the Alipay flow + +## 5. Regression Verification + +- [ ] 5.1 Verify WeChat package payment still uses `pay_config` +- [ ] 5.2 Verify wallet-balance package payment remains unchanged +- [ ] 5.3 Verify Alipay package payment, order-list payment, wallet recharge, and recharge retry can all open a valid payment link flow +- [ ] 5.4 Verify payment success is determined by refreshed backend status instead of the link jump result diff --git a/pages/index/index.vue b/pages/index/index.vue index edbcdeb..b48f677 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -216,6 +216,21 @@ return dateStr.split('T')[0] || '-'; }; + const resolveAssetRealName = (assetData) => { + const cards = Array.isArray(assetData?.cards) ? assetData.cards : []; + + if (cards.length === 1) { + return !!cards[0]?.real_name_at; + } + + if (cards.length > 1) { + const currentCard = cards.find(card => card.is_current); + return !!currentCard?.real_name_at; + } + + return assetData?.real_name_status === 1; + }; + const loadAssetInfo = async () => { const identifier = userStore.state.identifier; if (!identifier) return; @@ -236,8 +251,8 @@ deviceInfo.expireDate = formatDate(data.current_package_expires_at); deviceInfo.iccid = data.iccid || '-'; deviceInfo.walletBalance = data.wallet_balance ?? 0; - isRealName.value = data.real_name_status === 1; - realNameStatus.value = data.real_name_status === 1 ? '已实名' : '未实名'; + isRealName.value = resolveAssetRealName(data); + realNameStatus.value = isRealName.value ? '已实名' : '未实名'; boundPhone.value = data.bound_phone || ''; alreadyBindPhone.value = !!data.bound_phone; diff --git a/pages/my-wallet/my-wallet.vue b/pages/my-wallet/my-wallet.vue index 702c73e..44fd1ce 100644 --- a/pages/my-wallet/my-wallet.vue +++ b/pages/my-wallet/my-wallet.vue @@ -1,151 +1,179 @@ @@ -638,6 +689,7 @@ border-radius: var(--radius-large); padding: 40rpx; color: #fff; + .wallet-header { display: flex; align-items: flex-start; @@ -668,7 +720,7 @@ border: 2rpx solid rgba(255, 255, 255, 0.3); color: #fff; font-size: 28rpx; - padding: 0rpx 32rpx; + padding: 0 32rpx; border-radius: 50rpx; font-weight: 600; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); @@ -777,12 +829,7 @@ .record-header { margin-bottom: 20rpx; - .record-no { - font-size: 28rpx; - font-weight: 600; - color: var(--text-primary); - } - + .record-no, .record-type { font-size: 28rpx; font-weight: 600; @@ -815,9 +862,9 @@ } } - .record-actions { - margin-top: 20rpx; - padding-top: 20rpx; + .record-actions { + margin-top: 20rpx; + padding-top: 20rpx; border-top: 1rpx solid var(--border-light); display: flex; justify-content: flex-end; @@ -828,16 +875,16 @@ border: none; border-radius: 8rpx; padding: 12rpx 32rpx; - font-size: 26rpx; - font-weight: 500; - width:100%; + font-size: 26rpx; + font-weight: 500; + width: 100%; - &[disabled] { - opacity: 0.7; - } + &[disabled] { + opacity: 0.7; + } - &::after { - border: none; + &::after { + border: none; } } } @@ -851,9 +898,10 @@ padding: 120rpx 40rpx; .empty-icon { - font-size: 100rpx; + font-size: 56rpx; margin-bottom: 24rpx; opacity: 0.5; + letter-spacing: 4rpx; } .empty-title { @@ -946,6 +994,88 @@ } } + .payment-methods { + padding: 0 30rpx 30rpx; + + .method-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24rpx; + margin-bottom: 20rpx; + border: 2rpx solid var(--border-light); + border-radius: var(--radius-medium); + cursor: pointer; + transition: all 0.3s; + + &:last-child { + margin-bottom: 0; + } + + &.active { + border-color: var(--primary); + background: rgba(0, 122, 255, 0.05); + } + + .method-left { + display: flex; + align-items: center; + gap: 20rpx; + + .method-icon { + width: 64rpx; + height: 64rpx; + } + + .method-badge { + display: flex; + align-items: center; + justify-content: center; + border-radius: 18rpx; + font-size: 32rpx; + font-weight: 700; + color: #fff; + } + + .method-badge-alipay { + background: linear-gradient(135deg, #1677ff 0%, #45a5ff 100%); + box-shadow: 0 8rpx 20rpx rgba(22, 119, 255, 0.2); + } + + .method-name { + font-size: 28rpx; + font-weight: 500; + color: var(--text-primary); + } + } + + .method-radio { + width: 36rpx; + height: 36rpx; + border: 2rpx solid var(--gray-400); + border-radius: 50%; + position: relative; + transition: all 0.3s; + + &.checked { + border-color: var(--primary); + background: var(--primary); + + &::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #fff; + font-size: 20rpx; + font-weight: 700; + } + } + } + } + } + .popup-footer { display: flex; gap: 20rpx; diff --git a/pages/order-list/order-list.vue b/pages/order-list/order-list.vue index 427e0f1..b40184d 100644 --- a/pages/order-list/order-list.vue +++ b/pages/order-list/order-list.vue @@ -1,73 +1,66 @@ @@ -323,20 +380,45 @@ justify-content: center; padding: 120rpx 40rpx; min-height: 400rpx; - .empty-icon { font-size: 120rpx; margin-bottom: 30rpx; opacity: 0.6; } - .empty-title { font-size: 32rpx; font-weight: 600; color: var(--text-primary); margin-bottom: 16rpx; } - .empty-desc { font-size: 26rpx; color: var(--text-tertiary); text-align: center; } + + .empty-icon { + font-size: 60rpx; + margin-bottom: 30rpx; + opacity: 0.6; + letter-spacing: 4rpx; + } + + .empty-title { + font-size: 32rpx; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 16rpx; + } + + .empty-desc { + font-size: 26rpx; + color: var(--text-tertiary); + text-align: center; + } } .package-card { margin-bottom: var(--space-md); + .package-header { margin-bottom: var(--space-sm); - .package-name { font-size: 32rpx; font-weight: 600; color: var(--text-primary); } + + .package-name { + font-size: 32rpx; + font-weight: 600; + color: var(--text-primary); + } + .header-right { display: flex; align-items: center; gap: 12rpx; + .validity-tag { font-size: 24rpx; color: var(--text-secondary); @@ -346,6 +428,7 @@ } } } + .package-data { font-size: 48rpx; font-weight: 700; @@ -353,18 +436,22 @@ margin-bottom: var(--space-sm); letter-spacing: -1rpx; } + .package-desc { font-size: 26rpx; color: var(--text-secondary); margin-bottom: var(--space-md); } + .package-footer { align-items: center; + .package-price { font-size: 40rpx; font-weight: 700; color: var(--warning); } + .btn { flex-shrink: 0; } @@ -492,6 +579,21 @@ height: 64rpx; } + .method-badge { + display: flex; + align-items: center; + justify-content: center; + border-radius: 18rpx; + font-size: 32rpx; + font-weight: 700; + color: #fff; + } + + .method-badge-alipay { + background: linear-gradient(135deg, #1677ff 0%, #45a5ff 100%); + box-shadow: 0 8rpx 20rpx rgba(22, 119, 255, 0.2); + } + .method-name { font-size: 28rpx; font-weight: 500; @@ -532,24 +634,24 @@ padding: 30rpx; border-top: 1rpx solid var(--border-light); - .btn-apple { - flex: 1; - height: 88rpx; - display: flex; + .btn-apple { + flex: 1; + height: 88rpx; + display: flex; align-items: center; justify-content: center; border-radius: var(--radius-medium); - font-size: 28rpx; - font-weight: 500; - border: none; + font-size: 28rpx; + font-weight: 500; + border: none; - &[disabled] { - opacity: 0.7; - } + &[disabled] { + opacity: 0.7; + } - &.btn-secondary { - background: var(--gray-200); - color: var(--text-primary); + &.btn-secondary { + background: var(--gray-200); + color: var(--text-primary); } &.btn-primary { diff --git a/utils/payment.js b/utils/payment.js index 3bec550..16a7dfc 100644 --- a/utils/payment.js +++ b/utils/payment.js @@ -1,40 +1,81 @@ -/** - * 微信H5支付工具 - * 统一处理微信JSAPI支付逻辑 - */ +const ALIPAY_PENDING_REFRESH_KEY = 'pending_alipay_payment_refresh'; + +export const PAYMENT_REFRESH_TARGETS = { + PACKAGE_ORDER: 'package-order', + ORDER_LIST: 'order-list', + MY_WALLET: 'my-wallet' +}; + +export function isValidWechatPayConfig(payConfig) { + return !!payConfig && + typeof payConfig.package === 'string' && + payConfig.package.startsWith('prepay_id='); +} + +export function getAlipayPaymentUrl(paymentLink) { + if (!paymentLink) return ''; + return paymentLink.copy_link || paymentLink.qr_link || ''; +} + +export function isValidAlipayPaymentLink(paymentLink) { + return !!getAlipayPaymentUrl(paymentLink); +} + +export function markPendingPaymentRefresh(target) { + if (!target) return; + uni.setStorageSync(ALIPAY_PENDING_REFRESH_KEY, { + target, + timestamp: Date.now() + }); +} + +export function consumePendingPaymentRefresh(target) { + const pending = uni.getStorageSync(ALIPAY_PENDING_REFRESH_KEY); + if (!pending || pending.target !== target) { + return false; + } + + uni.removeStorageSync(ALIPAY_PENDING_REFRESH_KEY); + return true; +} + +export function openAlipayPayment(paymentLink, refreshTarget) { + return new Promise((resolve, reject) => { + const paymentUrl = getAlipayPaymentUrl(paymentLink); + + if (!paymentUrl) { + reject(new Error('Invalid payment link')); + return; + } + + markPendingPaymentRefresh(refreshTarget); + + if (typeof window !== 'undefined' && window.location) { + window.location.href = paymentUrl; + resolve({ redirecting: true }); + return; + } + + uni.setClipboardData({ + data: paymentUrl, + success: () => resolve({ copied: true }), + fail: () => reject(new Error('Unable to open payment link in this environment')) + }); + }); +} -/** - * 调起微信H5支付 - * @param {Object} payConfig - 支付配置对象 - * @param {string} payConfig.app_id - 微信公众号appid - * @param {string} payConfig.timestamp - 时间戳 - * @param {string} payConfig.nonce_str - 随机字符串 - * @param {string} payConfig.package - 预支付交易会话标识 - * @param {string} payConfig.sign_type - 签名方式 - * @param {string} payConfig.pay_sign - 签名 - * @returns {Promise} 支付结果 - */ export function wechatH5Pay(payConfig) { return new Promise((resolve, reject) => { - // 验证支付配置 - if (!payConfig || !payConfig.package) { - reject(new Error('支付参数无效')); + if (!isValidWechatPayConfig(payConfig)) { + reject(new Error('Invalid payment params')); return; } - // 验证 package 格式 - if (!payConfig.package.startsWith('prepay_id=')) { - reject(new Error('支付参数无效')); - return; - } - - // 检查是否在微信环境中 if (typeof WeixinJSBridge === 'undefined') { - reject(new Error('请在微信中打开')); + reject(new Error('Please open in WeChat')); return; } - // 调起微信支付 WeixinJSBridge.invoke('getBrandWCPayRequest', { appId: payConfig.app_id, timeStamp: payConfig.timestamp, @@ -44,22 +85,19 @@ export function wechatH5Pay(payConfig) { paySign: payConfig.pay_sign }, function(res) { if (res.err_msg === 'get_brand_wcpay_request:ok') { - // 支付成功 resolve({ success: true, - message: '支付成功' + message: 'Payment successful' }); } else if (res.err_msg === 'get_brand_wcpay_request:cancel') { - // 用户取消支付 reject({ cancelled: true, - message: '已取消支付' + message: 'Payment cancelled' }); } else { - // 支付失败 reject({ success: false, - message: '支付失败', + message: 'Payment failed', error: res }); } @@ -67,30 +105,19 @@ export function wechatH5Pay(payConfig) { }); } -/** - * 显示支付结果提示 - * @param {boolean} success - 是否成功 - * @param {string} message - 提示消息 - */ export function showPaymentToast(success, message) { uni.showToast({ - title: message || (success ? '支付成功' : '支付失败'), + title: message || (success ? 'Payment successful' : 'Payment failed'), icon: success ? 'success' : 'none', duration: 2000 }); } -/** - * 处理支付错误 - * @param {Error|Object} error - 错误对象 - */ export function handlePaymentError(error) { if (error.cancelled) { - // 用户主动取消,提示取消 - showPaymentToast(false, '已取消支付'); - } else { - // 其他错误 - const message = error.message || '支付失败'; - showPaymentToast(false, message); + showPaymentToast(false, 'Payment cancelled'); + return; } + + showPaymentToast(false, error.message || 'Payment failed'); }