This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
307
docs/新增支付宝支付.md
Normal file
307
docs/新增支付宝支付.md
Normal file
@@ -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 文档为准。
|
||||
25
openspec/changes/add-alipay-payment-link-flow/proposal.md
Normal file
25
openspec/changes/add-alipay-payment-link-flow/proposal.md
Normal file
@@ -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
|
||||
@@ -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
|
||||
29
openspec/changes/add-alipay-payment-link-flow/tasks.md
Normal file
29
openspec/changes/add-alipay-payment-link-flow/tasks.md
Normal file
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,27 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="filter-tabs">
|
||||
<view v-for="(item, index) in filterOptions" :key="index" class="tab-item"
|
||||
:class="{ active: filterIndex === index }" @tap="onFilterChange(index)">
|
||||
<view
|
||||
v-for="(item, index) in filterOptions"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: filterIndex === index }"
|
||||
@tap="onFilterChange(index)"
|
||||
>
|
||||
{{ item.label }}
|
||||
<view v-if="filterIndex === index" class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="scroll-container" @scrolltolower="loadMore">
|
||||
|
||||
<view v-if="orderList.length === 0 && !loading" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<view class="empty-icon">ORD</view>
|
||||
<view class="empty-title">暂无订单</view>
|
||||
<view class="empty-desc">当前账号下暂无订单信息</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="order-list">
|
||||
<view class="order-card" v-for="(item, index) in orderList" :key="index">
|
||||
<view class="order-card" v-for="item in orderList" :key="item.order_id">
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<view class="header-row">
|
||||
@@ -35,7 +39,7 @@
|
||||
<view class="info-item">
|
||||
<view class="info-label">套餐名称</view>
|
||||
<view class="info-value">
|
||||
<view class="package-item" v-for="(pkgName, pIndex) in item.package_names" :key="pIndex">
|
||||
<view class="package-item" v-for="(pkgName, pkgIndex) in item.package_names" :key="pkgIndex">
|
||||
<text class="package-name">{{ pkgName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -50,19 +54,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="divider"></view>
|
||||
|
||||
<view class="package-section">
|
||||
<view class="section-label">套餐名称</view>
|
||||
<view class="package-list">
|
||||
<view class="package-item" v-for="(pkgName, pIndex) in item.package_names" :key="pIndex">
|
||||
<text class="package-name">{{ pkgName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view v-if="item.payment_status === 1" class="card-footer">
|
||||
<button class="btn-pay" :disabled="orderPayingId !== null" @tap="handleOrderPayment(item)">
|
||||
<button class="btn-pay" :disabled="orderPayingId !== null" @tap="showOrderPaymentMethods(item)">
|
||||
{{ isOrderPaying(item) ? '处理中...' : '立即支付' }}
|
||||
</button>
|
||||
</view>
|
||||
@@ -80,48 +73,40 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { orderApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import {
|
||||
orderApi
|
||||
} from '@/api/index.js';
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/store/index.js';
|
||||
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||
consumePendingPaymentRefresh,
|
||||
handlePaymentError,
|
||||
isValidAlipayPaymentLink,
|
||||
isValidWechatPayConfig,
|
||||
openAlipayPayment,
|
||||
PAYMENT_REFRESH_TARGETS,
|
||||
showPaymentToast,
|
||||
wechatH5Pay
|
||||
} from '@/utils/payment.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
let orderList = reactive([]);
|
||||
let loading = ref(false);
|
||||
let noMore = ref(false);
|
||||
let page = ref(1);
|
||||
let orderPayingId = ref(null);
|
||||
const orderList = reactive([]);
|
||||
const loading = ref(false);
|
||||
const noMore = ref(false);
|
||||
const page = ref(1);
|
||||
const orderPayingId = ref(null);
|
||||
const pageSize = 10;
|
||||
let filterIndex = ref(0);
|
||||
let filterOptions = [{
|
||||
label: '全部',
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: '待支付',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '已支付',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '已取消',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '已退款',
|
||||
value: 4
|
||||
}
|
||||
const filterIndex = ref(0);
|
||||
const paymentMethodOptions = [
|
||||
{ label: '微信支付', value: 'wechat' },
|
||||
{ label: '支付宝支付', value: 'alipay' }
|
||||
];
|
||||
const filterOptions = [
|
||||
{ label: '全部', value: null },
|
||||
{ label: '待支付', value: 1 },
|
||||
{ label: '已支付', value: 2 },
|
||||
{ label: '已取消', value: 3 },
|
||||
{ label: '已退款', value: 4 }
|
||||
];
|
||||
|
||||
const formatMoney = (amount) => {
|
||||
@@ -139,17 +124,35 @@
|
||||
return classMap[status] || '';
|
||||
};
|
||||
|
||||
const onFilterChange = (index) => {
|
||||
filterIndex.value = index;
|
||||
const resetOrderListAndLoad = () => {
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
loadOrderList();
|
||||
};
|
||||
|
||||
const loadOrderList = async (append = false) => {
|
||||
if (loading.value || noMore.value) return;
|
||||
loading.value = true;
|
||||
const scheduleOrderStatusRefresh = () => {
|
||||
uni.showToast({
|
||||
title: '正在同步支付状态',
|
||||
icon: 'none'
|
||||
});
|
||||
resetOrderListAndLoad();
|
||||
setTimeout(() => {
|
||||
resetOrderListAndLoad();
|
||||
}, 1500);
|
||||
setTimeout(() => {
|
||||
resetOrderListAndLoad();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const onFilterChange = (index) => {
|
||||
filterIndex.value = index;
|
||||
resetOrderListAndLoad();
|
||||
};
|
||||
|
||||
const loadOrderList = async (append = false) => {
|
||||
if (loading.value || (append && noMore.value)) return;
|
||||
|
||||
loading.value = true;
|
||||
const paymentStatus = filterOptions[filterIndex.value].value;
|
||||
|
||||
try {
|
||||
@@ -160,11 +163,17 @@
|
||||
paymentStatus
|
||||
);
|
||||
|
||||
const newData = (data.items || []).map(item => ({
|
||||
const newData = (data.items || []).map((item) => ({
|
||||
...item,
|
||||
payment_status_name: item.payment_status === 1 ? '待支付' : item.payment_status === 2 ?
|
||||
'已支付' : item.payment_status === 3 ? '已取消' : item.payment_status === 4 ? '已退款' :
|
||||
'未知',
|
||||
payment_status_name: item.payment_status === 1
|
||||
? '待支付'
|
||||
: item.payment_status === 2
|
||||
? '已支付'
|
||||
: item.payment_status === 3
|
||||
? '已取消'
|
||||
: item.payment_status === 4
|
||||
? '已退款'
|
||||
: '未知',
|
||||
created_at: item.created_at ? item.created_at.split('T').join(' ').slice(0, 19) : ''
|
||||
}));
|
||||
|
||||
@@ -177,11 +186,12 @@
|
||||
if (newData.length < pageSize) {
|
||||
noMore.value = true;
|
||||
} else {
|
||||
page.value++;
|
||||
page.value += 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载订单列表失败', e);
|
||||
} catch (error) {
|
||||
console.error('加载订单列表失败', error);
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
@@ -193,21 +203,35 @@
|
||||
|
||||
const isOrderPaying = (order) => orderPayingId.value === order.order_id;
|
||||
|
||||
const handleOrderPayment = async (order) => {
|
||||
const showOrderPaymentMethods = (order) => {
|
||||
if (orderPayingId.value !== null || !order?.order_id) return;
|
||||
orderPayingId.value = order.order_id;
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: paymentMethodOptions.map((item) => item.label),
|
||||
success: ({ tapIndex }) => {
|
||||
const selectedMethod = paymentMethodOptions[tapIndex]?.value;
|
||||
if (selectedMethod) {
|
||||
handleOrderPayment(order, selectedMethod);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleOrderPayment = async (order, paymentMethod) => {
|
||||
if (orderPayingId.value !== null || !order?.order_id) return;
|
||||
|
||||
orderPayingId.value = order.order_id;
|
||||
uni.showLoading({
|
||||
title: '处理中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
try {
|
||||
const payData = await orderApi.pay(order.order_id, 'wechat');
|
||||
|
||||
const payData = await orderApi.pay(order.order_id, paymentMethod);
|
||||
uni.hideLoading();
|
||||
|
||||
if (!payData.pay_config || !payData.pay_config.package) {
|
||||
if (paymentMethod === 'wechat') {
|
||||
if (!isValidWechatPayConfig(payData?.pay_config)) {
|
||||
uni.showToast({
|
||||
title: '支付参数获取失败',
|
||||
icon: 'none'
|
||||
@@ -215,27 +239,32 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 调起微信支付
|
||||
try {
|
||||
await wechatH5Pay(payData.pay_config);
|
||||
// 支付成功
|
||||
showPaymentToast(true, '支付成功');
|
||||
setTimeout(() => {
|
||||
page.value = 1;
|
||||
noMore.value = false;
|
||||
loadOrderList();
|
||||
resetOrderListAndLoad();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
// 支付失败或取消
|
||||
handlePaymentError(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
console.error('支付失败', e);
|
||||
const errorMsg = e.msg || e.message || '支付失败,请稍后重试';
|
||||
if (!isValidAlipayPaymentLink(payData?.payment_link)) {
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
title: '支付链接获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await openAlipayPayment(payData.payment_link, PAYMENT_REFRESH_TARGETS.ORDER_LIST);
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('订单支付失败', error);
|
||||
uni.showToast({
|
||||
title: error.msg || error.message || '支付失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
@@ -243,6 +272,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
onShow(() => {
|
||||
if (!consumePendingPaymentRefresh(PAYMENT_REFRESH_TARGETS.ORDER_LIST)) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleOrderStatusRefresh();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadOrderList();
|
||||
});
|
||||
@@ -315,9 +352,10 @@
|
||||
padding: 200rpx 40rpx;
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
font-size: 56rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
@@ -374,12 +412,6 @@
|
||||
color: #333;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.order-id {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
@@ -410,33 +442,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background: #f5f5f5;
|
||||
margin: 24rpx 0;
|
||||
}
|
||||
|
||||
.package-section {
|
||||
.section-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.package-list {
|
||||
.package-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
margin-top: 24rpx;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view v-if="packageList.length === 0 && !loading" class="empty-state">
|
||||
<view class="empty-icon">📦</view>
|
||||
<view class="empty-title">暂无可购套餐,请联系客服</view>
|
||||
<view class="empty-icon">PKG</view>
|
||||
<view class="empty-title">暂无可购套餐,请联系客服</view>
|
||||
<view class="empty-desc">当前资产暂无适用的套餐</view>
|
||||
</view>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付方式选择弹窗 -->
|
||||
<up-popup :show="showModal" mode="center" @close="showModal = false">
|
||||
<view class="payment-popup">
|
||||
<view class="popup-header">
|
||||
@@ -59,6 +58,14 @@
|
||||
<view class="method-radio" :class="{ checked: paymentMethod === 'wechat' }"></view>
|
||||
</view>
|
||||
|
||||
<view class="method-item" :class="{ active: paymentMethod === 'alipay' }" @tap="selectPaymentMethod('alipay')">
|
||||
<view class="method-left">
|
||||
<view class="method-icon method-badge method-badge-alipay">支</view>
|
||||
<text class="method-name">支付宝支付</text>
|
||||
</view>
|
||||
<view class="method-radio" :class="{ checked: paymentMethod === 'alipay' }"></view>
|
||||
</view>
|
||||
|
||||
<view class="method-item" :class="{ active: paymentMethod === 'wallet' }" @tap="selectPaymentMethod('wallet')">
|
||||
<view class="method-left">
|
||||
<image class="method-icon" src="/static/wallet.png" mode="aspectFit"></image>
|
||||
@@ -81,19 +88,29 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { assetApi, orderApi, walletApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import { wechatH5Pay, handlePaymentError, showPaymentToast } from '@/utils/payment.js';
|
||||
import {
|
||||
consumePendingPaymentRefresh,
|
||||
handlePaymentError,
|
||||
isValidAlipayPaymentLink,
|
||||
isValidWechatPayConfig,
|
||||
openAlipayPayment,
|
||||
PAYMENT_REFRESH_TARGETS,
|
||||
showPaymentToast,
|
||||
wechatH5Pay
|
||||
} from '@/utils/payment.js';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
let showModal = ref(false);
|
||||
let currentPackage = ref(null);
|
||||
let packageList = reactive([]);
|
||||
let loading = ref(false);
|
||||
let paymentMethod = ref('wechat');
|
||||
let walletBalance = ref(0);
|
||||
let paySubmitting = ref(false);
|
||||
const showModal = ref(false);
|
||||
const currentPackage = ref(null);
|
||||
const packageList = reactive([]);
|
||||
const loading = ref(false);
|
||||
const paymentMethod = ref('wechat');
|
||||
const walletBalance = ref(0);
|
||||
const paySubmitting = ref(false);
|
||||
|
||||
const formatMoney = (amount) => {
|
||||
if (!amount && amount !== 0) return '0.00';
|
||||
@@ -102,9 +119,9 @@
|
||||
|
||||
const formatData = (allowance, unit) => {
|
||||
if (unit === 'MB') {
|
||||
return allowance >= 1024 ? (allowance / 1024).toFixed(0) + ' GB' : allowance + ' MB';
|
||||
return allowance >= 1024 ? `${(allowance / 1024).toFixed(0)} GB` : `${allowance} MB`;
|
||||
}
|
||||
return allowance + ' ' + unit;
|
||||
return `${allowance} ${unit}`;
|
||||
};
|
||||
|
||||
const loadPackages = async () => {
|
||||
@@ -112,8 +129,8 @@
|
||||
try {
|
||||
const data = await assetApi.getPackages(userStore.state.identifier);
|
||||
packageList.splice(0, packageList.length, ...(data.packages || []));
|
||||
} catch (e) {
|
||||
console.error('加载套餐列表失败', e);
|
||||
} catch (error) {
|
||||
console.error('加载套餐列表失败', error);
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
@@ -122,11 +139,16 @@
|
||||
try {
|
||||
const data = await walletApi.getDetail(userStore.state.identifier);
|
||||
walletBalance.value = data.balance || 0;
|
||||
} catch (e) {
|
||||
console.error('加载钱包余额失败', e);
|
||||
} catch (error) {
|
||||
console.error('加载钱包余额失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const syncPackagePageState = () => {
|
||||
loadPackages();
|
||||
loadWalletBalance();
|
||||
};
|
||||
|
||||
const buyPackage = (item) => {
|
||||
currentPackage.value = item;
|
||||
paymentMethod.value = 'wechat';
|
||||
@@ -137,27 +159,66 @@
|
||||
paymentMethod.value = method;
|
||||
};
|
||||
|
||||
const hasPreparedPaymentData = (paymentData) => {
|
||||
return isValidWechatPayConfig(paymentData?.pay_config) ||
|
||||
isValidAlipayPaymentLink(paymentData?.payment_link);
|
||||
};
|
||||
|
||||
const handleWechatPay = async (payConfig, isForceRecharge = false) => {
|
||||
try {
|
||||
await wechatH5Pay(payConfig);
|
||||
showPaymentToast(true, isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功');
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
handlePaymentError(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreparedPayment = async (paymentData, isForceRecharge = false) => {
|
||||
if (isValidWechatPayConfig(paymentData?.pay_config)) {
|
||||
await handleWechatPay(paymentData.pay_config, isForceRecharge);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isValidAlipayPaymentLink(paymentData?.payment_link)) {
|
||||
await openAlipayPayment(paymentData.payment_link, PAYMENT_REFRESH_TARGETS.PACKAGE_ORDER);
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: '支付参数获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const getCreateOrderPaymentMethod = () => {
|
||||
return paymentMethod.value === 'alipay' ? 'alipay' : undefined;
|
||||
};
|
||||
|
||||
const confirmPay = async () => {
|
||||
if (paySubmitting.value || !currentPackage.value) return;
|
||||
|
||||
paySubmitting.value = true;
|
||||
let releaseInModalCallback = false;
|
||||
|
||||
uni.showLoading({ title: '创建订单...', mask: true });
|
||||
uni.showLoading({
|
||||
title: '创建订单...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
try {
|
||||
// 第一步:创建订单
|
||||
const orderResult = await orderApi.create(
|
||||
userStore.state.identifier,
|
||||
[currentPackage.value.package_id]
|
||||
[currentPackage.value.package_id],
|
||||
getCreateOrderPaymentMethod()
|
||||
);
|
||||
|
||||
// 判断是否为强充订单
|
||||
if (orderResult.order_type === 'recharge' && orderResult.recharge) {
|
||||
const recharge = orderResult.recharge;
|
||||
|
||||
// 检查充值订单状态
|
||||
if (recharge.status === 2 || recharge.status === 3) {
|
||||
// 已关闭或已退款的订单
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
uni.showToast({
|
||||
@@ -168,9 +229,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查充值订单是否已存在(待支付状态但无支付配置)
|
||||
if (recharge.status === 1 && (!orderResult.pay_config || !orderResult.pay_config.package)) {
|
||||
// 待支付订单已存在,引导用户跳转到订单页面
|
||||
if (recharge.status === 1 && !hasPreparedPaymentData(orderResult)) {
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
uni.showModal({
|
||||
@@ -187,36 +246,33 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有支付配置(新订单应该有)
|
||||
if (!orderResult.pay_config || !orderResult.pay_config.package) {
|
||||
if (!hasPreparedPaymentData(orderResult)) {
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
uni.showToast({ title: '支付参数获取失败', icon: 'none' });
|
||||
uni.showToast({
|
||||
title: '支付参数获取失败',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取强充信息
|
||||
const forceRechargeAmount = orderResult.linked_package_info?.force_recharge_amount || recharge.amount;
|
||||
const packageNames = orderResult.linked_package_info?.package_names || [];
|
||||
const packageNameStr = packageNames.length > 0 ? packageNames.join('、') : '套餐';
|
||||
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
|
||||
// 提示用户需要强充
|
||||
const packageNameStr = packageNames.length > 0 ? packageNames.join('、') : '套餐';
|
||||
const message = `购买${packageNameStr}需要先充值 ¥${formatMoney(forceRechargeAmount)},充值成功后将自动购买套餐`;
|
||||
releaseInModalCallback = true;
|
||||
|
||||
uni.showModal({
|
||||
title: '需要充值',
|
||||
content: message,
|
||||
content: `购买${packageNameStr}需要先充值 ¥${formatMoney(forceRechargeAmount)},充值成功后将自动购买套餐`,
|
||||
confirmText: '去充值',
|
||||
cancelText: '取消',
|
||||
success: async (res) => {
|
||||
try {
|
||||
if (res.confirm) {
|
||||
// 调起微信支付
|
||||
await handleWechatPay(orderResult.pay_config, true);
|
||||
await handlePreparedPayment(orderResult, true);
|
||||
}
|
||||
} finally {
|
||||
paySubmitting.value = false;
|
||||
@@ -229,47 +285,44 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有订单信息(非强充订单)
|
||||
if (!orderResult || !orderResult.order) {
|
||||
if (!orderResult?.order?.order_id) {
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
uni.showToast({ title: '订单创建失败', icon: 'none' });
|
||||
uni.showToast({
|
||||
title: '订单创建失败',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const orderId = orderResult.order.order_id;
|
||||
uni.showLoading({
|
||||
title: '准备支付...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
// 第二步:获取支付参数
|
||||
uni.showLoading({ title: '准备支付...', mask: true });
|
||||
|
||||
const payResult = await orderApi.pay(orderId, paymentMethod.value);
|
||||
const payResult = await orderApi.pay(orderResult.order.order_id, paymentMethod.value);
|
||||
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
|
||||
// 处理不同支付方式
|
||||
if (paymentMethod.value === 'wechat') {
|
||||
// 微信支付
|
||||
if (!payResult.pay_config || !payResult.pay_config.package) {
|
||||
uni.showToast({ title: '支付参数获取失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
await handleWechatPay(payResult.pay_config);
|
||||
} else if (paymentMethod.value === 'wallet') {
|
||||
// 钱包支付成功
|
||||
uni.showToast({ title: '支付成功', icon: 'success' });
|
||||
if (paymentMethod.value === 'wallet') {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
await handlePreparedPayment(payResult);
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
showModal.value = false;
|
||||
console.error('创建订单或支付失败', e);
|
||||
console.error('创建订单或支付失败', error);
|
||||
|
||||
// 特殊错误处理:订单正在创建中
|
||||
if (e.code === 1008) {
|
||||
if (error.code === 1008) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '订单正在创建中,是否跳转到我的订单页面进行支付?',
|
||||
@@ -283,9 +336,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他错误提示
|
||||
const errorMsg = e.msg || e.message || '操作失败,请稍后重试';
|
||||
uni.showToast({ title: errorMsg, icon: 'none' });
|
||||
uni.showToast({
|
||||
title: error.msg || error.message || '操作失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
if (!releaseInModalCallback) {
|
||||
paySubmitting.value = false;
|
||||
@@ -293,24 +347,27 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleWechatPay = async (payConfig, isForceRecharge = false) => {
|
||||
try {
|
||||
await wechatH5Pay(payConfig);
|
||||
// 支付成功
|
||||
const successMsg = isForceRecharge ? '充值成功,套餐将自动购买' : '支付成功';
|
||||
showPaymentToast(true, successMsg);
|
||||
setTimeout(() => {
|
||||
loadWalletBalance();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
// 支付失败或取消
|
||||
handlePaymentError(error);
|
||||
onShow(() => {
|
||||
if (!consumePendingPaymentRefresh(PAYMENT_REFRESH_TARGETS.PACKAGE_ORDER)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
syncPackagePageState();
|
||||
uni.showModal({
|
||||
title: '支付状态确认',
|
||||
content: '已返回页面,支付结果以订单状态为准。是否前往我的订单查看最新状态?',
|
||||
confirmText: '查看订单',
|
||||
cancelText: '稍后',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/order-list/order-list' });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadPackages();
|
||||
loadWalletBalance();
|
||||
syncPackagePageState();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -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;
|
||||
|
||||
125
utils/payment.js
125
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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user