@zucker-framework/payment 1.0.2 → 1.0.3
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/dist/index.d.mts +440 -188
- package/dist/index.d.ts +440 -188
- package/dist/index.js +896 -181
- package/dist/index.mjs +878 -179
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,121 +1,425 @@
|
|
|
1
|
+
import * as _paypal_paypal_server_sdk from '@paypal/paypal-server-sdk';
|
|
2
|
+
import { OrdersController, Order as Order$1 } from '@paypal/paypal-server-sdk';
|
|
3
|
+
import Stripe from 'stripe';
|
|
1
4
|
import { ModelOperations } from '@zucker-framework/core';
|
|
2
5
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
3
6
|
import { Type, DynamicModule, InjectionToken } from '@nestjs/common';
|
|
4
|
-
import Stripe from 'stripe';
|
|
5
|
-
import * as _paypal_paypal_server_sdk from '@paypal/paypal-server-sdk';
|
|
6
|
-
import { OrdersController } from '@paypal/paypal-server-sdk';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* 结算状态的中立模型与迁移规则。
|
|
10
|
+
*
|
|
11
|
+
* 这是多渠道支付里最容易出错、也最值得统一的一层:各渠道的状态名不同
|
|
12
|
+
* (Stripe 的 requires_capture / succeeded、PayPal 的 APPROVED / COMPLETED、
|
|
13
|
+
* 微信的 NOTPAY / SUCCESS / CLOSED),但"钱有没有动"的语义是共通的。
|
|
14
|
+
*
|
|
15
|
+
* 适配器只负责把供应商状态映射成这里的 SettlementState,不做任何持久化决策;
|
|
16
|
+
* 迁移是否允许由 reconcile() 统一裁决,保证所有渠道遵守同一套安全性质。
|
|
11
17
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
declare const SETTLEMENT_STATES: readonly ["created", "authorized", "captured", "refunded", "partially_refunded", "failed", "canceled", "unknown"];
|
|
19
|
+
type SettlementState = (typeof SETTLEMENT_STATES)[number];
|
|
20
|
+
declare function isMoneyMoved(state: SettlementState): boolean;
|
|
21
|
+
declare function isTerminal(state: SettlementState): boolean;
|
|
22
|
+
/** reconcile 的裁决结果,供调用方决定是否写库、是否告警。 */
|
|
23
|
+
interface SettlementDecision {
|
|
24
|
+
/** 应当持久化的状态;与 current 相同表示不必写库 */
|
|
25
|
+
next: SettlementState;
|
|
26
|
+
/** 是否发生了实际变化 */
|
|
27
|
+
changed: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 观察到的状态被拒绝采纳时给出原因,调用方应据此告警而不是静默丢弃。
|
|
30
|
+
* 例如已扣款的订单收到 failed 通知——这通常意味着两边对不上,需要人工核对。
|
|
31
|
+
*/
|
|
32
|
+
rejected?: "money-already-moved" | "already-terminal" | "not-observable";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 依据当前持久化状态与本次观察到的状态,裁决应当落库的状态。
|
|
36
|
+
*
|
|
37
|
+
* 安全性质(由测试锁定):
|
|
38
|
+
* 1. unknown 永不覆盖任何已知状态——查不到不等于没付。
|
|
39
|
+
* 2. 钱已移动过的状态不会被 failed / canceled 回退——迟到的失败回调不能抹掉已收款。
|
|
40
|
+
* 3. 终态不会被非终态覆盖——已退款的订单不会因为一条旧的 captured 回调变回已付。
|
|
41
|
+
* 4. captured → partially_refunded → refunded 单向推进,不可逆。
|
|
42
|
+
* 5. 幂等:同样的观察重复裁决,结果不变且 changed=false。
|
|
43
|
+
*/
|
|
44
|
+
declare function reconcile(current: SettlementState, observed: SettlementState): SettlementDecision;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 多渠道支付的中立契约。
|
|
48
|
+
*
|
|
49
|
+
* 取代 ../payment.interface.ts 里那份未经任何适配器验证的 PaymentProvider。
|
|
50
|
+
* 形状取自 CNIPA 已跑通 Stripe + PayPal 的 PaymentGateway,并按 Medusa
|
|
51
|
+
* AbstractPaymentProcessor 的生命周期补齐授权/扣款分离,同时预留微信、
|
|
52
|
+
* 支付宝这类非跳转渠道所需的结构。
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* 金额。
|
|
56
|
+
*
|
|
57
|
+
* 刻意用十进制字符串而不是 number:
|
|
58
|
+
* - 旧 PaymentProvider 用 `number` + 注释"最小单位(分)",CNIPA 用 Decimal 元,
|
|
59
|
+
* 两套单位并存迟早在某个边界上错一位。
|
|
60
|
+
* - 浮点数表示钱本身就是错的。
|
|
61
|
+
* - JPY/HUF/TWD 无小数位,cents 模型对它们是特例;字符串对所有币种一致。
|
|
62
|
+
* 适配器负责在边界上转成各自 SDK 需要的形式。
|
|
63
|
+
*/
|
|
64
|
+
interface Money {
|
|
65
|
+
/** 十进制字符串,如 "29.00";不接受 number */
|
|
66
|
+
amount: string;
|
|
67
|
+
/** ISO 4217,大写 */
|
|
28
68
|
currency: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 用户需要执行的下一步动作。
|
|
72
|
+
*
|
|
73
|
+
* 不能简化成 `redirectUrl?: string`——微信 Native 下单返回的是
|
|
74
|
+
* `weixin://wxpay/bizpayurl?pr=...`,是要渲染成二维码给用户扫的字符串,
|
|
75
|
+
* 不是能 location.assign 过去的地址;支付宝 PC 常返回需自动提交的表单。
|
|
76
|
+
* 用判别联合表达,调用方按 kind 分派,新增渠道不必改已有分支。
|
|
77
|
+
*/
|
|
78
|
+
type PaymentNextAction = {
|
|
79
|
+
kind: "redirect";
|
|
80
|
+
url: string;
|
|
81
|
+
} | {
|
|
82
|
+
kind: "qrcode";
|
|
83
|
+
value: string;
|
|
84
|
+
} | {
|
|
85
|
+
kind: "form_post";
|
|
86
|
+
action: string;
|
|
87
|
+
fields: Record<string, string>;
|
|
88
|
+
} | {
|
|
89
|
+
kind: "sdk_params";
|
|
90
|
+
params: Record<string, unknown>;
|
|
91
|
+
} | {
|
|
92
|
+
kind: "none";
|
|
93
|
+
};
|
|
94
|
+
interface PaymentSession {
|
|
95
|
+
/** 供应商侧的支付标识,后续 capture / retrieve / refund 都用它 */
|
|
96
|
+
providerRef: string;
|
|
97
|
+
nextAction: PaymentNextAction;
|
|
98
|
+
state: SettlementState;
|
|
99
|
+
/**
|
|
100
|
+
* 供应商侧记录的金额。
|
|
101
|
+
* 放在契约里而不是让调用方去 raw 里翻:金额是每个渠道都必须如实回传的字段,
|
|
102
|
+
* 调用方要拿它和本地订单核对,不该为此了解某个供应商的响应结构。
|
|
103
|
+
*/
|
|
104
|
+
money: Money;
|
|
105
|
+
/** 供应商原始响应,供排查与对账留痕;不参与业务判断 */
|
|
106
|
+
raw?: unknown;
|
|
107
|
+
}
|
|
108
|
+
interface CreatePaymentInput {
|
|
109
|
+
/** 调用方的订单标识,会回传到供应商以便双向核对 */
|
|
110
|
+
reference: string;
|
|
111
|
+
money: Money;
|
|
29
112
|
description?: string;
|
|
30
|
-
|
|
31
|
-
customerId?: string;
|
|
113
|
+
/** 付款完成/取消后的回跳地址;不适用的渠道可忽略 */
|
|
32
114
|
returnUrl?: string;
|
|
33
115
|
cancelUrl?: string;
|
|
116
|
+
/**
|
|
117
|
+
* 幂等键。同一个键重试必须返回同一笔支付,不得产生第二笔。
|
|
118
|
+
* 旧接口完全没有这个概念,重试即重复下单。
|
|
119
|
+
*/
|
|
120
|
+
idempotencyKey: string;
|
|
121
|
+
metadata?: Record<string, string>;
|
|
34
122
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Webhook 原始请求。
|
|
125
|
+
*
|
|
126
|
+
* 必须同时给出原始字节与**全部**请求头:PayPal 验签需要 5 个头
|
|
127
|
+
* (paypal-transmission-id/time/sig、paypal-cert-url、paypal-auth-algo),
|
|
128
|
+
* 旧接口的 `signature: string` 结构上装不下;微信 v3 同样需要多个头。
|
|
129
|
+
* 验签必须针对原始字节,序列化过一轮再验会因键序/数字规范化失败。
|
|
130
|
+
*/
|
|
131
|
+
interface WebhookRequest {
|
|
132
|
+
rawBody: Uint8Array | string;
|
|
133
|
+
headers: Record<string, string>;
|
|
42
134
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
135
|
+
interface WebhookObservation {
|
|
136
|
+
/** 供应商事件类型原文,便于排查 */
|
|
137
|
+
type: string;
|
|
138
|
+
/** 本次事件指向的支付;无法关联时为 undefined,调用方应告警而非静默丢弃 */
|
|
139
|
+
providerRef?: string;
|
|
140
|
+
/** 归一化后的状态;无法判定时为 unknown */
|
|
141
|
+
state: SettlementState;
|
|
142
|
+
raw: unknown;
|
|
143
|
+
}
|
|
144
|
+
interface RefundInput {
|
|
145
|
+
providerRef: string;
|
|
146
|
+
/** 省略表示全额退款 */
|
|
147
|
+
money?: Money;
|
|
148
|
+
reason?: string;
|
|
149
|
+
idempotencyKey: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 渠道适配器。
|
|
153
|
+
*
|
|
154
|
+
* 实现约定:
|
|
155
|
+
* - 只做协议与状态映射,不做任何产品决策(金额、资格、时机、文案)。
|
|
156
|
+
* - retrieve 必须是只读的。对账会调用它,绝不能在对账过程中扣款——
|
|
157
|
+
* "查询顺手把 APPROVED 捕获了"是这类系统最贵的一类 bug。
|
|
158
|
+
* - 判定不了就返回 unknown,不要猜成 failed。
|
|
159
|
+
*/
|
|
160
|
+
interface PaymentProviderAdapter {
|
|
161
|
+
readonly id: string;
|
|
162
|
+
/**
|
|
163
|
+
* 该渠道客观支持的结算币种;undefined 表示未声明限制。
|
|
164
|
+
* 这是供应商事实,不是产品策略——"我们只卖 USD"属于上层应用。
|
|
165
|
+
*/
|
|
166
|
+
readonly supportedCurrencies?: readonly string[];
|
|
167
|
+
createPayment(input: CreatePaymentInput): Promise<PaymentSession>;
|
|
168
|
+
/** 授权与扣款分离的渠道用它完成扣款(PayPal APPROVED → capture) */
|
|
169
|
+
capture(providerRef: string, idempotencyKey: string): Promise<PaymentSession>;
|
|
170
|
+
/** 只读查询,供回跳后主动确认与定时对账使用 */
|
|
171
|
+
retrieve(providerRef: string): Promise<PaymentSession>;
|
|
172
|
+
refund(input: RefundInput): Promise<{
|
|
173
|
+
refundRef: string;
|
|
174
|
+
state: SettlementState;
|
|
175
|
+
}>;
|
|
176
|
+
cancel?(providerRef: string): Promise<PaymentSession>;
|
|
177
|
+
/** 验签并归一化;验签失败必须抛错,不得返回一个"未知"事件蒙混过去 */
|
|
178
|
+
parseWebhook(request: WebhookRequest): Promise<WebhookObservation>;
|
|
50
179
|
}
|
|
51
|
-
|
|
180
|
+
/** 该渠道能否处理这个币种;未声明限制时一律放行。 */
|
|
181
|
+
declare function adapterSupportsCurrency(adapter: Pick<PaymentProviderAdapter, "supportedCurrencies">, currency: string): boolean;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 十进制金额字符串与供应商最小单位之间的转换。
|
|
185
|
+
*
|
|
186
|
+
* 契约对外一律用十进制字符串(见 Money),但 Stripe 等渠道的 API 收最小单位整数。
|
|
187
|
+
* 转换只发生在适配器边界。用字符串补位而不是 `Math.round(x * 100)`,真正的收益有两点
|
|
188
|
+
* (不是"浮点一定算错"——对两位小数的常规金额,乘 100 再取整其实是可靠的):
|
|
189
|
+
* 1. **精度超限会报错而不是静默取整**。`Math.round(1.005 * 100)` 得到 100,
|
|
190
|
+
* 悄悄吞掉半分钱;这里直接拒绝,让调用方去面对它本就不该产生的金额。
|
|
191
|
+
* 2. **零小数位币种不会被乘以 100**。JPY 的 500 就是 500,按两位小数处理会放大 100 倍。
|
|
192
|
+
*/
|
|
193
|
+
declare function currencyExponent(currency: string): number;
|
|
194
|
+
/** "29.00" + USD → 2900;"500" + JPY → 500 */
|
|
195
|
+
declare function toMinorUnits(amount: string, currency: string): number;
|
|
196
|
+
/** 2900 + USD → "29.00";500 + JPY → "500" */
|
|
197
|
+
declare function fromMinorUnits(value: number, currency: string): string;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 渠道适配器注册表。
|
|
201
|
+
*
|
|
202
|
+
* 取代旧的 PaymentService:那一版路由的是从未被任何适配器实现过的 PaymentProvider,
|
|
203
|
+
* 并且把 createPaymentIntent / confirmPayment 之类的业务动作也挂在自己身上。
|
|
204
|
+
* 这里只做注册与查找——编排(建单、履约、对账)属于上层应用,不属于注册表。
|
|
205
|
+
*/
|
|
206
|
+
declare class PaymentProviderRegistry {
|
|
207
|
+
private readonly logger;
|
|
208
|
+
private readonly adapters;
|
|
209
|
+
constructor(adapters?: readonly PaymentProviderAdapter[]);
|
|
210
|
+
register(adapter: PaymentProviderAdapter): void;
|
|
211
|
+
has(id: string): boolean;
|
|
212
|
+
get(id: string): PaymentProviderAdapter;
|
|
213
|
+
list(): PaymentProviderAdapter[];
|
|
214
|
+
/** 能处理该币种的渠道;"本产品卖不卖这个币种"由上层决定,不在这里过滤。 */
|
|
215
|
+
listForCurrency(currency: string): PaymentProviderAdapter[];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Read-only provider evidence; unknown must never be treated as an instruction to charge. */
|
|
219
|
+
type PaymentSettlementState = 'paid' | 'unpaid' | 'unknown';
|
|
220
|
+
|
|
221
|
+
interface PayPalPaymentTransportOptions {
|
|
222
|
+
clientId: string;
|
|
223
|
+
clientSecret: string;
|
|
224
|
+
mode: 'live' | 'sandbox';
|
|
225
|
+
fetcher?: typeof fetch;
|
|
226
|
+
logger?: {
|
|
227
|
+
warn: (message: string) => void;
|
|
228
|
+
error: (message: string) => void;
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
interface PayPalCaptureRefundRequest {
|
|
232
|
+
/**
|
|
233
|
+
* 省略表示全额退款——PayPal 的 refund 接口本就允许不传 amount。
|
|
234
|
+
* 原先声明为必填,导致调用方为了"全额退"必须自己算出金额再传回去,
|
|
235
|
+
* 一旦本地金额与供应商记录有出入就会退错数额。
|
|
236
|
+
*/
|
|
237
|
+
amount?: {
|
|
238
|
+
value: string;
|
|
239
|
+
currency_code: string;
|
|
240
|
+
};
|
|
241
|
+
note_to_payer?: string;
|
|
242
|
+
}
|
|
243
|
+
interface PayPalCaptureRefundResponse {
|
|
244
|
+
id?: string;
|
|
245
|
+
status?: string;
|
|
246
|
+
message?: string;
|
|
247
|
+
}
|
|
248
|
+
interface PayPalOrderRepresentation {
|
|
249
|
+
id?: string;
|
|
250
|
+
status?: string;
|
|
251
|
+
links?: Array<{
|
|
252
|
+
rel?: string;
|
|
253
|
+
href?: string;
|
|
254
|
+
}>;
|
|
255
|
+
purchaseUnits?: Array<{
|
|
256
|
+
payments?: {
|
|
257
|
+
captures?: Array<{
|
|
258
|
+
id?: string;
|
|
259
|
+
}>;
|
|
260
|
+
};
|
|
261
|
+
}>;
|
|
262
|
+
}
|
|
263
|
+
interface PayPalOrderObservation<T extends PayPalOrderRepresentation = PayPalOrderRepresentation> {
|
|
264
|
+
raw: T;
|
|
265
|
+
id?: string;
|
|
266
|
+
approveUrl?: string;
|
|
267
|
+
captureId?: string;
|
|
268
|
+
settlement: PaymentSettlementState;
|
|
269
|
+
}
|
|
270
|
+
/** APPROVED means approval only, never evidence of a captured payment. */
|
|
271
|
+
declare function observePayPalOrder<T extends PayPalOrderRepresentation>(raw: T): PayPalOrderObservation<T>;
|
|
272
|
+
interface PayPalCaptureObservation {
|
|
273
|
+
raw: Record<string, unknown>;
|
|
52
274
|
id: string;
|
|
53
|
-
|
|
54
|
-
amount: number;
|
|
55
|
-
status: 'pending' | 'succeeded' | 'failed';
|
|
56
|
-
reason?: string;
|
|
275
|
+
failureReason?: string;
|
|
57
276
|
}
|
|
58
|
-
|
|
277
|
+
type PayPalWebhookObservation = {
|
|
278
|
+
raw: Record<string, unknown>;
|
|
59
279
|
type: string;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
280
|
+
kind: 'capture-succeeded' | 'capture-failed';
|
|
281
|
+
capture: PayPalCaptureObservation;
|
|
282
|
+
} | {
|
|
283
|
+
raw: Record<string, unknown>;
|
|
284
|
+
type: string;
|
|
285
|
+
kind: 'unknown';
|
|
286
|
+
};
|
|
287
|
+
interface PayPalMoneyReturnedObservation {
|
|
63
288
|
id: string;
|
|
64
|
-
|
|
65
|
-
|
|
289
|
+
captureId?: string;
|
|
290
|
+
orderId?: string;
|
|
291
|
+
amount: string;
|
|
66
292
|
currency: string;
|
|
67
|
-
|
|
68
|
-
intervalCount: number;
|
|
69
|
-
trialDays?: number;
|
|
70
|
-
features?: string[];
|
|
293
|
+
status: 'PENDING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
|
|
71
294
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
295
|
+
/** Decode only after signature verification; this performs no authentication or network call. */
|
|
296
|
+
declare function decodePayPalWebhookEvent(payload: string): PayPalWebhookObservation;
|
|
297
|
+
/** Additive decoder: the original capture-event discriminated union stays compatible. */
|
|
298
|
+
declare function decodePayPalMoneyReturnedEvent(payload: string): {
|
|
299
|
+
raw: Record<string, unknown>;
|
|
300
|
+
type: string;
|
|
301
|
+
kind: 'capture-refunded' | 'capture-reversed';
|
|
302
|
+
returned: PayPalMoneyReturnedObservation;
|
|
303
|
+
} | null;
|
|
304
|
+
/** SDK order transport plus the OAuth-backed REST endpoints absent from the current SDK integration. */
|
|
305
|
+
declare class PayPalPaymentTransport {
|
|
306
|
+
private readonly options;
|
|
307
|
+
readonly orders: OrdersController;
|
|
308
|
+
private readonly baseUrl;
|
|
309
|
+
private readonly fetcher;
|
|
310
|
+
constructor(options: PayPalPaymentTransportOptions);
|
|
311
|
+
createOrder(input: Parameters<OrdersController['createOrder']>[0]): Promise<PayPalOrderObservation<_paypal_paypal_server_sdk.Order>>;
|
|
312
|
+
/** An explicit capture command; never invoked by getOrder or status observation. */
|
|
313
|
+
captureOrder(input: Parameters<OrdersController['captureOrder']>[0]): Promise<PayPalOrderObservation<_paypal_paypal_server_sdk.Order>>;
|
|
314
|
+
getOrder(input: Parameters<OrdersController['getOrder']>[0]): Promise<PayPalOrderObservation<_paypal_paypal_server_sdk.Order>>;
|
|
315
|
+
private getAccessToken;
|
|
316
|
+
verifyWebhookSignature(payload: string, headers: Record<string, string>, webhookId: string): Promise<boolean>;
|
|
317
|
+
refundCapture(captureId: string, request: PayPalCaptureRefundRequest, requestId?: string): Promise<PayPalCaptureRefundResponse>;
|
|
87
318
|
}
|
|
88
319
|
|
|
89
320
|
/**
|
|
90
|
-
*
|
|
321
|
+
* PayPal Orders v2 适配器。
|
|
91
322
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* paymentService.register(wechatPayProvider);
|
|
95
|
-
* await paymentService.createPaymentIntent('stripe', params);
|
|
323
|
+
* 只做协议转换与状态映射,不含任何产品策略。传输层沿用已在生产验证过的
|
|
324
|
+
* PayPalPaymentTransport,本适配器不重写它。
|
|
96
325
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
326
|
+
interface PayPalAdapterOptions {
|
|
327
|
+
transport: PayPalPaymentTransport;
|
|
328
|
+
/** Webhook 验签用;缺失时 parseWebhook 必须拒绝,而不是放行 */
|
|
329
|
+
webhookId?: string;
|
|
330
|
+
brandName?: string;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* PayPal REST 支持的 25 种结算币种。
|
|
334
|
+
* 来源:https://developer.paypal.com/api/rest/reference/currency-codes/
|
|
335
|
+
* 这是供应商事实;"本产品只卖哪几种"属于上层应用,不写在这里。
|
|
336
|
+
*/
|
|
337
|
+
declare const PAYPAL_SUPPORTED_CURRENCIES: readonly ["AUD", "BRL", "CAD", "CNY", "CZK", "DKK", "EUR", "HKD", "HUF", "ILS", "JPY", "MYR", "MXN", "TWD", "NZD", "NOK", "PHP", "PLN", "GBP", "RUB", "SGD", "SEK", "CHF", "THB", "USD"];
|
|
338
|
+
/**
|
|
339
|
+
* PayPal 订单状态 → 中立状态。
|
|
340
|
+
*
|
|
341
|
+
* 关键约定:顶层 COMPLETED 还不足以判定已扣款,必须同时看到一条 COMPLETED 的
|
|
342
|
+
* capture;否则停在 authorized 继续观察。识别不了的状态一律 unknown,绝不猜成 failed。
|
|
343
|
+
*/
|
|
344
|
+
declare function observePayPalState(order: Order$1): SettlementState;
|
|
345
|
+
declare function createPayPalAdapter(options: PayPalAdapterOptions): PaymentProviderAdapter;
|
|
346
|
+
|
|
347
|
+
interface StripeAdapterOptions {
|
|
348
|
+
client: Stripe;
|
|
349
|
+
/** Webhook 验签密钥;缺失时 parseWebhook 必须拒绝 */
|
|
350
|
+
webhookSecret?: string;
|
|
351
|
+
/** 传给 Checkout 的支付方式配置(pmc_xxx),由上层按业务决定 */
|
|
352
|
+
paymentMethodConfiguration?: string;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Stripe Checkout 会话状态 → 中立状态。
|
|
356
|
+
* 识别不了的组合一律 unknown:Stripe 的异步支付方式(如银行转账)会长时间停在
|
|
357
|
+
* complete + unpaid,那不是失败。
|
|
358
|
+
*/
|
|
359
|
+
declare function observeStripeState(session: Stripe.Checkout.Session): SettlementState;
|
|
360
|
+
declare function createStripeAdapter(options: StripeAdapterOptions): PaymentProviderAdapter;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 渠道适配器契约套件。
|
|
364
|
+
*
|
|
365
|
+
* 多渠道抽象的价值不在接口长什么样,而在于**所有渠道遵守同一组安全性质**。
|
|
366
|
+
* 每接一个渠道都要跑这套;它抓的是那些只在生产环境、只在特定时序下才暴露的错误。
|
|
367
|
+
*
|
|
368
|
+
* 刻意不依赖任何测试框架:返回结构化结果,既能在 vitest/jest 里包一层,
|
|
369
|
+
* 也能被 node --test 或 CI 脚本直接调用。
|
|
370
|
+
*/
|
|
371
|
+
interface ContractCheck {
|
|
372
|
+
name: string;
|
|
373
|
+
ok: boolean;
|
|
374
|
+
detail?: string;
|
|
375
|
+
}
|
|
376
|
+
interface AdapterProbe {
|
|
377
|
+
/** 每次返回全新的适配器实例,以及它对供应商发起过的**写操作**记录 */
|
|
378
|
+
create(): {
|
|
379
|
+
adapter: PaymentProviderAdapter;
|
|
380
|
+
mutatingCalls: () => string[];
|
|
381
|
+
};
|
|
382
|
+
/** 该渠道支持的一个币种 */
|
|
383
|
+
supportedCurrency: string;
|
|
384
|
+
/** 该渠道不支持的一个币种;未声明限制时传 null */
|
|
385
|
+
unsupportedCurrency: string | null;
|
|
386
|
+
/** 一个签名无效的 webhook 请求 */
|
|
387
|
+
invalidWebhook: WebhookRequest;
|
|
388
|
+
/** 写操作端点名中代表"扣款"的片段,用于检测只读违规 */
|
|
389
|
+
captureCallNames?: readonly string[];
|
|
390
|
+
}
|
|
391
|
+
declare function checkPaymentAdapterContract(probe: AdapterProbe): Promise<ContractCheck[]>;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 内存参考适配器。
|
|
395
|
+
*
|
|
396
|
+
* 两个用途:
|
|
397
|
+
* 1. 让接入方在不连真实网关的情况下测自己的编排逻辑。
|
|
398
|
+
* 2. 给 contract-suite 当基准——一个**正确**的适配器必须全过,
|
|
399
|
+
* 故意写坏的变体必须被逐条抓出来。没有这一步,契约套件只是一组永远
|
|
400
|
+
* 绿灯的断言,证明不了任何事。
|
|
401
|
+
*/
|
|
402
|
+
interface FakeAdapterOptions {
|
|
403
|
+
id?: string;
|
|
404
|
+
supportedCurrencies?: readonly string[];
|
|
405
|
+
/** 故意违约,用于验证 contract-suite 的检出能力 */
|
|
406
|
+
defects?: {
|
|
407
|
+
/** retrieve 里偷偷扣款——最贵的一类 bug */
|
|
408
|
+
retrieveCaptures?: boolean;
|
|
409
|
+
/** 忽略幂等键,每次都开新支付 */
|
|
410
|
+
ignoreIdempotency?: boolean;
|
|
411
|
+
/** 验签失败也不抛错,返回一个"未知"事件蒙混过去 */
|
|
412
|
+
acceptsBadSignature?: boolean;
|
|
413
|
+
/** 把中间态猜成 failed */
|
|
414
|
+
guessesPendingAsFailed?: boolean;
|
|
415
|
+
/** 金额按浮点处理 */
|
|
416
|
+
mangleAmount?: boolean;
|
|
417
|
+
};
|
|
118
418
|
}
|
|
419
|
+
declare function createFakeAdapter(options?: FakeAdapterOptions): {
|
|
420
|
+
adapter: PaymentProviderAdapter;
|
|
421
|
+
mutatingCalls: () => string[];
|
|
422
|
+
};
|
|
119
423
|
|
|
120
424
|
declare const ORDER_PERSISTENCE = "PAYMENT_ORDER_PERSISTENCE";
|
|
121
425
|
/**
|
|
@@ -147,7 +451,12 @@ interface Order {
|
|
|
147
451
|
providerId: string;
|
|
148
452
|
paymentIntentId?: string;
|
|
149
453
|
paymentId?: string;
|
|
150
|
-
|
|
454
|
+
/**
|
|
455
|
+
* 十进制字符串,与 Money.amount 同一表示。
|
|
456
|
+
* 原先是 number:一旦金额在 number 与字符串之间来回转换,"29.00" 会变成 29,
|
|
457
|
+
* 三位小数币种会被静默截断。整条链路只保留一种表示。
|
|
458
|
+
*/
|
|
459
|
+
amount: string;
|
|
151
460
|
currency: string;
|
|
152
461
|
status: OrderStatus;
|
|
153
462
|
metadata?: Record<string, string>;
|
|
@@ -159,7 +468,7 @@ interface Order {
|
|
|
159
468
|
interface CreateOrderParams {
|
|
160
469
|
orderId: string;
|
|
161
470
|
providerId: string;
|
|
162
|
-
payment:
|
|
471
|
+
payment: CreatePaymentInput;
|
|
163
472
|
}
|
|
164
473
|
declare class OrderNotFoundError extends Error {
|
|
165
474
|
readonly orderId: string;
|
|
@@ -167,14 +476,35 @@ declare class OrderNotFoundError extends Error {
|
|
|
167
476
|
}
|
|
168
477
|
/** Durable provider workflow. External calls never run inside a database transaction here. */
|
|
169
478
|
declare class OrderService {
|
|
170
|
-
private readonly
|
|
479
|
+
private readonly providers;
|
|
171
480
|
private readonly orders;
|
|
172
|
-
constructor(
|
|
481
|
+
constructor(providers: PaymentProviderRegistry, orders: OrderPersistence<Order>);
|
|
173
482
|
createOrder(params: CreateOrderParams): Promise<Order>;
|
|
174
483
|
private initiatePayment;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
484
|
+
/**
|
|
485
|
+
* 扣款。幂等键由调用方给出——同一笔订单重试必须复用同一个键,否则渠道会认为是新支付。
|
|
486
|
+
* 判定不了的结果保持 confirming,交由只读对账解决,绝不在这里重复扣款。
|
|
487
|
+
*/
|
|
488
|
+
confirmOrder(orderId: string, idempotencyKey: string): Promise<PaymentSession>;
|
|
489
|
+
/**
|
|
490
|
+
* 把渠道观察到的结算状态落到订单上。
|
|
491
|
+
* 迁移是否允许由 reconcile() 统一裁决,保证所有渠道遵守同一套安全性质
|
|
492
|
+
* (unknown 不覆盖已知状态、已收款不被迟到的失败回调回退)。
|
|
493
|
+
*/
|
|
494
|
+
private applySettlement;
|
|
495
|
+
refundOrder(orderId: string, idempotencyKey: string, options?: {
|
|
496
|
+
money?: CreatePaymentInput['money'];
|
|
497
|
+
reason?: string;
|
|
498
|
+
}): Promise<{
|
|
499
|
+
refundRef: string;
|
|
500
|
+
state: SettlementState;
|
|
501
|
+
}>;
|
|
502
|
+
/**
|
|
503
|
+
* 重试失败订单。
|
|
504
|
+
* idempotencyKey 必须与上一次不同:沿用旧键会让渠道原样返回那笔已失败的支付,
|
|
505
|
+
* 看起来"重试了"其实什么都没发生。
|
|
506
|
+
*/
|
|
507
|
+
retryOrder(orderId: string, idempotencyKey: string): Promise<Order>;
|
|
178
508
|
cancelOrder(orderId: string): Promise<Order>;
|
|
179
509
|
getOrder(orderId: string): Promise<Order>;
|
|
180
510
|
listOrders(): Promise<Order[]>;
|
|
@@ -190,7 +520,8 @@ interface RecoveryEmailParams {
|
|
|
190
520
|
userName: string;
|
|
191
521
|
orderId: string;
|
|
192
522
|
orderNumber?: string;
|
|
193
|
-
amount
|
|
523
|
+
/** 十进制字符串,与 Order.amount / Money.amount 同一表示 */
|
|
524
|
+
amount: string;
|
|
194
525
|
currency: string;
|
|
195
526
|
errorReason: string;
|
|
196
527
|
retryUrl: string;
|
|
@@ -258,9 +589,10 @@ declare class PaymentRecoveryService {
|
|
|
258
589
|
*/
|
|
259
590
|
handleFailedPayment(orderId: string, errorCode?: string, errorMessage?: string): Promise<void>;
|
|
260
591
|
/**
|
|
261
|
-
* 重试失败订单 — 将 failed → created
|
|
592
|
+
* 重试失败订单 — 将 failed → created 并重新发起支付。
|
|
593
|
+
* 幂等键由调用方给出且必须与上次不同,否则渠道会原样返回那笔已失败的支付。
|
|
262
594
|
*/
|
|
263
|
-
retryOrder(orderId: string): Promise<Order>;
|
|
595
|
+
retryOrder(orderId: string, idempotencyKey: string): Promise<Order>;
|
|
264
596
|
getRecoveryAttempts(orderId: string): Promise<{
|
|
265
597
|
count: number;
|
|
266
598
|
lastAt: Date;
|
|
@@ -302,7 +634,7 @@ interface PaymentRecoverySentEvent {
|
|
|
302
634
|
|
|
303
635
|
declare const PAYMENT_PROVIDERS = "PAYMENT_PROVIDERS";
|
|
304
636
|
interface PaymentModuleOptions {
|
|
305
|
-
providers?:
|
|
637
|
+
providers?: PaymentProviderAdapter[];
|
|
306
638
|
orderPersistence: OrderPersistence<Order>;
|
|
307
639
|
/** Required when using payment recovery mail; no process-memory default. */
|
|
308
640
|
recoveryAttempts?: RecoveryAttemptStore;
|
|
@@ -317,9 +649,6 @@ declare class ZuckerPaymentModule {
|
|
|
317
649
|
static forRootAsync(options: PaymentModuleAsyncOptions): DynamicModule;
|
|
318
650
|
}
|
|
319
651
|
|
|
320
|
-
/** Read-only provider evidence; unknown must never be treated as an instruction to charge. */
|
|
321
|
-
type PaymentSettlementState = 'paid' | 'unpaid' | 'unknown';
|
|
322
|
-
|
|
323
652
|
/** Construct the installed Stripe peer with the consumer's explicit API/transport configuration. */
|
|
324
653
|
declare function createStripeClient(secretKey: string, options: Stripe.StripeConfig): Stripe;
|
|
325
654
|
/** Verify raw webhook bytes using the installed SDK's timestamp/signature checks. */
|
|
@@ -338,7 +667,9 @@ interface StripeCheckoutObservation {
|
|
|
338
667
|
settlement: PaymentSettlementState;
|
|
339
668
|
}
|
|
340
669
|
declare function observeStripeCheckoutSession(raw: Stripe.Checkout.Session): StripeCheckoutObservation;
|
|
341
|
-
declare function createStripeCheckoutSession(client: Pick<Stripe, 'checkout'>, params: Stripe.Checkout.SessionCreateParams
|
|
670
|
+
declare function createStripeCheckoutSession(client: Pick<Stripe, 'checkout'>, params: Stripe.Checkout.SessionCreateParams,
|
|
671
|
+
/** 传入 idempotencyKey 后,重试同一请求不会产生第二个 Session */
|
|
672
|
+
options?: Stripe.RequestOptions): Promise<StripeCheckoutObservation>;
|
|
342
673
|
declare function retrieveStripeCheckoutSession(client: Pick<Stripe, 'checkout'>, id: string): Promise<StripeCheckoutObservation>;
|
|
343
674
|
interface StripePaymentIntentObservation {
|
|
344
675
|
raw: Stripe.PaymentIntent;
|
|
@@ -352,83 +683,4 @@ interface StripePaymentIntentObservation {
|
|
|
352
683
|
declare function observeStripePaymentIntent(raw: Stripe.PaymentIntent): StripePaymentIntentObservation;
|
|
353
684
|
declare function retrieveStripePaymentIntent(client: Pick<Stripe, 'paymentIntents'>, id: string, params?: Stripe.PaymentIntentRetrieveParams): Promise<StripePaymentIntentObservation>;
|
|
354
685
|
|
|
355
|
-
|
|
356
|
-
clientId: string;
|
|
357
|
-
clientSecret: string;
|
|
358
|
-
mode: 'live' | 'sandbox';
|
|
359
|
-
fetcher?: typeof fetch;
|
|
360
|
-
logger?: {
|
|
361
|
-
warn: (message: string) => void;
|
|
362
|
-
error: (message: string) => void;
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
interface PayPalCaptureRefundRequest {
|
|
366
|
-
amount: {
|
|
367
|
-
value: string;
|
|
368
|
-
currency_code: string;
|
|
369
|
-
};
|
|
370
|
-
note_to_payer?: string;
|
|
371
|
-
}
|
|
372
|
-
interface PayPalCaptureRefundResponse {
|
|
373
|
-
id?: string;
|
|
374
|
-
status?: string;
|
|
375
|
-
message?: string;
|
|
376
|
-
}
|
|
377
|
-
interface PayPalOrderRepresentation {
|
|
378
|
-
id?: string;
|
|
379
|
-
status?: string;
|
|
380
|
-
links?: Array<{
|
|
381
|
-
rel?: string;
|
|
382
|
-
href?: string;
|
|
383
|
-
}>;
|
|
384
|
-
purchaseUnits?: Array<{
|
|
385
|
-
payments?: {
|
|
386
|
-
captures?: Array<{
|
|
387
|
-
id?: string;
|
|
388
|
-
}>;
|
|
389
|
-
};
|
|
390
|
-
}>;
|
|
391
|
-
}
|
|
392
|
-
interface PayPalOrderObservation<T extends PayPalOrderRepresentation = PayPalOrderRepresentation> {
|
|
393
|
-
raw: T;
|
|
394
|
-
id?: string;
|
|
395
|
-
approveUrl?: string;
|
|
396
|
-
captureId?: string;
|
|
397
|
-
settlement: PaymentSettlementState;
|
|
398
|
-
}
|
|
399
|
-
/** APPROVED means approval only, never evidence of a captured payment. */
|
|
400
|
-
declare function observePayPalOrder<T extends PayPalOrderRepresentation>(raw: T): PayPalOrderObservation<T>;
|
|
401
|
-
interface PayPalCaptureObservation {
|
|
402
|
-
raw: Record<string, unknown>;
|
|
403
|
-
id: string;
|
|
404
|
-
failureReason?: string;
|
|
405
|
-
}
|
|
406
|
-
type PayPalWebhookObservation = {
|
|
407
|
-
raw: Record<string, unknown>;
|
|
408
|
-
type: string;
|
|
409
|
-
kind: 'capture-succeeded' | 'capture-failed';
|
|
410
|
-
capture: PayPalCaptureObservation;
|
|
411
|
-
} | {
|
|
412
|
-
raw: Record<string, unknown>;
|
|
413
|
-
type: string;
|
|
414
|
-
kind: 'unknown';
|
|
415
|
-
};
|
|
416
|
-
/** Decode only after signature verification; this performs no authentication or network call. */
|
|
417
|
-
declare function decodePayPalWebhookEvent(payload: string): PayPalWebhookObservation;
|
|
418
|
-
/** SDK order transport plus the OAuth-backed REST endpoints absent from the current SDK integration. */
|
|
419
|
-
declare class PayPalPaymentTransport {
|
|
420
|
-
private readonly options;
|
|
421
|
-
readonly orders: OrdersController;
|
|
422
|
-
private readonly baseUrl;
|
|
423
|
-
private readonly fetcher;
|
|
424
|
-
constructor(options: PayPalPaymentTransportOptions);
|
|
425
|
-
createOrder(input: Parameters<OrdersController['createOrder']>[0]): Promise<PayPalOrderObservation<_paypal_paypal_server_sdk.Order>>;
|
|
426
|
-
/** An explicit capture command; never invoked by getOrder or status observation. */
|
|
427
|
-
captureOrder(input: Parameters<OrdersController['captureOrder']>[0]): Promise<PayPalOrderObservation<_paypal_paypal_server_sdk.Order>>;
|
|
428
|
-
getOrder(input: Parameters<OrdersController['getOrder']>[0]): Promise<PayPalOrderObservation<_paypal_paypal_server_sdk.Order>>;
|
|
429
|
-
private getAccessToken;
|
|
430
|
-
verifyWebhookSignature(payload: string, headers: Record<string, string>, webhookId: string): Promise<boolean>;
|
|
431
|
-
refundCapture(captureId: string, request: PayPalCaptureRefundRequest): Promise<PayPalCaptureRefundResponse>;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
export { type CreateOrderParams, type CreatePaymentParams, DatabaseOrderPersistence, ORDER_PERSISTENCE, type Order, type OrderFailedEvent, OrderNotFoundError, type OrderPersistence, OrderService, type OrderStatus, PAYMENT_PROVIDERS, type PayPalCaptureObservation, type PayPalCaptureRefundRequest, type PayPalCaptureRefundResponse, type PayPalOrderObservation, type PayPalOrderRepresentation, PayPalPaymentTransport, type PayPalPaymentTransportOptions, type PayPalWebhookObservation, type PaymentEventType, PaymentEvents, type PaymentIntent, type PaymentModuleAsyncOptions, type PaymentModuleOptions, type PaymentProvider, type PaymentRecoverySentEvent, PaymentRecoveryService, type PaymentResult, PaymentService, type PaymentSettlementState, type PaymentStatus, RECOVERY_ATTEMPT_STORE, RECOVERY_MAIL_SENDER, RECOVERY_URL_GENERATOR, RECOVERY_USER_LOOKUP, type RecoveryAttemptStore, type RecoveryEmailParams, type RecoveryMailSender, type RecoveryOptions, type RecoveryUrlGenerator, type RecoveryUserLookup, type RefundResult, type StripeCheckoutObservation, type StripePaymentIntentObservation, type Subscription, type SubscriptionPlan, type SubscriptionProvider, type WebhookEvent, ZuckerPaymentModule, createStripeCheckoutSession, createStripeClient, decodePayPalWebhookEvent, friendlyErrorReason, observePayPalOrder, observeStripeCheckoutSession, observeStripePaymentIntent, retrieveStripeCheckoutSession, retrieveStripePaymentIntent, stripeResourceId, verifyStripeWebhookEvent };
|
|
686
|
+
export { type AdapterProbe, type ContractCheck, type CreateOrderParams, type CreatePaymentInput, DatabaseOrderPersistence, type FakeAdapterOptions, type Money, ORDER_PERSISTENCE, type Order, type OrderFailedEvent, OrderNotFoundError, type OrderPersistence, OrderService, type OrderStatus, PAYMENT_PROVIDERS, PAYPAL_SUPPORTED_CURRENCIES, type PayPalAdapterOptions, type PayPalCaptureObservation, type PayPalCaptureRefundRequest, type PayPalCaptureRefundResponse, type PayPalMoneyReturnedObservation, type PayPalOrderObservation, type PayPalOrderRepresentation, PayPalPaymentTransport, type PayPalPaymentTransportOptions, type PayPalWebhookObservation, type PaymentEventType, PaymentEvents, type PaymentModuleAsyncOptions, type PaymentModuleOptions, type PaymentNextAction, type PaymentProviderAdapter, PaymentProviderRegistry, type PaymentRecoverySentEvent, PaymentRecoveryService, type PaymentSession, type PaymentSettlementState, RECOVERY_ATTEMPT_STORE, RECOVERY_MAIL_SENDER, RECOVERY_URL_GENERATOR, RECOVERY_USER_LOOKUP, type RecoveryAttemptStore, type RecoveryEmailParams, type RecoveryMailSender, type RecoveryOptions, type RecoveryUrlGenerator, type RecoveryUserLookup, type RefundInput, SETTLEMENT_STATES, type SettlementDecision, type SettlementState, type StripeAdapterOptions, type StripeCheckoutObservation, type StripePaymentIntentObservation, type WebhookObservation, type WebhookRequest, ZuckerPaymentModule, adapterSupportsCurrency, checkPaymentAdapterContract, createFakeAdapter, createPayPalAdapter, createStripeAdapter, createStripeCheckoutSession, createStripeClient, currencyExponent, decodePayPalMoneyReturnedEvent, decodePayPalWebhookEvent, friendlyErrorReason, fromMinorUnits, isMoneyMoved, isTerminal, observePayPalOrder, observePayPalState, observeStripeCheckoutSession, observeStripePaymentIntent, observeStripeState, reconcile, retrieveStripeCheckoutSession, retrieveStripePaymentIntent, stripeResourceId, toMinorUnits, verifyStripeWebhookEvent };
|