payment-kit 1.20.5 → 1.20.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/crons/index.ts +11 -3
- package/api/src/index.ts +18 -14
- package/api/src/libs/env.ts +7 -0
- package/api/src/libs/url.ts +77 -0
- package/api/src/libs/vendor/adapters/factory.ts +40 -0
- package/api/src/libs/vendor/adapters/launcher-adapter.ts +179 -0
- package/api/src/libs/vendor/adapters/types.ts +91 -0
- package/api/src/libs/vendor/fulfillment.ts +317 -0
- package/api/src/queues/payment.ts +14 -10
- package/api/src/queues/payout.ts +1 -0
- package/api/src/queues/vendor/commission.ts +192 -0
- package/api/src/queues/vendor/fulfillment-coordinator.ts +625 -0
- package/api/src/queues/vendor/fulfillment.ts +98 -0
- package/api/src/queues/vendor/status-check.ts +178 -0
- package/api/src/routes/checkout-sessions.ts +12 -0
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/products.ts +72 -1
- package/api/src/routes/vendor.ts +527 -0
- package/api/src/store/migrations/20250820-add-product-vendor.ts +102 -0
- package/api/src/store/migrations/20250822-add-vendor-config-to-products.ts +56 -0
- package/api/src/store/models/checkout-session.ts +84 -18
- package/api/src/store/models/index.ts +3 -0
- package/api/src/store/models/payout.ts +11 -0
- package/api/src/store/models/product-vendor.ts +118 -0
- package/api/src/store/models/product.ts +15 -0
- package/blocklet.yml +8 -2
- package/doc/vendor_fulfillment_system.md +929 -0
- package/package.json +5 -4
- package/src/components/collapse.tsx +1 -0
- package/src/components/product/edit.tsx +9 -0
- package/src/components/product/form.tsx +11 -0
- package/src/components/product/vendor-config.tsx +249 -0
- package/src/components/vendor/actions.tsx +145 -0
- package/src/locales/en.tsx +89 -0
- package/src/locales/zh.tsx +89 -0
- package/src/pages/admin/products/index.tsx +11 -1
- package/src/pages/admin/products/products/detail.tsx +79 -2
- package/src/pages/admin/products/vendors/create.tsx +418 -0
- package/src/pages/admin/products/vendors/index.tsx +313 -0
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
# 多供应商发货系统完整实现方案
|
|
2
|
+
|
|
3
|
+
## 📋 项目概述
|
|
4
|
+
|
|
5
|
+
本文档总结了 Payment Kit 中多供应商发货系统的完整实现,包括架构设计、流程逻辑、技术实现和问题解决方案。
|
|
6
|
+
|
|
7
|
+
## 🎯 系统目标
|
|
8
|
+
|
|
9
|
+
### 核心需求
|
|
10
|
+
|
|
11
|
+
1. **多供应商支持**: 一个订单可以包含多个供应商的商品
|
|
12
|
+
2. **异步发货**: 不同供应商可以独立、并行发货
|
|
13
|
+
3. **状态协调**: 统一管理所有供应商的发货状态
|
|
14
|
+
4. **容错机制**: 处理发货失败、重试、超时等异常情况
|
|
15
|
+
5. **分成处理**: 所有供应商发货完成后,自动进行佣金分成
|
|
16
|
+
6. **退款保障**: 任何供应商失败即全额退款,确保用户权益
|
|
17
|
+
|
|
18
|
+
### 业务规则
|
|
19
|
+
|
|
20
|
+
- **全部成功**: 所有供应商发货成功 → 进行佣金分成
|
|
21
|
+
- **任何失败**: 有任何供应商失败、超时或超过重试次数 → **对已成功的供应商发起退货请求** → 全额退款给用户
|
|
22
|
+
- **超时时间**: 5分钟无响应视为超时
|
|
23
|
+
- **重试次数**: 最多3次重试
|
|
24
|
+
- **退货机制**: 对已完成发货的供应商发起退货请求
|
|
25
|
+
|
|
26
|
+
## 🏗️ 系统架构
|
|
27
|
+
|
|
28
|
+
### 架构模式: 协调器模式 (Coordinator Pattern)
|
|
29
|
+
|
|
30
|
+
#### 🔄 **核心流程 (文字版)**
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
1. Payment Success
|
|
34
|
+
↓
|
|
35
|
+
2. vendor-commission.ts
|
|
36
|
+
├─ 检查供应商配置
|
|
37
|
+
├─ 无供应商 → 直接冷钱包转账 ✅
|
|
38
|
+
└─ 有供应商 → 调用协调器
|
|
39
|
+
|
|
40
|
+
3. vendor-fulfillment-coordinator.ts
|
|
41
|
+
├─ 启动发货流程
|
|
42
|
+
├─ 初始化 vendor_info 状态
|
|
43
|
+
├─ 为每个供应商创建发货任务
|
|
44
|
+
└─ 被动等待通知
|
|
45
|
+
|
|
46
|
+
4. vendor-fulfillment.ts (并行执行)
|
|
47
|
+
├─ 供应商A发货 → 通知协调器 ✅
|
|
48
|
+
├─ 供应商B发货 → 通知协调器 ✅
|
|
49
|
+
├─ 供应商C发货 → 通知协调器 ❌
|
|
50
|
+
└─ 重试机制 (最多3次)
|
|
51
|
+
|
|
52
|
+
5. vendor-fulfillment-coordinator.ts
|
|
53
|
+
├─ 接收所有通知
|
|
54
|
+
├─ 更新 vendor_info 状态 (事务+锁)
|
|
55
|
+
├─ 检查整体状态
|
|
56
|
+
├─ 全部成功 → 分成 + 冷钱包 ✅
|
|
57
|
+
└─ 任何失败 → 🔄 对已成功供应商发起退货请求 → 全额退款 ❌
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### 📊 **详细架构图**
|
|
61
|
+
|
|
62
|
+
```mermaid
|
|
63
|
+
graph TD
|
|
64
|
+
A[Payment Success] --> B[Vendor Commission Queue]
|
|
65
|
+
B --> C{Has Vendor Config?}
|
|
66
|
+
C -->|No| D[Direct Deposit to Cold Wallet]
|
|
67
|
+
C -->|Yes| E[Start Multi-Vendor Fulfillment]
|
|
68
|
+
|
|
69
|
+
E --> F[Initialize vendor_info]
|
|
70
|
+
F --> G[Emit vendor.fulfillment.queued Events]
|
|
71
|
+
|
|
72
|
+
G --> H1[Vendor Fulfillment Queue - Launcher]
|
|
73
|
+
G --> H2[Vendor Fulfillment Queue - DID Names]
|
|
74
|
+
G --> H3[Vendor Fulfillment Queue - Vendor N]
|
|
75
|
+
|
|
76
|
+
H1 --> I1[LauncherAdapter.fulfillOrder]
|
|
77
|
+
H2 --> I2[DIDNamesAdapter.fulfillOrder]
|
|
78
|
+
H3 --> I3[VendorAdapter.fulfillOrder]
|
|
79
|
+
|
|
80
|
+
I1 --> J1{Success?}
|
|
81
|
+
I2 --> J2{Success?}
|
|
82
|
+
I3 --> J3{Success?}
|
|
83
|
+
|
|
84
|
+
J1 -->|Yes| K1[Update vendor_info: completed]
|
|
85
|
+
J1 -->|No| L1[Update vendor_info: failed + Retry]
|
|
86
|
+
J2 -->|Yes| K2[Update vendor_info: completed]
|
|
87
|
+
J2 -->|No| L2[Update vendor_info: failed + Retry]
|
|
88
|
+
J3 -->|Yes| K3[Update vendor_info: completed]
|
|
89
|
+
J3 -->|No| L3[Update vendor_info: failed + Retry]
|
|
90
|
+
|
|
91
|
+
K1 --> M[Trigger Coordinator]
|
|
92
|
+
K2 --> M
|
|
93
|
+
K3 --> M
|
|
94
|
+
L1 --> N{Max Retries?}
|
|
95
|
+
L2 --> N
|
|
96
|
+
L3 --> N
|
|
97
|
+
|
|
98
|
+
N -->|No| H1
|
|
99
|
+
N -->|Yes| O[Update vendor_info: max_retries_exceeded]
|
|
100
|
+
O --> M
|
|
101
|
+
|
|
102
|
+
M --> P[Fulfillment Coordinator]
|
|
103
|
+
P --> Q{All Completed?}
|
|
104
|
+
Q -->|Yes| R[Trigger Commission Process]
|
|
105
|
+
Q -->|No| S{Any Failed/Timeout?}
|
|
106
|
+
S -->|Yes| T[Handle Partial Failure]
|
|
107
|
+
S -->|No| U[Wait for More Completions]
|
|
108
|
+
|
|
109
|
+
T --> V[Request Return from Completed Vendors]
|
|
110
|
+
V --> X[Full Refund]
|
|
111
|
+
|
|
112
|
+
R --> Y[Commission & Payout Processing]
|
|
113
|
+
X --> Z[Refund Processing]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 核心组件
|
|
117
|
+
|
|
118
|
+
#### 1. **Vendor Commission Queue** (`vendor-commission.ts`)
|
|
119
|
+
|
|
120
|
+
- **职责**: 决策流程入口,判断是否需要供应商发货
|
|
121
|
+
- **输入**: PaymentIntent ID
|
|
122
|
+
- **输出**: 直接转冷钱包 OR 启动多供应商发货流程
|
|
123
|
+
|
|
124
|
+
#### 2. **Vendor Fulfillment Coordinator** (`vendor-fulfillment-coordinator.ts`)
|
|
125
|
+
|
|
126
|
+
- **职责**: 协调多个供应商的发货状态,决策后续流程
|
|
127
|
+
- **核心功能**:
|
|
128
|
+
- 初始化 `vendor_info` 状态
|
|
129
|
+
- 分发发货任务到各个供应商队列
|
|
130
|
+
- 收集和分析供应商状态
|
|
131
|
+
- 决策分成或退款
|
|
132
|
+
|
|
133
|
+
#### 3. **Vendor Fulfillment Queue** (`vendor-fulfillment.ts`)
|
|
134
|
+
|
|
135
|
+
- **职责**: 处理单个供应商的发货请求
|
|
136
|
+
- **核心功能**:
|
|
137
|
+
- 调用供应商适配器 (Adapter)
|
|
138
|
+
- 处理发货重试
|
|
139
|
+
- 更新供应商状态
|
|
140
|
+
- 通知协调器
|
|
141
|
+
|
|
142
|
+
#### 4. **Vendor Adapters** (`libs/adapters/`)
|
|
143
|
+
|
|
144
|
+
- **职责**: 与具体供应商系统集成
|
|
145
|
+
- **实现**: `LauncherAdapter`, `DIDNamesAdapter`
|
|
146
|
+
- **接口统一**: `VendorAdapter` 接口
|
|
147
|
+
|
|
148
|
+
## 📊 数据模型
|
|
149
|
+
|
|
150
|
+
### CheckoutSession.vendor_info 结构
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface VendorInfo {
|
|
154
|
+
vendor_id: string; // 供应商ID
|
|
155
|
+
order_id: string; // 供应商订单ID
|
|
156
|
+
status: 'pending' | 'completed' | 'failed' | 'max_retries_exceeded' | 'return_requested';
|
|
157
|
+
amount: string; // 订单金额
|
|
158
|
+
attempts: number; // 重试次数
|
|
159
|
+
lastAttemptAt: string; // 最后尝试时间
|
|
160
|
+
error_message?: string; // 错误信息
|
|
161
|
+
commissionAmount?: string; // 佣金金额
|
|
162
|
+
service_url?: string; // 服务访问URL
|
|
163
|
+
completedAt?: string; // 完成时间
|
|
164
|
+
// 🆕 退货请求相关字段
|
|
165
|
+
returnRequest?: {
|
|
166
|
+
reason: string; // 退货原因
|
|
167
|
+
requestedAt: string; // 请求时间
|
|
168
|
+
status: 'pending' | 'accepted' | 'rejected'; // 退货状态
|
|
169
|
+
returnDetails?: string; // 退货详情
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 状态流转
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
正常流程:
|
|
178
|
+
pending → completed ✅
|
|
179
|
+
|
|
180
|
+
重试流程:
|
|
181
|
+
pending → failed → pending (retry) → completed ✅
|
|
182
|
+
pending → failed → pending (retry) → failed → max_retries_exceeded ❌
|
|
183
|
+
|
|
184
|
+
退货流程 (🆕):
|
|
185
|
+
completed → return_requested (当其他供应商失败时)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 🔄 核心流程
|
|
189
|
+
|
|
190
|
+
### 📋 **流程概览**
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
📥 用户支付成功
|
|
194
|
+
↓
|
|
195
|
+
🔍 检查是否有供应商配置
|
|
196
|
+
├─ 无供应商 → 💰 直接转冷钱包 (结束)
|
|
197
|
+
└─ 有供应商 → 🚀 启动多供应商发货流程
|
|
198
|
+
↓
|
|
199
|
+
📊 初始化供应商状态追踪
|
|
200
|
+
↓
|
|
201
|
+
🔀 并行发送发货任务给所有供应商
|
|
202
|
+
├─ 📦 供应商A: Launcher 发货
|
|
203
|
+
├─ 📦 供应商B: DID Names 发货
|
|
204
|
+
└─ 📦 供应商C: 其他供应商发货
|
|
205
|
+
↓ (各自独立处理)
|
|
206
|
+
📝 每个供应商完成后更新状态
|
|
207
|
+
↓
|
|
208
|
+
🎯 协调器检查整体状态
|
|
209
|
+
├─ ✅ 全部成功 → 💸 佣金分成
|
|
210
|
+
└─ ❌ 任何失败 → 🔄 对已成功供应商发起退货请求 → 全额退款
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 1. 支付成功触发
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// payment.ts
|
|
217
|
+
async function updateCheckoutSessionOnPaymentSuccess() {
|
|
218
|
+
// ... 更新支付状态 ...
|
|
219
|
+
|
|
220
|
+
// 触发供应商佣金处理
|
|
221
|
+
await vendorCommissionQueue.push({
|
|
222
|
+
paymentIntentId: paymentIntent.id,
|
|
223
|
+
retryOnError: true,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 2. 供应商佣金决策
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// vendor-commission.ts
|
|
232
|
+
export const handleVendorCommission = async (job: VendorCommissionJob) => {
|
|
233
|
+
const hasVendorConfig = await checkIfPaymentIntentHasVendors(paymentIntentId);
|
|
234
|
+
|
|
235
|
+
if (!hasVendorConfig) {
|
|
236
|
+
// 无供应商配置 → 直接转冷钱包
|
|
237
|
+
await executeDirectDepositVault(paymentIntent);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 有供应商配置 → 启动多供应商发货流程
|
|
242
|
+
await startVendorFulfillment(checkoutSessionId, paymentIntentId);
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 3. 多供应商发货启动
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// vendor-fulfillment-coordinator.ts
|
|
250
|
+
export async function startVendorFulfillment(
|
|
251
|
+
checkoutSessionId: string,
|
|
252
|
+
paymentIntentId: string
|
|
253
|
+
) {
|
|
254
|
+
// 1. 初始化供应商信息
|
|
255
|
+
const vendorConfigs = await getVendorConfigurations(checkoutSessionId);
|
|
256
|
+
const vendorInfo: VendorInfo[] = vendorConfigs.map(config => ({
|
|
257
|
+
vendor_id: config.vendor_id,
|
|
258
|
+
order_id: '',
|
|
259
|
+
status: 'pending',
|
|
260
|
+
amount: '0',
|
|
261
|
+
attempts: 0,
|
|
262
|
+
lastAttemptAt: new Date().toISOString(),
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
await updateVendorInfo(checkoutSessionId, vendorInfo);
|
|
266
|
+
|
|
267
|
+
// 2. 为每个供应商分发发货任务
|
|
268
|
+
for (const config of vendorConfigs) {
|
|
269
|
+
const jobId = `vendor-fulfillment-${checkoutSessionId}-${config.vendor_id}`;
|
|
270
|
+
|
|
271
|
+
events.emit('vendor.fulfillment.queued', jobId, {
|
|
272
|
+
checkoutSessionId,
|
|
273
|
+
paymentIntentId,
|
|
274
|
+
vendorId: config.vendor_id,
|
|
275
|
+
vendorConfig: config,
|
|
276
|
+
retryOnError: true,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 4. 单供应商发货处理
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// vendor-fulfillment.ts
|
|
286
|
+
export const handleVendorFulfillment = async (job: VendorFulfillmentJob) => {
|
|
287
|
+
const { checkoutSessionId, paymentIntentId, vendorId, vendorConfig } = job;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
// 调用供应商发货服务
|
|
291
|
+
const fulfillmentResult = await VendorFulfillmentService.fulfillSingleVendorOrder(
|
|
292
|
+
checkoutSessionId,
|
|
293
|
+
paymentIntentId,
|
|
294
|
+
vendorConfig
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// 发货成功 → 通知协调器
|
|
298
|
+
await notifyVendorFulfillmentComplete(checkoutSessionId, paymentIntentId, vendorId, 'completed', {
|
|
299
|
+
orderId: fulfillmentResult.orderId,
|
|
300
|
+
commissionAmount: fulfillmentResult.commissionAmount,
|
|
301
|
+
serviceUrl: fulfillmentResult.serviceUrl,
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// 发货失败 → 通知协调器,让队列系统处理重试
|
|
305
|
+
await notifyVendorFulfillmentComplete(checkoutSessionId, paymentIntentId, vendorId, 'failed', {
|
|
306
|
+
lastError: error.message,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
throw error; // 重新抛出,触发队列重试
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 5. 协调器状态管理
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// vendor-fulfillment-coordinator.ts
|
|
318
|
+
async function handleFulfillmentCoordination(job: CoordinatorJob) {
|
|
319
|
+
const { checkoutSessionId, paymentIntentId } = job;
|
|
320
|
+
|
|
321
|
+
// 1. 获取所有供应商的当前状态
|
|
322
|
+
const vendorInfo = await getVendorInfo(checkoutSessionId);
|
|
323
|
+
const vendorConfigs = await getVendorConfigurations(checkoutSessionId);
|
|
324
|
+
|
|
325
|
+
// 2. 分析当前状态
|
|
326
|
+
const analysis = analyzeVendorStates(vendorInfo, vendorConfigs);
|
|
327
|
+
|
|
328
|
+
// 3. 根据分析结果决策
|
|
329
|
+
if (analysis.allCompleted) {
|
|
330
|
+
// 所有供应商发货成功 → 触发分成
|
|
331
|
+
await triggerCommissionProcess(checkoutSessionId, paymentIntentId);
|
|
332
|
+
} else if (analysis.anyMaxRetriesExceeded || analysis.shouldTimeout) {
|
|
333
|
+
// 有供应商超过重试次数或超时 → 决策是否部分退款
|
|
334
|
+
await handlePartialFailure(checkoutSessionId, paymentIntentId, vendorInfo, analysis);
|
|
335
|
+
} else {
|
|
336
|
+
// 还有供应商在处理中 → 等待下次发货完成通知
|
|
337
|
+
logger.info('Some vendors still in progress, waiting for completion');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function analyzeVendorStates(vendorInfo: VendorInfo[], vendorConfigs: any[]) {
|
|
342
|
+
const successfulVendors = vendorInfo.filter(vendor => vendor.status === 'completed');
|
|
343
|
+
const failedVendors = vendorInfo.filter(vendor => vendor.status === 'failed');
|
|
344
|
+
const maxRetriesExceededVendors = vendorInfo.filter(vendor => vendor.status === 'max_retries_exceeded');
|
|
345
|
+
const pendingVendors = vendorInfo.filter(vendor => vendor.status === 'pending');
|
|
346
|
+
|
|
347
|
+
const allCompleted = successfulVendors.length === vendorConfigs.length;
|
|
348
|
+
const anyMaxRetriesExceeded = maxRetriesExceededVendors.length > 0;
|
|
349
|
+
|
|
350
|
+
// 检查超时(包括长时间pending状态)
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
const shouldTimeout = vendorInfo.some(vendor => {
|
|
353
|
+
if (!vendor.lastAttemptAt) return false;
|
|
354
|
+
const timeSinceLastAttempt = now - new Date(vendor.lastAttemptAt).getTime();
|
|
355
|
+
|
|
356
|
+
// 对于pending状态,如果超过超时时间,也认为是超时
|
|
357
|
+
if (vendor.status === 'pending' && timeSinceLastAttempt > MAX_FULFILLMENT_TIMEOUT) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return timeSinceLastAttempt > MAX_FULFILLMENT_TIMEOUT;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
allCompleted,
|
|
366
|
+
anyMaxRetriesExceeded,
|
|
367
|
+
shouldTimeout,
|
|
368
|
+
successfulVendors,
|
|
369
|
+
failedVendors,
|
|
370
|
+
maxRetriesExceededVendors,
|
|
371
|
+
pendingVendors,
|
|
372
|
+
totalVendors: vendorConfigs.length,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 6. 失败处理
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// 协调器决策逻辑
|
|
381
|
+
if (analysis.allCompleted) {
|
|
382
|
+
// 所有供应商发货成功 → 触发分成
|
|
383
|
+
await triggerCommissionProcess(checkoutSessionId, paymentIntentId);
|
|
384
|
+
} else if (analysis.anyMaxRetriesExceeded || analysis.shouldTimeout || analysis.failedVendors.length > 0) {
|
|
385
|
+
// 有任何供应商失败、超过重试次数或超时 → 对已成功的供应商发起退货请求,再退款
|
|
386
|
+
await initiateFullRefund(checkoutSessionId, paymentIntentId, 'vendor_failure_detected');
|
|
387
|
+
} else {
|
|
388
|
+
// 还有供应商在处理中 → 等待下次发货完成通知
|
|
389
|
+
logger.info('Some vendors still in progress, waiting for completion');
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## 🔧 技术实现细节
|
|
394
|
+
|
|
395
|
+
### 1. 竞态条件解决方案
|
|
396
|
+
|
|
397
|
+
**问题**: 多个供应商同时更新 `vendor_info` 字段导致数据覆盖
|
|
398
|
+
|
|
399
|
+
**解决方案**: 数据库事务 + 行锁
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
async function updateSingleVendorInfo(
|
|
403
|
+
checkoutSessionId: string,
|
|
404
|
+
vendorId: string,
|
|
405
|
+
update: Partial<VendorInfo>
|
|
406
|
+
): Promise<void> {
|
|
407
|
+
const lockKey = `vendor-info-${checkoutSessionId}`;
|
|
408
|
+
const lock = await getLock(lockKey); // 应用层锁
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
// 使用数据库事务确保原子性操作
|
|
412
|
+
await sequelize.transaction(async (transaction: any) => {
|
|
413
|
+
// 在事务中读取最新数据,加行锁
|
|
414
|
+
const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId, {
|
|
415
|
+
transaction,
|
|
416
|
+
lock: transaction.LOCK.UPDATE, // 数据库行锁
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (!checkoutSession) {
|
|
420
|
+
throw new Error(`CheckoutSession not found: ${checkoutSessionId}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const current = (checkoutSession.vendor_info as VendorInfo[]) || [];
|
|
424
|
+
const index = current.findIndex((vendor) => vendor.vendor_id === vendorId);
|
|
425
|
+
|
|
426
|
+
// 更新数据
|
|
427
|
+
if (index >= 0) {
|
|
428
|
+
current[index] = { ...current[index], ...update };
|
|
429
|
+
} else {
|
|
430
|
+
current.push({ vendor_id: vendorId, ...update });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// 在同一事务中更新
|
|
434
|
+
await CheckoutSession.update(
|
|
435
|
+
{ vendor_info: current },
|
|
436
|
+
{ where: { id: checkoutSessionId }, transaction }
|
|
437
|
+
);
|
|
438
|
+
});
|
|
439
|
+
} finally {
|
|
440
|
+
lock.release(); // 释放应用层锁
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### 2. 事件驱动架构
|
|
446
|
+
|
|
447
|
+
**优势**:
|
|
448
|
+
|
|
449
|
+
- **解耦**: 各组件独立,易于维护
|
|
450
|
+
- **扩展性**: 容易添加新的供应商适配器
|
|
451
|
+
- **可观测性**: 每个步骤都有明确的事件和日志
|
|
452
|
+
|
|
453
|
+
**事件流**:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
payment_intent.succeeded → vendor.commission.queued → vendor.fulfillment.queued → vendor.fulfillment.completed → fulfillment.coordination.triggered → commission.processing.started
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 3. 队列系统设计
|
|
460
|
+
|
|
461
|
+
**特性**:
|
|
462
|
+
|
|
463
|
+
- **持久化**: 作业存储在数据库中,重启不丢失
|
|
464
|
+
- **重试机制**: 自动重试失败的作业,最多3次
|
|
465
|
+
- **并发控制**: 多个供应商并行处理
|
|
466
|
+
- **优雅降级**: 单个供应商失败不影响其他供应商
|
|
467
|
+
|
|
468
|
+
**队列配置**:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
const vendorFulfillmentQueue = createQueue({
|
|
472
|
+
name: 'vendor-fulfillment',
|
|
473
|
+
onJob: handleVendorFulfillment,
|
|
474
|
+
maxRetries: 3,
|
|
475
|
+
retryDelay: 5000, // 5秒延迟重试
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 4. 供应商适配器接口
|
|
480
|
+
|
|
481
|
+
**统一接口设计**:
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
interface VendorAdapter {
|
|
485
|
+
fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult>;
|
|
486
|
+
requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult>; // 🆕 退货请求接口
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
interface FulfillOrderParams {
|
|
490
|
+
productCode: string;
|
|
491
|
+
customerId: string;
|
|
492
|
+
amount: string;
|
|
493
|
+
currency: string;
|
|
494
|
+
metadata?: Record<string, any>;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
interface FulfillOrderResult {
|
|
498
|
+
orderId: string;
|
|
499
|
+
status: 'completed' | 'pending' | 'failed';
|
|
500
|
+
serviceUrl?: string;
|
|
501
|
+
estimatedTime?: number;
|
|
502
|
+
message?: string;
|
|
503
|
+
progress?: number;
|
|
504
|
+
installationInfo?: InstallationInfo;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// 🆕 退货请求相关接口
|
|
508
|
+
interface ReturnRequestParams {
|
|
509
|
+
orderId: string; // 原订单ID
|
|
510
|
+
vendorOrderId?: string; // 供应商内部订单ID
|
|
511
|
+
reason: string; // 退货原因
|
|
512
|
+
paymentIntentId: string; // 支付意图ID
|
|
513
|
+
customParams?: Record<string, any>; // 自定义参数
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
interface ReturnRequestResult {
|
|
517
|
+
orderId: string;
|
|
518
|
+
status: 'requested' | 'accepted' | 'rejected' | 'failed';
|
|
519
|
+
message: string;
|
|
520
|
+
expectedProcessingTime?: number; // 预期处理时间(秒)
|
|
521
|
+
returnDetails?: {
|
|
522
|
+
instructions: string; // 退货说明
|
|
523
|
+
contactInfo?: string; // 联系方式
|
|
524
|
+
deadline?: string; // 退货截止时间
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**具体实现示例**:
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
// LauncherAdapter - 模拟应用部署
|
|
533
|
+
export class LauncherAdapter implements VendorAdapter {
|
|
534
|
+
async fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult> {
|
|
535
|
+
const appId = generateAppId();
|
|
536
|
+
const serviceUrl = `https://${appId}.slp.abtnet.io/.well-known/service/admin/overview`;
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
orderId: `launcher_order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
540
|
+
status: 'completed', // 立即完成(测试模式)
|
|
541
|
+
serviceUrl,
|
|
542
|
+
estimatedTime: 0,
|
|
543
|
+
message: 'Launcher application installation completed successfully',
|
|
544
|
+
progress: 100,
|
|
545
|
+
installationInfo: {
|
|
546
|
+
appId,
|
|
547
|
+
appUrl: `https://${appId}.slp.abtnet.io`,
|
|
548
|
+
adminUrl: serviceUrl,
|
|
549
|
+
status: 'completed',
|
|
550
|
+
estimatedCompletionTime: new Date().toISOString(),
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// DIDNamesAdapter - 模拟域名注册
|
|
557
|
+
export class DIDNamesAdapter implements VendorAdapter {
|
|
558
|
+
async fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult> {
|
|
559
|
+
return {
|
|
560
|
+
orderId: `didnames_order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
561
|
+
status: 'completed',
|
|
562
|
+
serviceUrl: 'https://bbqaxpyvvmw7prkuecfwoavocixwuithkshfnjd4gai.did.abtnet.io',
|
|
563
|
+
estimatedTime: 0,
|
|
564
|
+
message: 'DID Names service activated successfully',
|
|
565
|
+
progress: 100,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// 🆕 退货请求实现
|
|
570
|
+
async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
|
|
571
|
+
// 发起退货请求
|
|
572
|
+
return {
|
|
573
|
+
orderId: params.orderId,
|
|
574
|
+
status: 'requested',
|
|
575
|
+
message: 'Return request submitted for Launcher application',
|
|
576
|
+
expectedProcessingTime: 1800, // 30分钟处理时间
|
|
577
|
+
returnDetails: {
|
|
578
|
+
instructions: 'Application will be automatically destroyed within 30 minutes',
|
|
579
|
+
contactInfo: 'support@launcher.example.com',
|
|
580
|
+
deadline: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24小时
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// DIDNamesAdapter - 模拟域名注册
|
|
587
|
+
export class DIDNamesAdapter implements VendorAdapter {
|
|
588
|
+
async fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult> {
|
|
589
|
+
return {
|
|
590
|
+
orderId: `didnames_order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
591
|
+
status: 'completed',
|
|
592
|
+
serviceUrl: 'https://bbqaxpyvvmw7prkuecfwoavocixwuithkshfnjd4gai.did.abtnet.io',
|
|
593
|
+
estimatedTime: 0,
|
|
594
|
+
message: 'DID Names service activated successfully',
|
|
595
|
+
progress: 100,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// 🆕 退货请求实现
|
|
600
|
+
async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
|
|
601
|
+
// 发起退货请求
|
|
602
|
+
return {
|
|
603
|
+
orderId: params.orderId,
|
|
604
|
+
status: 'requested',
|
|
605
|
+
message: 'Return request submitted for DID Names service',
|
|
606
|
+
expectedProcessingTime: 600, // 10分钟处理时间
|
|
607
|
+
returnDetails: {
|
|
608
|
+
instructions: 'Domain service will be deactivated and domain released to available pool',
|
|
609
|
+
contactInfo: 'support@didnames.example.com',
|
|
610
|
+
deadline: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7天
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
## 🔍 监控和调试
|
|
618
|
+
|
|
619
|
+
### 📊 **状态追踪流程**
|
|
620
|
+
|
|
621
|
+
```
|
|
622
|
+
🔍 日志追踪点:
|
|
623
|
+
|
|
624
|
+
1️⃣ 发货启动
|
|
625
|
+
└─ 'Starting vendor fulfillment process'
|
|
626
|
+
└─ { checkoutSessionId, vendorCount }
|
|
627
|
+
|
|
628
|
+
2️⃣ 供应商状态更新 (关键!)
|
|
629
|
+
├─ '🔧 updateSingleVendorInfo - Before update'
|
|
630
|
+
├─ '🔧 updateSingleVendorInfo - Current vendor info (in transaction)'
|
|
631
|
+
├─ '🔧 updateSingleVendorInfo - Database update completed'
|
|
632
|
+
└─ '🔧 updateSingleVendorInfo - Lock released'
|
|
633
|
+
|
|
634
|
+
3️⃣ 协调器决策
|
|
635
|
+
├─ 'Vendor fulfillment analysis' { analysis }
|
|
636
|
+
├─ 'All vendors completed successfully' ✅
|
|
637
|
+
├─ 'Some vendors failed, initiating full refund' ❌
|
|
638
|
+
└─ 'Some vendors still in progress' ⏳
|
|
639
|
+
|
|
640
|
+
4️⃣ 错误处理
|
|
641
|
+
└─ 'Vendor fulfillment failed' { vendorId, error, attempts }
|
|
642
|
+
|
|
643
|
+
5️⃣ 退货请求流程 (🆕)
|
|
644
|
+
├─ '🔄 Starting return request process'
|
|
645
|
+
├─ '📤 Requesting return for vendor' { vendorId, orderId }
|
|
646
|
+
├─ '✅ Return request submitted successfully'
|
|
647
|
+
├─ '❌ Return request failed'
|
|
648
|
+
└─ '🏁 Return request process completed'
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### 日志策略
|
|
652
|
+
|
|
653
|
+
**调试日志关键点**:
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
// 1. 发货启动
|
|
657
|
+
logger.info('Starting vendor fulfillment process', { checkoutSessionId, vendorCount });
|
|
658
|
+
|
|
659
|
+
// 2. 供应商状态更新
|
|
660
|
+
logger.info('🔧 updateSingleVendorInfo - Before update', { checkoutSessionId, vendorId, update });
|
|
661
|
+
logger.info('🔧 updateSingleVendorInfo - Current vendor info (in transaction)', { vendorIndex, currentVendorStatus });
|
|
662
|
+
logger.info('🔧 updateSingleVendorInfo - Database update completed (in transaction)', { updateStatus });
|
|
663
|
+
|
|
664
|
+
// 3. 协调器决策
|
|
665
|
+
logger.info('Vendor fulfillment analysis', { checkoutSessionId, analysis });
|
|
666
|
+
logger.info('All vendors completed successfully, triggering commission', { successfulVendors });
|
|
667
|
+
|
|
668
|
+
// 4. 错误处理
|
|
669
|
+
logger.error('Vendor fulfillment failed', { vendorId, error: error.message, attempts });
|
|
670
|
+
|
|
671
|
+
// 5. 退货请求流程 (🆕)
|
|
672
|
+
logger.info('🔄 Starting return request process', { checkoutSessionId, paymentIntentId, reason });
|
|
673
|
+
logger.info('📤 Requesting return for vendor', { vendorId, orderId, reason });
|
|
674
|
+
logger.info('✅ Return request submitted successfully', { vendorId, returnStatus, expectedTime });
|
|
675
|
+
logger.error('❌ Return request failed', { vendorId, orderId, error });
|
|
676
|
+
logger.info('🏁 Return request process completed', { totalVendors, successful, failed });
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**关键指标监控**:
|
|
680
|
+
|
|
681
|
+
- 供应商发货成功率
|
|
682
|
+
- 平均发货时间
|
|
683
|
+
- 重试次数分布
|
|
684
|
+
- 超时发生频率
|
|
685
|
+
- 竞态条件检测
|
|
686
|
+
- 🆕 **退货请求相关指标**:
|
|
687
|
+
- 退货请求触发频率
|
|
688
|
+
- 各供应商退货请求成功率
|
|
689
|
+
- 退货请求响应时间
|
|
690
|
+
- 退货请求失败原因分析
|
|
691
|
+
|
|
692
|
+
## 🔄 **退货请求系统**
|
|
693
|
+
|
|
694
|
+
### 🎯 **退货机制概述**
|
|
695
|
+
|
|
696
|
+
当多供应商发货过程中出现失败时,系统需要对已经成功发货的供应商**发起退货请求**,通知供应商处理退货事宜,然后直接进行全额退款。
|
|
697
|
+
|
|
698
|
+
#### **退货请求触发条件**
|
|
699
|
+
|
|
700
|
+
- ✅ 任何供应商发货失败
|
|
701
|
+
- ✅ 任何供应商发货超时 (5分钟)
|
|
702
|
+
- ✅ 任何供应商超过最大重试次数 (3次)
|
|
703
|
+
|
|
704
|
+
#### **退货流程设计**
|
|
705
|
+
|
|
706
|
+
```
|
|
707
|
+
❌ 失败检测
|
|
708
|
+
↓
|
|
709
|
+
🔍 筛选已成功发货的供应商 (status: 'completed')
|
|
710
|
+
↓
|
|
711
|
+
📤 并行发送退货请求
|
|
712
|
+
├─ 📦 Launcher: 通知退货,提供退货说明
|
|
713
|
+
├─ 📦 DID Names: 通知退货,提供退货说明
|
|
714
|
+
└─ 📦 其他供应商: 发送退货请求
|
|
715
|
+
↓
|
|
716
|
+
📝 更新状态为 return_requested
|
|
717
|
+
↓
|
|
718
|
+
💰 立即执行全额退款给用户 (不等待供应商处理完成)
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### 🏗️ **退货请求系统实现**
|
|
722
|
+
|
|
723
|
+
#### **1. 退货请求协调器实现**
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
// vendor-fulfillment-coordinator.ts
|
|
727
|
+
async function initiateFullRefund(
|
|
728
|
+
checkoutSessionId: string,
|
|
729
|
+
paymentIntentId: string,
|
|
730
|
+
reason: string
|
|
731
|
+
): Promise<void> {
|
|
732
|
+
logger.warn('Initiating full refund with return requests', {
|
|
733
|
+
checkoutSessionId, paymentIntentId, reason
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
try {
|
|
737
|
+
// 🔄 第一步:对已成功发货的供应商发起退货请求
|
|
738
|
+
await requestReturnsFromCompletedVendors(checkoutSessionId, paymentIntentId, reason);
|
|
739
|
+
|
|
740
|
+
// 💰 第二步:立即创建退款记录并执行退款(不等待退货处理完成)
|
|
741
|
+
const refund = await Refund.create({
|
|
742
|
+
type: 'refund',
|
|
743
|
+
amount: paymentIntent.amount,
|
|
744
|
+
description: `Full refund due to ${reason}`,
|
|
745
|
+
// ... 其他字段
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
} catch (error) {
|
|
749
|
+
logger.error('Failed to create full refund', { error: error.message });
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
async function requestReturnsFromCompletedVendors(
|
|
754
|
+
checkoutSessionId: string,
|
|
755
|
+
paymentIntentId: string,
|
|
756
|
+
reason: string
|
|
757
|
+
): Promise<void> {
|
|
758
|
+
const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
|
|
759
|
+
const vendorInfos = (checkoutSession.vendor_info as VendorInfo[]) || [];
|
|
760
|
+
|
|
761
|
+
// 筛选已完成的供应商
|
|
762
|
+
const completedVendors = vendorInfos.filter(vendor => vendor.status === 'completed');
|
|
763
|
+
|
|
764
|
+
if (completedVendors.length === 0) {
|
|
765
|
+
logger.info('No completed vendors to request returns from', { checkoutSessionId });
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// 并行处理所有供应商的退货请求
|
|
770
|
+
const returnRequestPromises = completedVendors.map(vendor =>
|
|
771
|
+
requestReturnFromSingleVendor(checkoutSessionId, paymentIntentId, vendor, reason)
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
const returnResults = await Promise.allSettled(returnRequestPromises);
|
|
775
|
+
|
|
776
|
+
// 记录退货请求结果
|
|
777
|
+
logger.info('🏁 Return request process completed', {
|
|
778
|
+
checkoutSessionId,
|
|
779
|
+
totalVendors: completedVendors.length,
|
|
780
|
+
successful: returnResults.filter(r => r.status === 'fulfilled').length,
|
|
781
|
+
failed: returnResults.filter(r => r.status === 'rejected').length,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function requestReturnFromSingleVendor(
|
|
786
|
+
checkoutSessionId: string,
|
|
787
|
+
paymentIntentId: string,
|
|
788
|
+
vendor: VendorInfo,
|
|
789
|
+
reason: string
|
|
790
|
+
): Promise<void> {
|
|
791
|
+
try {
|
|
792
|
+
// 1. 获取供应商适配器
|
|
793
|
+
const vendorAdapter = VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
|
|
794
|
+
|
|
795
|
+
// 2. 调用供应商的退货请求方法
|
|
796
|
+
const returnResult = await vendorAdapter.requestReturn({
|
|
797
|
+
orderId: vendor.order_id,
|
|
798
|
+
reason: `Return request due to: ${reason}`,
|
|
799
|
+
paymentIntentId,
|
|
800
|
+
customParams: {
|
|
801
|
+
returnType: 'order_failure',
|
|
802
|
+
originalAmount: vendor.amount,
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// 3. 更新为退货请求状态
|
|
807
|
+
await updateSingleVendorInfo(checkoutSessionId, vendor.vendor_id, {
|
|
808
|
+
status: 'return_requested',
|
|
809
|
+
returnRequest: {
|
|
810
|
+
reason,
|
|
811
|
+
requestedAt: new Date().toISOString(),
|
|
812
|
+
status: returnResult.status === 'requested' ? 'pending' : returnResult.status,
|
|
813
|
+
returnDetails: returnResult.returnDetails?.instructions || returnResult.message,
|
|
814
|
+
},
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
logger.info('✅ Return request submitted successfully', {
|
|
818
|
+
vendorId: vendor.vendor_id,
|
|
819
|
+
returnStatus: returnResult.status,
|
|
820
|
+
expectedTime: returnResult.expectedProcessingTime,
|
|
821
|
+
});
|
|
822
|
+
} catch (error) {
|
|
823
|
+
logger.error('❌ Return request failed', {
|
|
824
|
+
vendorId: vendor.vendor_id,
|
|
825
|
+
error: error.message,
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// 更新状态为退货请求失败,但继续退款流程
|
|
829
|
+
await updateSingleVendorInfo(checkoutSessionId, vendor.vendor_id, {
|
|
830
|
+
status: 'return_requested', // 标记为已请求退货(即使请求失败)
|
|
831
|
+
error_message: `Return request failed: ${error.message}`,
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// 不重新抛出错误,退货请求失败不影响用户退款
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
#### **2. 供应商适配器退货请求实现**
|
|
840
|
+
|
|
841
|
+
```typescript
|
|
842
|
+
// LauncherAdapter 退货请求实现
|
|
843
|
+
export class LauncherAdapter implements VendorAdapter {
|
|
844
|
+
async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
|
|
845
|
+
logger.info('Requesting return for Launcher order', {
|
|
846
|
+
orderId: params.orderId,
|
|
847
|
+
reason: params.reason,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
// 发起退货请求 - 这里只是通知,不立即执行销毁
|
|
851
|
+
return {
|
|
852
|
+
orderId: params.orderId,
|
|
853
|
+
status: 'requested',
|
|
854
|
+
message: 'Return request submitted for Launcher application',
|
|
855
|
+
expectedProcessingTime: 1800, // 30分钟处理时间
|
|
856
|
+
returnDetails: {
|
|
857
|
+
instructions: 'Application will be automatically destroyed within 30 minutes. You will receive confirmation email once completed.',
|
|
858
|
+
contactInfo: 'support@launcher.example.com',
|
|
859
|
+
deadline: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24小时内处理
|
|
860
|
+
},
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// DIDNamesAdapter 退货请求实现
|
|
866
|
+
export class DIDNamesAdapter implements VendorAdapter {
|
|
867
|
+
async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
|
|
868
|
+
logger.info('Requesting return for DID Names order', {
|
|
869
|
+
orderId: params.orderId,
|
|
870
|
+
reason: params.reason,
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
// 发起退货请求 - 通知供应商处理退货
|
|
874
|
+
return {
|
|
875
|
+
orderId: params.orderId,
|
|
876
|
+
status: 'requested',
|
|
877
|
+
message: 'Return request submitted for DID Names service',
|
|
878
|
+
expectedProcessingTime: 600, // 10分钟处理时间
|
|
879
|
+
returnDetails: {
|
|
880
|
+
instructions: 'Domain service will be deactivated and domain released to available pool within 10 minutes.',
|
|
881
|
+
contactInfo: 'support@didnames.example.com',
|
|
882
|
+
deadline: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7天内处理
|
|
883
|
+
},
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### 🔒 **退货请求系统特性**
|
|
890
|
+
|
|
891
|
+
#### **简化设计**
|
|
892
|
+
|
|
893
|
+
- **请求即忘**: 发起退货请求后立即进行用户退款,不等待供应商处理结果
|
|
894
|
+
- **状态记录**: 在 `vendor_info` 中记录退货请求状态和详情
|
|
895
|
+
- **容错优先**: 退货请求失败不影响用户退款流程
|
|
896
|
+
|
|
897
|
+
#### **用户体验优先**
|
|
898
|
+
|
|
899
|
+
- **快速退款**: 用户立即获得退款,无需等待供应商处理
|
|
900
|
+
- **透明记录**: 系统记录所有退货请求的详细信息
|
|
901
|
+
- **后台处理**: 供应商退货处理在后台异步进行
|
|
902
|
+
|
|
903
|
+
#### **并发处理**
|
|
904
|
+
|
|
905
|
+
- **并行请求**: 同时对多个供应商发起退货请求
|
|
906
|
+
- **异步设计**: 退货请求不阻塞退款流程
|
|
907
|
+
- **错误隔离**: 单个供应商退货请求失败不影响其他供应商
|
|
908
|
+
|
|
909
|
+
### 📊 **退货请求监控指标**
|
|
910
|
+
|
|
911
|
+
#### **关键指标**
|
|
912
|
+
|
|
913
|
+
- **退货请求触发率**: `退货请求次数 / 总订单数`
|
|
914
|
+
- **退货请求成功率**: `成功发起退货请求数 / 总退货请求数`
|
|
915
|
+
- **平均请求响应时间**: 从触发到请求完成的时间
|
|
916
|
+
- **供应商响应表现**: 各供应商的退货请求响应情况
|
|
917
|
+
|
|
918
|
+
#### **告警策略**
|
|
919
|
+
|
|
920
|
+
- 退货请求成功率低于 95% 时告警
|
|
921
|
+
- 退货请求平均响应时间超过 10 秒时告警
|
|
922
|
+
- 特定供应商退货请求连续失败 3 次时告警
|
|
923
|
+
|
|
924
|
+
### 🎯 **业务价值**
|
|
925
|
+
|
|
926
|
+
1. **用户体验优先**: 用户立即获得退款,无需等待供应商处理
|
|
927
|
+
2. **简化流程**: 去除复杂的分布式事务逻辑,降低系统复杂度
|
|
928
|
+
3. **风险隔离**: 供应商退货处理问题不影响用户权益
|
|
929
|
+
4. **操作透明**: 完整记录退货请求过程,便于后续跟踪和审计
|