@zucker-framework/payment 1.0.2 → 1.0.5
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 +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,150 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
-
// src/payment.
|
|
4
|
+
// src/contract/payment-contract.ts
|
|
5
|
+
function adapterSupportsCurrency(adapter, currency) {
|
|
6
|
+
return !adapter.supportedCurrencies || adapter.supportedCurrencies.includes(currency.toUpperCase());
|
|
7
|
+
}
|
|
8
|
+
__name(adapterSupportsCurrency, "adapterSupportsCurrency");
|
|
9
|
+
|
|
10
|
+
// src/contract/settlement.ts
|
|
11
|
+
var SETTLEMENT_STATES = [
|
|
12
|
+
/** 已创建,用户尚未授权 */
|
|
13
|
+
"created",
|
|
14
|
+
/** 用户已授权但尚未扣款(PayPal APPROVED / Stripe requires_capture) */
|
|
15
|
+
"authorized",
|
|
16
|
+
/** 已扣款 */
|
|
17
|
+
"captured",
|
|
18
|
+
/** 已全额退款 */
|
|
19
|
+
"refunded",
|
|
20
|
+
/** 已部分退款 */
|
|
21
|
+
"partially_refunded",
|
|
22
|
+
/** 终态失败 */
|
|
23
|
+
"failed",
|
|
24
|
+
/** 终态取消(用户放弃 / 商户撤单) */
|
|
25
|
+
"canceled",
|
|
26
|
+
/**
|
|
27
|
+
* 无法判定。
|
|
28
|
+
* 不是"失败",也不是一个可持久化的终态——查询异常、供应商返回中间态、
|
|
29
|
+
* 回调迟到都会落到这里,必须保留原状继续观察。
|
|
30
|
+
*/
|
|
31
|
+
"unknown"
|
|
32
|
+
];
|
|
33
|
+
var MONEY_MOVED = /* @__PURE__ */ new Set([
|
|
34
|
+
"captured",
|
|
35
|
+
"refunded",
|
|
36
|
+
"partially_refunded"
|
|
37
|
+
]);
|
|
38
|
+
var TERMINAL = /* @__PURE__ */ new Set([
|
|
39
|
+
"refunded",
|
|
40
|
+
"failed",
|
|
41
|
+
"canceled"
|
|
42
|
+
]);
|
|
43
|
+
function isMoneyMoved(state) {
|
|
44
|
+
return MONEY_MOVED.has(state);
|
|
45
|
+
}
|
|
46
|
+
__name(isMoneyMoved, "isMoneyMoved");
|
|
47
|
+
function isTerminal(state) {
|
|
48
|
+
return TERMINAL.has(state);
|
|
49
|
+
}
|
|
50
|
+
__name(isTerminal, "isTerminal");
|
|
51
|
+
function reconcile(current, observed) {
|
|
52
|
+
if (observed === "unknown") {
|
|
53
|
+
return {
|
|
54
|
+
next: current,
|
|
55
|
+
changed: false,
|
|
56
|
+
rejected: "not-observable"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (observed === current) {
|
|
60
|
+
return {
|
|
61
|
+
next: current,
|
|
62
|
+
changed: false
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (isMoneyMoved(current) && (observed === "failed" || observed === "canceled")) {
|
|
66
|
+
return {
|
|
67
|
+
next: current,
|
|
68
|
+
changed: false,
|
|
69
|
+
rejected: "money-already-moved"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (current === "refunded") {
|
|
73
|
+
return {
|
|
74
|
+
next: current,
|
|
75
|
+
changed: false,
|
|
76
|
+
rejected: "already-terminal"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (current === "partially_refunded" && observed === "captured") {
|
|
80
|
+
return {
|
|
81
|
+
next: current,
|
|
82
|
+
changed: false,
|
|
83
|
+
rejected: "already-terminal"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (isTerminal(current) && !isMoneyMoved(observed)) {
|
|
87
|
+
return {
|
|
88
|
+
next: current,
|
|
89
|
+
changed: false,
|
|
90
|
+
rejected: "already-terminal"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
next: observed,
|
|
95
|
+
changed: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
__name(reconcile, "reconcile");
|
|
99
|
+
|
|
100
|
+
// src/contract/money.ts
|
|
101
|
+
var ZERO_DECIMAL = /* @__PURE__ */ new Set([
|
|
102
|
+
"BIF",
|
|
103
|
+
"CLP",
|
|
104
|
+
"DJF",
|
|
105
|
+
"GNF",
|
|
106
|
+
"JPY",
|
|
107
|
+
"KMF",
|
|
108
|
+
"KRW",
|
|
109
|
+
"MGA",
|
|
110
|
+
"PYG",
|
|
111
|
+
"RWF",
|
|
112
|
+
"UGX",
|
|
113
|
+
"VND",
|
|
114
|
+
"VUV",
|
|
115
|
+
"XAF",
|
|
116
|
+
"XOF",
|
|
117
|
+
"XPF"
|
|
118
|
+
]);
|
|
119
|
+
function currencyExponent(currency) {
|
|
120
|
+
return ZERO_DECIMAL.has(currency.toUpperCase()) ? 0 : 2;
|
|
121
|
+
}
|
|
122
|
+
__name(currencyExponent, "currencyExponent");
|
|
123
|
+
function toMinorUnits(amount, currency) {
|
|
124
|
+
const exponent = currencyExponent(currency);
|
|
125
|
+
const match = /^(-?)(\d+)(?:\.(\d*))?$/.exec(amount.trim());
|
|
126
|
+
if (!match) throw new Error(`\u91D1\u989D\u683C\u5F0F\u975E\u6CD5\uFF1A${amount}`);
|
|
127
|
+
const [, sign, whole, fraction = ""] = match;
|
|
128
|
+
if (fraction.length > exponent && /[^0]/.test(fraction.slice(exponent))) {
|
|
129
|
+
throw new Error(`${currency} \u6700\u591A\u652F\u6301 ${exponent} \u4F4D\u5C0F\u6570\uFF0C\u6536\u5230 ${amount}`);
|
|
130
|
+
}
|
|
131
|
+
const padded = (fraction + "0".repeat(exponent)).slice(0, exponent);
|
|
132
|
+
const value = Number(`${whole}${padded}`);
|
|
133
|
+
if (!Number.isSafeInteger(value)) throw new Error(`\u91D1\u989D\u8D85\u51FA\u5B89\u5168\u6574\u6570\u8303\u56F4\uFF1A${amount}`);
|
|
134
|
+
return sign === "-" ? -value : value;
|
|
135
|
+
}
|
|
136
|
+
__name(toMinorUnits, "toMinorUnits");
|
|
137
|
+
function fromMinorUnits(value, currency) {
|
|
138
|
+
if (!Number.isInteger(value)) throw new Error(`\u6700\u5C0F\u5355\u4F4D\u5FC5\u987B\u662F\u6574\u6570\uFF1A${value}`);
|
|
139
|
+
const exponent = currencyExponent(currency);
|
|
140
|
+
const sign = value < 0 ? "-" : "";
|
|
141
|
+
const digits = String(Math.abs(value)).padStart(exponent + 1, "0");
|
|
142
|
+
if (exponent === 0) return `${sign}${digits}`;
|
|
143
|
+
return `${sign}${digits.slice(0, -exponent)}.${digits.slice(-exponent)}`;
|
|
144
|
+
}
|
|
145
|
+
__name(fromMinorUnits, "fromMinorUnits");
|
|
146
|
+
|
|
147
|
+
// src/contract/registry.ts
|
|
5
148
|
import { Injectable, Logger } from "@nestjs/common";
|
|
6
149
|
function _ts_decorate(decorators, target, key, desc) {
|
|
7
150
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -17,73 +160,585 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
17
160
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
18
161
|
}
|
|
19
162
|
__name(_ts_decorate, "_ts_decorate");
|
|
20
|
-
|
|
163
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
164
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
165
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
169
|
+
var PaymentProviderRegistry = class _PaymentProviderRegistry {
|
|
21
170
|
static {
|
|
22
|
-
__name(this, "
|
|
23
|
-
}
|
|
24
|
-
logger = new Logger(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const provider = this.getProvider(providerId);
|
|
57
|
-
this.logger.log(`[${providerId}] \u521B\u5EFA\u652F\u4ED8\u610F\u56FE: ${params.amount} ${params.currency}`);
|
|
58
|
-
return provider.createPaymentIntent(params);
|
|
59
|
-
}
|
|
60
|
-
/** 确认支付 */
|
|
61
|
-
async confirmPayment(providerId, intentId) {
|
|
62
|
-
const provider = this.getProvider(providerId);
|
|
63
|
-
this.logger.log(`[${providerId}] \u786E\u8BA4\u652F\u4ED8: ${intentId}`);
|
|
64
|
-
return provider.confirmPayment(intentId);
|
|
65
|
-
}
|
|
66
|
-
/** 退款 */
|
|
67
|
-
async refund(providerId, paymentId, amount, reason) {
|
|
68
|
-
const provider = this.getProvider(providerId);
|
|
69
|
-
this.logger.log(`[${providerId}] \u9000\u6B3E: ${paymentId}, amount=${amount ?? "full"}`);
|
|
70
|
-
return provider.refund(paymentId, amount, reason);
|
|
71
|
-
}
|
|
72
|
-
/** 查询支付状态 */
|
|
73
|
-
async getPaymentStatus(providerId, intentId) {
|
|
74
|
-
const provider = this.getProvider(providerId);
|
|
75
|
-
return provider.getPaymentStatus(intentId);
|
|
76
|
-
}
|
|
77
|
-
/** 处理 Webhook */
|
|
78
|
-
async handleWebhook(providerId, payload, signature) {
|
|
79
|
-
const provider = this.getProvider(providerId);
|
|
80
|
-
this.logger.log(`[${providerId}] \u5904\u7406 Webhook`);
|
|
81
|
-
return provider.handleWebhook(payload, signature);
|
|
171
|
+
__name(this, "PaymentProviderRegistry");
|
|
172
|
+
}
|
|
173
|
+
logger = new Logger(_PaymentProviderRegistry.name);
|
|
174
|
+
adapters = /* @__PURE__ */ new Map();
|
|
175
|
+
constructor(adapters = []) {
|
|
176
|
+
for (const adapter of adapters) this.register(adapter);
|
|
177
|
+
}
|
|
178
|
+
register(adapter) {
|
|
179
|
+
if (this.adapters.has(adapter.id)) {
|
|
180
|
+
throw new Error(`\u6E20\u9053 "${adapter.id}" \u5DF2\u6CE8\u518C\uFF1B\u91CD\u590D\u6CE8\u518C\u901A\u5E38\u610F\u5473\u7740\u88C5\u914D\u9519\u8BEF\uFF0C\u4E0D\u4E88\u9759\u9ED8\u8986\u76D6`);
|
|
181
|
+
}
|
|
182
|
+
this.adapters.set(adapter.id, adapter);
|
|
183
|
+
this.logger.log(`\u5DF2\u6CE8\u518C\u652F\u4ED8\u6E20\u9053 "${adapter.id}"`);
|
|
184
|
+
}
|
|
185
|
+
has(id) {
|
|
186
|
+
return this.adapters.has(id);
|
|
187
|
+
}
|
|
188
|
+
get(id) {
|
|
189
|
+
const adapter = this.adapters.get(id);
|
|
190
|
+
if (!adapter) {
|
|
191
|
+
throw new Error(`\u6E20\u9053 "${id}" \u672A\u6CE8\u518C\u3002\u5DF2\u6CE8\u518C\uFF1A[${[
|
|
192
|
+
...this.adapters.keys()
|
|
193
|
+
].join(", ")}]`);
|
|
194
|
+
}
|
|
195
|
+
return adapter;
|
|
196
|
+
}
|
|
197
|
+
list() {
|
|
198
|
+
return [
|
|
199
|
+
...this.adapters.values()
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
/** 能处理该币种的渠道;"本产品卖不卖这个币种"由上层决定,不在这里过滤。 */
|
|
203
|
+
listForCurrency(currency) {
|
|
204
|
+
return this.list().filter((adapter) => adapterSupportsCurrency(adapter, currency));
|
|
82
205
|
}
|
|
83
206
|
};
|
|
84
|
-
|
|
85
|
-
Injectable()
|
|
86
|
-
|
|
207
|
+
PaymentProviderRegistry = _ts_decorate([
|
|
208
|
+
Injectable(),
|
|
209
|
+
_ts_metadata("design:type", Function),
|
|
210
|
+
_ts_metadata("design:paramtypes", [
|
|
211
|
+
Object
|
|
212
|
+
])
|
|
213
|
+
], PaymentProviderRegistry);
|
|
214
|
+
|
|
215
|
+
// src/contract/adapters/paypal.adapter.ts
|
|
216
|
+
var CAPTURE_INTENT = "CAPTURE";
|
|
217
|
+
var PAY_NOW = "PAY_NOW";
|
|
218
|
+
var NO_SHIPPING = "NO_SHIPPING";
|
|
219
|
+
var PAYPAL_SUPPORTED_CURRENCIES = [
|
|
220
|
+
"AUD",
|
|
221
|
+
"BRL",
|
|
222
|
+
"CAD",
|
|
223
|
+
"CNY",
|
|
224
|
+
"CZK",
|
|
225
|
+
"DKK",
|
|
226
|
+
"EUR",
|
|
227
|
+
"HKD",
|
|
228
|
+
"HUF",
|
|
229
|
+
"ILS",
|
|
230
|
+
"JPY",
|
|
231
|
+
"MYR",
|
|
232
|
+
"MXN",
|
|
233
|
+
"TWD",
|
|
234
|
+
"NZD",
|
|
235
|
+
"NOK",
|
|
236
|
+
"PHP",
|
|
237
|
+
"PLN",
|
|
238
|
+
"GBP",
|
|
239
|
+
"RUB",
|
|
240
|
+
"SGD",
|
|
241
|
+
"SEK",
|
|
242
|
+
"CHF",
|
|
243
|
+
"THB",
|
|
244
|
+
"USD"
|
|
245
|
+
];
|
|
246
|
+
function observePayPalState(order) {
|
|
247
|
+
const captures = order.purchaseUnits?.[0]?.payments?.captures ?? [];
|
|
248
|
+
switch (order.status) {
|
|
249
|
+
case "CREATED":
|
|
250
|
+
case "SAVED":
|
|
251
|
+
case "PAYER_ACTION_REQUIRED":
|
|
252
|
+
return "created";
|
|
253
|
+
case "APPROVED":
|
|
254
|
+
return "authorized";
|
|
255
|
+
case "VOIDED":
|
|
256
|
+
return "canceled";
|
|
257
|
+
case "COMPLETED": {
|
|
258
|
+
if (captures.some((capture) => capture.status === "COMPLETED")) return "captured";
|
|
259
|
+
if (captures.some((c) => c.status === "DECLINED" || c.status === "FAILED")) return "failed";
|
|
260
|
+
return "authorized";
|
|
261
|
+
}
|
|
262
|
+
default:
|
|
263
|
+
return "unknown";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
__name(observePayPalState, "observePayPalState");
|
|
267
|
+
function createPayPalAdapter(options) {
|
|
268
|
+
const { transport } = options;
|
|
269
|
+
const toSession = /* @__PURE__ */ __name((order) => {
|
|
270
|
+
const unit = order.purchaseUnits?.[0];
|
|
271
|
+
const approve = order.links?.find((link) => link.rel === "approve")?.href;
|
|
272
|
+
return {
|
|
273
|
+
providerRef: order.id ?? "",
|
|
274
|
+
nextAction: approve ? {
|
|
275
|
+
kind: "redirect",
|
|
276
|
+
url: approve
|
|
277
|
+
} : {
|
|
278
|
+
kind: "none"
|
|
279
|
+
},
|
|
280
|
+
state: observePayPalState(order),
|
|
281
|
+
money: {
|
|
282
|
+
amount: unit?.amount?.value ?? "0",
|
|
283
|
+
currency: unit?.amount?.currencyCode ?? ""
|
|
284
|
+
},
|
|
285
|
+
raw: order
|
|
286
|
+
};
|
|
287
|
+
}, "toSession");
|
|
288
|
+
return {
|
|
289
|
+
id: "paypal",
|
|
290
|
+
supportedCurrencies: PAYPAL_SUPPORTED_CURRENCIES,
|
|
291
|
+
async createPayment(input2) {
|
|
292
|
+
const payment = await transport.createOrder({
|
|
293
|
+
body: {
|
|
294
|
+
intent: CAPTURE_INTENT,
|
|
295
|
+
purchaseUnits: [
|
|
296
|
+
{
|
|
297
|
+
referenceId: input2.reference,
|
|
298
|
+
amount: {
|
|
299
|
+
currencyCode: input2.money.currency,
|
|
300
|
+
// 原样透传十进制字符串,不经过 Number
|
|
301
|
+
value: input2.money.amount
|
|
302
|
+
},
|
|
303
|
+
description: input2.description
|
|
304
|
+
}
|
|
305
|
+
],
|
|
306
|
+
applicationContext: {
|
|
307
|
+
returnUrl: input2.returnUrl,
|
|
308
|
+
cancelUrl: input2.cancelUrl,
|
|
309
|
+
userAction: PAY_NOW,
|
|
310
|
+
shippingPreference: NO_SHIPPING,
|
|
311
|
+
brandName: options.brandName
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
// 幂等:同一个键重试,PayPal 返回同一笔订单
|
|
315
|
+
paypalRequestId: input2.idempotencyKey,
|
|
316
|
+
prefer: "return=representation"
|
|
317
|
+
});
|
|
318
|
+
return toSession(payment.raw);
|
|
319
|
+
},
|
|
320
|
+
async capture(providerRef, idempotencyKey) {
|
|
321
|
+
const payment = await transport.captureOrder({
|
|
322
|
+
id: providerRef,
|
|
323
|
+
paypalRequestId: idempotencyKey,
|
|
324
|
+
prefer: "return=representation"
|
|
325
|
+
});
|
|
326
|
+
return toSession(payment.raw);
|
|
327
|
+
},
|
|
328
|
+
/** 只读。对账会反复调用它,绝不能在这里触发 captureOrder。 */
|
|
329
|
+
async retrieve(providerRef) {
|
|
330
|
+
const payment = await transport.getOrder({
|
|
331
|
+
id: providerRef
|
|
332
|
+
});
|
|
333
|
+
return toSession(payment.raw);
|
|
334
|
+
},
|
|
335
|
+
async refund(input2) {
|
|
336
|
+
const refund = await transport.refundCapture(input2.providerRef, {
|
|
337
|
+
...input2.money ? {
|
|
338
|
+
amount: {
|
|
339
|
+
value: input2.money.amount,
|
|
340
|
+
currency_code: input2.money.currency
|
|
341
|
+
}
|
|
342
|
+
} : {},
|
|
343
|
+
note_to_payer: input2.reason
|
|
344
|
+
}, input2.idempotencyKey);
|
|
345
|
+
return {
|
|
346
|
+
refundRef: refund.id ?? "",
|
|
347
|
+
state: refund.status === "COMPLETED" ? "refunded" : "unknown"
|
|
348
|
+
};
|
|
349
|
+
},
|
|
350
|
+
async parseWebhook(request) {
|
|
351
|
+
if (!options.webhookId) {
|
|
352
|
+
throw new Error("PayPal webhookId is not configured; refusing to trust the request");
|
|
353
|
+
}
|
|
354
|
+
const body = typeof request.rawBody === "string" ? request.rawBody : new TextDecoder().decode(request.rawBody);
|
|
355
|
+
const valid = await transport.verifyWebhookSignature(body, request.headers, options.webhookId);
|
|
356
|
+
if (!valid) throw new Error("PayPal webhook signature verification failed");
|
|
357
|
+
const event = JSON.parse(body);
|
|
358
|
+
const type = event.event_type ?? "unknown";
|
|
359
|
+
const providerRef = event.resource?.supplementary_data?.related_ids?.order_id;
|
|
360
|
+
const state = type === "PAYMENT.CAPTURE.COMPLETED" ? "captured" : type === "PAYMENT.CAPTURE.DENIED" || type === "PAYMENT.CAPTURE.DECLINED" ? "failed" : type === "PAYMENT.CAPTURE.REFUNDED" ? "refunded" : type === "PAYMENT.CAPTURE.REVERSED" ? "refunded" : "unknown";
|
|
361
|
+
return {
|
|
362
|
+
type,
|
|
363
|
+
providerRef,
|
|
364
|
+
state,
|
|
365
|
+
raw: event
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
__name(createPayPalAdapter, "createPayPalAdapter");
|
|
371
|
+
|
|
372
|
+
// src/providers/stripe.ts
|
|
373
|
+
import { createRequire } from "module";
|
|
374
|
+
var requireSdk = createRequire(typeof __filename === "string" ? __filename : import.meta.url);
|
|
375
|
+
function createStripeClient(secretKey, options) {
|
|
376
|
+
const StripeSdk = requireSdk("stripe");
|
|
377
|
+
return new StripeSdk(secretKey, options);
|
|
378
|
+
}
|
|
379
|
+
__name(createStripeClient, "createStripeClient");
|
|
380
|
+
function verifyStripeWebhookEvent(client, payload, signature, webhookSecret) {
|
|
381
|
+
return client.webhooks.constructEvent(payload, signature, webhookSecret);
|
|
382
|
+
}
|
|
383
|
+
__name(verifyStripeWebhookEvent, "verifyStripeWebhookEvent");
|
|
384
|
+
function stripeResourceId(value) {
|
|
385
|
+
return typeof value === "string" ? value : value?.id;
|
|
386
|
+
}
|
|
387
|
+
__name(stripeResourceId, "stripeResourceId");
|
|
388
|
+
function observeStripeCheckoutSession(raw) {
|
|
389
|
+
return {
|
|
390
|
+
raw,
|
|
391
|
+
id: raw.id,
|
|
392
|
+
url: raw.url || void 0,
|
|
393
|
+
clientReferenceId: raw.client_reference_id || void 0,
|
|
394
|
+
paymentIntentId: stripeResourceId(raw.payment_intent),
|
|
395
|
+
subscriptionId: stripeResourceId(raw.subscription),
|
|
396
|
+
settlement: raw.payment_status === "paid" ? "paid" : raw.status === "expired" ? "unpaid" : "unknown"
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
__name(observeStripeCheckoutSession, "observeStripeCheckoutSession");
|
|
400
|
+
async function createStripeCheckoutSession(client, params, options) {
|
|
401
|
+
return observeStripeCheckoutSession(options ? await client.checkout.sessions.create(params, options) : await client.checkout.sessions.create(params));
|
|
402
|
+
}
|
|
403
|
+
__name(createStripeCheckoutSession, "createStripeCheckoutSession");
|
|
404
|
+
async function retrieveStripeCheckoutSession(client, id) {
|
|
405
|
+
return observeStripeCheckoutSession(await client.checkout.sessions.retrieve(id));
|
|
406
|
+
}
|
|
407
|
+
__name(retrieveStripeCheckoutSession, "retrieveStripeCheckoutSession");
|
|
408
|
+
function observeStripePaymentIntent(raw) {
|
|
409
|
+
const charge = typeof raw.latest_charge === "object" ? raw.latest_charge : null;
|
|
410
|
+
return {
|
|
411
|
+
raw,
|
|
412
|
+
id: raw.id,
|
|
413
|
+
chargeId: stripeResourceId(raw.latest_charge) ?? null,
|
|
414
|
+
paymentMethodType: charge?.payment_method_details?.type,
|
|
415
|
+
failureCode: raw.last_payment_error?.code,
|
|
416
|
+
failureMessage: raw.last_payment_error?.message,
|
|
417
|
+
settlement: raw.status === "succeeded" ? "paid" : raw.status === "canceled" ? "unpaid" : "unknown"
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
__name(observeStripePaymentIntent, "observeStripePaymentIntent");
|
|
421
|
+
async function retrieveStripePaymentIntent(client, id, params) {
|
|
422
|
+
const raw = params ? await client.paymentIntents.retrieve(id, params) : await client.paymentIntents.retrieve(id);
|
|
423
|
+
return observeStripePaymentIntent(raw);
|
|
424
|
+
}
|
|
425
|
+
__name(retrieveStripePaymentIntent, "retrieveStripePaymentIntent");
|
|
426
|
+
|
|
427
|
+
// src/contract/adapters/stripe.adapter.ts
|
|
428
|
+
function observeStripeState(session) {
|
|
429
|
+
if (session.payment_status === "paid" || session.payment_status === "no_payment_required") {
|
|
430
|
+
return "captured";
|
|
431
|
+
}
|
|
432
|
+
if (session.status === "expired") return "canceled";
|
|
433
|
+
if (session.status === "open") return "created";
|
|
434
|
+
return "unknown";
|
|
435
|
+
}
|
|
436
|
+
__name(observeStripeState, "observeStripeState");
|
|
437
|
+
function createStripeAdapter(options) {
|
|
438
|
+
const { client } = options;
|
|
439
|
+
const toSession = /* @__PURE__ */ __name((raw) => ({
|
|
440
|
+
providerRef: raw.id,
|
|
441
|
+
nextAction: raw.url ? {
|
|
442
|
+
kind: "redirect",
|
|
443
|
+
url: raw.url
|
|
444
|
+
} : {
|
|
445
|
+
kind: "none"
|
|
446
|
+
},
|
|
447
|
+
state: observeStripeState(raw),
|
|
448
|
+
money: {
|
|
449
|
+
amount: fromMinorUnits(raw.amount_total ?? 0, raw.currency ?? "usd"),
|
|
450
|
+
currency: (raw.currency ?? "").toUpperCase()
|
|
451
|
+
},
|
|
452
|
+
raw
|
|
453
|
+
}), "toSession");
|
|
454
|
+
return {
|
|
455
|
+
id: "stripe",
|
|
456
|
+
// Stripe 支持的币种远超本契约会用到的范围,不声明限制;
|
|
457
|
+
// 不受支持的币种由 Stripe 自己拒绝,胡乱穷举一份只会过期。
|
|
458
|
+
supportedCurrencies: void 0,
|
|
459
|
+
async createPayment(input2) {
|
|
460
|
+
const observation = await createStripeCheckoutSession(client, {
|
|
461
|
+
mode: "payment",
|
|
462
|
+
line_items: [
|
|
463
|
+
{
|
|
464
|
+
price_data: {
|
|
465
|
+
currency: input2.money.currency.toLowerCase(),
|
|
466
|
+
product_data: {
|
|
467
|
+
name: input2.description || input2.reference
|
|
468
|
+
},
|
|
469
|
+
unit_amount: toMinorUnits(input2.money.amount, input2.money.currency)
|
|
470
|
+
},
|
|
471
|
+
quantity: 1
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
success_url: input2.returnUrl,
|
|
475
|
+
cancel_url: input2.cancelUrl,
|
|
476
|
+
client_reference_id: input2.reference,
|
|
477
|
+
metadata: input2.metadata,
|
|
478
|
+
...options.paymentMethodConfiguration ? {
|
|
479
|
+
payment_method_configuration: options.paymentMethodConfiguration
|
|
480
|
+
} : {}
|
|
481
|
+
}, {
|
|
482
|
+
idempotencyKey: input2.idempotencyKey
|
|
483
|
+
});
|
|
484
|
+
return toSession(observation.raw);
|
|
485
|
+
},
|
|
486
|
+
/**
|
|
487
|
+
* Stripe Checkout 默认自动扣款,没有独立的 capture 步骤。
|
|
488
|
+
* 按契约仍需实现:返回当前真实状态即可,不得伪造成已扣款。
|
|
489
|
+
*/
|
|
490
|
+
async capture(providerRef) {
|
|
491
|
+
const observation = await retrieveStripeCheckoutSession(client, providerRef);
|
|
492
|
+
return toSession(observation.raw);
|
|
493
|
+
},
|
|
494
|
+
async retrieve(providerRef) {
|
|
495
|
+
const observation = await retrieveStripeCheckoutSession(client, providerRef);
|
|
496
|
+
return toSession(observation.raw);
|
|
497
|
+
},
|
|
498
|
+
async refund(input2) {
|
|
499
|
+
const session = await retrieveStripeCheckoutSession(client, input2.providerRef);
|
|
500
|
+
const paymentIntent = session.paymentIntentId;
|
|
501
|
+
if (!paymentIntent) throw new Error("\u8BE5 Checkout \u4F1A\u8BDD\u6CA1\u6709\u53EF\u9000\u6B3E\u7684 PaymentIntent");
|
|
502
|
+
const refund = await client.refunds.create({
|
|
503
|
+
payment_intent: paymentIntent,
|
|
504
|
+
...input2.money ? {
|
|
505
|
+
amount: toMinorUnits(input2.money.amount, input2.money.currency)
|
|
506
|
+
} : {}
|
|
507
|
+
}, {
|
|
508
|
+
idempotencyKey: input2.idempotencyKey
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
refundRef: refund.id,
|
|
512
|
+
state: refund.status === "succeeded" ? "refunded" : "unknown"
|
|
513
|
+
};
|
|
514
|
+
},
|
|
515
|
+
async parseWebhook(request) {
|
|
516
|
+
if (!options.webhookSecret) {
|
|
517
|
+
throw new Error("Stripe webhookSecret is not configured; refusing to trust the request");
|
|
518
|
+
}
|
|
519
|
+
const signature = request.headers["stripe-signature"];
|
|
520
|
+
if (!signature) throw new Error("Stripe webhook is missing the stripe-signature header");
|
|
521
|
+
const event = verifyStripeWebhookEvent(client, typeof request.rawBody === "string" ? request.rawBody : Buffer.from(request.rawBody), signature, options.webhookSecret);
|
|
522
|
+
const object = event.data.object;
|
|
523
|
+
const state = event.type === "checkout.session.completed" ? observeStripeState(event.data.object) : event.type === "checkout.session.expired" ? "canceled" : event.type === "charge.refunded" ? "refunded" : "unknown";
|
|
524
|
+
return {
|
|
525
|
+
type: event.type,
|
|
526
|
+
providerRef: object.id,
|
|
527
|
+
state,
|
|
528
|
+
raw: event
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
__name(createStripeAdapter, "createStripeAdapter");
|
|
534
|
+
|
|
535
|
+
// src/contract/contract-suite.ts
|
|
536
|
+
var MUTATING = [
|
|
537
|
+
"capture",
|
|
538
|
+
"refund",
|
|
539
|
+
"cancel"
|
|
540
|
+
];
|
|
541
|
+
async function checkPaymentAdapterContract(probe) {
|
|
542
|
+
const checks = [];
|
|
543
|
+
const captureNames = probe.captureCallNames ?? MUTATING;
|
|
544
|
+
const run = /* @__PURE__ */ __name(async (name, fn) => {
|
|
545
|
+
try {
|
|
546
|
+
const detail = await fn();
|
|
547
|
+
checks.push({
|
|
548
|
+
name,
|
|
549
|
+
ok: detail === void 0,
|
|
550
|
+
detail
|
|
551
|
+
});
|
|
552
|
+
} catch (error) {
|
|
553
|
+
checks.push({
|
|
554
|
+
name,
|
|
555
|
+
ok: false,
|
|
556
|
+
detail: `\u629B\u51FA\u5F02\u5E38\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}, "run");
|
|
560
|
+
await run("retrieve \u5FC5\u987B\u53EA\u8BFB", async () => {
|
|
561
|
+
const { adapter, mutatingCalls } = probe.create();
|
|
562
|
+
const created = await adapter.createPayment(input(probe.supportedCurrency));
|
|
563
|
+
const before = mutatingCalls().filter((c) => captureNames.includes(c)).length;
|
|
564
|
+
await adapter.retrieve(created.providerRef);
|
|
565
|
+
const after = mutatingCalls().filter((c) => captureNames.includes(c)).length;
|
|
566
|
+
return after > before ? `retrieve \u89E6\u53D1\u4E86\u5199\u64CD\u4F5C\uFF1A${mutatingCalls().join(", ")}` : void 0;
|
|
567
|
+
});
|
|
568
|
+
await run("\u76F8\u540C\u5E42\u7B49\u952E\u4E0D\u5F97\u4EA7\u751F\u7B2C\u4E8C\u7B14\u652F\u4ED8", async () => {
|
|
569
|
+
const { adapter } = probe.create();
|
|
570
|
+
const key = "contract-idempotency-key";
|
|
571
|
+
const first = await adapter.createPayment(input(probe.supportedCurrency, key));
|
|
572
|
+
const second = await adapter.createPayment(input(probe.supportedCurrency, key));
|
|
573
|
+
return first.providerRef === second.providerRef ? void 0 : `\u4EA7\u751F\u4E86\u4E24\u7B14\uFF1A${first.providerRef} \u4E0E ${second.providerRef}`;
|
|
574
|
+
});
|
|
575
|
+
await run("\u9A8C\u7B7E\u5931\u8D25\u5FC5\u987B\u629B\u9519\u800C\u975E\u8FD4\u56DE\u672A\u77E5\u4E8B\u4EF6", async () => {
|
|
576
|
+
const { adapter } = probe.create();
|
|
577
|
+
try {
|
|
578
|
+
const observation = await adapter.parseWebhook(probe.invalidWebhook);
|
|
579
|
+
return `\u672A\u629B\u9519\uFF0C\u8FD4\u56DE\u4E86 ${JSON.stringify(observation.type)}`;
|
|
580
|
+
} catch {
|
|
581
|
+
return void 0;
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
await run("\u91D1\u989D\u5FC5\u987B\u4EE5\u5341\u8FDB\u5236\u5B57\u7B26\u4E32\u539F\u6837\u900F\u4F20", async () => {
|
|
585
|
+
const { adapter } = probe.create();
|
|
586
|
+
const amount = "29.00";
|
|
587
|
+
const created = await adapter.createPayment({
|
|
588
|
+
...input(probe.supportedCurrency),
|
|
589
|
+
money: {
|
|
590
|
+
amount,
|
|
591
|
+
currency: probe.supportedCurrency
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
return created.money.amount === amount ? void 0 : `\u671F\u671B "${amount}"\uFF0C\u5B9E\u9645 ${JSON.stringify(created.money.amount)}`;
|
|
595
|
+
});
|
|
596
|
+
await run("\u5728\u9014\u652F\u4ED8\u4E0D\u5F97\u88AB\u5224\u4E3A failed", async () => {
|
|
597
|
+
const { adapter } = probe.create();
|
|
598
|
+
const created = await adapter.createPayment(input(probe.supportedCurrency));
|
|
599
|
+
const retrieved = await adapter.retrieve(created.providerRef);
|
|
600
|
+
return retrieved.state === "failed" ? "\u521A\u521B\u5EFA\u3001\u7528\u6237\u5C1A\u672A\u64CD\u4F5C\u7684\u652F\u4ED8\u88AB\u5224\u4E3A failed" : void 0;
|
|
601
|
+
});
|
|
602
|
+
await run("nextAction \u5FC5\u987B\u662F\u5951\u7EA6\u5185\u7684 kind", async () => {
|
|
603
|
+
const { adapter } = probe.create();
|
|
604
|
+
const created = await adapter.createPayment(input(probe.supportedCurrency));
|
|
605
|
+
const known = [
|
|
606
|
+
"redirect",
|
|
607
|
+
"qrcode",
|
|
608
|
+
"form_post",
|
|
609
|
+
"sdk_params",
|
|
610
|
+
"none"
|
|
611
|
+
];
|
|
612
|
+
return known.includes(created.nextAction.kind) ? void 0 : `\u672A\u77E5 kind\uFF1A${created.nextAction.kind}`;
|
|
613
|
+
});
|
|
614
|
+
await run("\u5E01\u79CD\u80FD\u529B\u58F0\u660E\u4E0E\u5B9E\u9645\u4E00\u81F4", async () => {
|
|
615
|
+
const { adapter } = probe.create();
|
|
616
|
+
if (!adapter.supportedCurrencies) return void 0;
|
|
617
|
+
if (!adapterSupportsCurrency(adapter, probe.supportedCurrency)) {
|
|
618
|
+
return `\u58F0\u660E\u4E0D\u652F\u6301 ${probe.supportedCurrency}\uFF0C\u4F46\u63A2\u9488\u79F0\u5176\u652F\u6301`;
|
|
619
|
+
}
|
|
620
|
+
if (probe.unsupportedCurrency && adapterSupportsCurrency(adapter, probe.unsupportedCurrency)) {
|
|
621
|
+
return `\u58F0\u660E\u652F\u6301 ${probe.unsupportedCurrency}\uFF0C\u4F46\u63A2\u9488\u79F0\u5176\u4E0D\u652F\u6301`;
|
|
622
|
+
}
|
|
623
|
+
return void 0;
|
|
624
|
+
});
|
|
625
|
+
return checks;
|
|
626
|
+
}
|
|
627
|
+
__name(checkPaymentAdapterContract, "checkPaymentAdapterContract");
|
|
628
|
+
function input(currency, idempotencyKey = "contract-default-key") {
|
|
629
|
+
return {
|
|
630
|
+
reference: "contract-order",
|
|
631
|
+
money: {
|
|
632
|
+
amount: "10.00",
|
|
633
|
+
currency
|
|
634
|
+
},
|
|
635
|
+
idempotencyKey
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
__name(input, "input");
|
|
639
|
+
|
|
640
|
+
// src/contract/fake-adapter.ts
|
|
641
|
+
function createFakeAdapter(options = {}) {
|
|
642
|
+
const defects = options.defects ?? {};
|
|
643
|
+
const payments = /* @__PURE__ */ new Map();
|
|
644
|
+
const byIdempotencyKey = /* @__PURE__ */ new Map();
|
|
645
|
+
const mutatingCalls = [];
|
|
646
|
+
let counter = 0;
|
|
647
|
+
const adapter = {
|
|
648
|
+
id: options.id ?? "fake",
|
|
649
|
+
supportedCurrencies: options.supportedCurrencies,
|
|
650
|
+
async createPayment(input2) {
|
|
651
|
+
mutatingCalls.push("createPayment");
|
|
652
|
+
const existing = defects.ignoreIdempotency ? void 0 : byIdempotencyKey.get(input2.idempotencyKey);
|
|
653
|
+
if (existing) {
|
|
654
|
+
const payment2 = payments.get(existing);
|
|
655
|
+
return session(payment2);
|
|
656
|
+
}
|
|
657
|
+
const ref = `fake_${++counter}`;
|
|
658
|
+
const amount = defects.mangleAmount ? String(Number(input2.money.amount)) : input2.money.amount;
|
|
659
|
+
const payment = {
|
|
660
|
+
ref,
|
|
661
|
+
state: "created",
|
|
662
|
+
amount,
|
|
663
|
+
currency: input2.money.currency
|
|
664
|
+
};
|
|
665
|
+
payments.set(ref, payment);
|
|
666
|
+
byIdempotencyKey.set(input2.idempotencyKey, ref);
|
|
667
|
+
return session(payment);
|
|
668
|
+
},
|
|
669
|
+
async capture(providerRef) {
|
|
670
|
+
mutatingCalls.push("capture");
|
|
671
|
+
const payment = expect(providerRef);
|
|
672
|
+
payment.state = "captured";
|
|
673
|
+
return session(payment);
|
|
674
|
+
},
|
|
675
|
+
async retrieve(providerRef) {
|
|
676
|
+
if (defects.retrieveCaptures) {
|
|
677
|
+
mutatingCalls.push("capture");
|
|
678
|
+
expect(providerRef).state = "captured";
|
|
679
|
+
}
|
|
680
|
+
const payment = expect(providerRef);
|
|
681
|
+
if (defects.guessesPendingAsFailed && payment.state === "created") {
|
|
682
|
+
return {
|
|
683
|
+
...session(payment),
|
|
684
|
+
state: "failed"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
return session(payment);
|
|
688
|
+
},
|
|
689
|
+
async refund(input2) {
|
|
690
|
+
mutatingCalls.push("refund");
|
|
691
|
+
const payment = expect(input2.providerRef);
|
|
692
|
+
payment.state = input2.money ? "partially_refunded" : "refunded";
|
|
693
|
+
return {
|
|
694
|
+
refundRef: `refund_${payment.ref}`,
|
|
695
|
+
state: payment.state
|
|
696
|
+
};
|
|
697
|
+
},
|
|
698
|
+
async parseWebhook(request) {
|
|
699
|
+
const valid = request.headers["x-fake-signature"] === "valid";
|
|
700
|
+
if (!valid && !defects.acceptsBadSignature) {
|
|
701
|
+
throw new Error("webhook signature verification failed");
|
|
702
|
+
}
|
|
703
|
+
const ref = request.headers["x-fake-ref"];
|
|
704
|
+
return {
|
|
705
|
+
type: "fake.payment.updated",
|
|
706
|
+
providerRef: ref,
|
|
707
|
+
state: ref && payments.has(ref) ? payments.get(ref).state : "unknown",
|
|
708
|
+
raw: {}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
function expect(ref) {
|
|
713
|
+
const payment = payments.get(ref);
|
|
714
|
+
if (!payment) throw new Error(`unknown payment ${ref}`);
|
|
715
|
+
return payment;
|
|
716
|
+
}
|
|
717
|
+
__name(expect, "expect");
|
|
718
|
+
function session(payment) {
|
|
719
|
+
return {
|
|
720
|
+
providerRef: payment.ref,
|
|
721
|
+
nextAction: {
|
|
722
|
+
kind: "redirect",
|
|
723
|
+
url: `https://fake.test/pay/${payment.ref}`
|
|
724
|
+
},
|
|
725
|
+
state: payment.state,
|
|
726
|
+
money: {
|
|
727
|
+
amount: payment.amount,
|
|
728
|
+
currency: payment.currency
|
|
729
|
+
},
|
|
730
|
+
raw: {}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
__name(session, "session");
|
|
734
|
+
return {
|
|
735
|
+
adapter,
|
|
736
|
+
mutatingCalls: /* @__PURE__ */ __name(() => [
|
|
737
|
+
...mutatingCalls
|
|
738
|
+
], "mutatingCalls")
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
__name(createFakeAdapter, "createFakeAdapter");
|
|
87
742
|
|
|
88
743
|
// src/order.service.ts
|
|
89
744
|
import { Injectable as Injectable2, Inject } from "@nestjs/common";
|
|
@@ -143,12 +798,12 @@ function _ts_decorate2(decorators, target, key, desc) {
|
|
|
143
798
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
144
799
|
}
|
|
145
800
|
__name(_ts_decorate2, "_ts_decorate");
|
|
146
|
-
function
|
|
801
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
147
802
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
148
803
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
149
804
|
}
|
|
150
805
|
}
|
|
151
|
-
__name(
|
|
806
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
152
807
|
function _ts_param(paramIndex, decorator) {
|
|
153
808
|
return function(target, key) {
|
|
154
809
|
decorator(target, key, paramIndex);
|
|
@@ -169,10 +824,10 @@ var OrderService = class {
|
|
|
169
824
|
static {
|
|
170
825
|
__name(this, "OrderService");
|
|
171
826
|
}
|
|
172
|
-
|
|
827
|
+
providers;
|
|
173
828
|
orders;
|
|
174
|
-
constructor(
|
|
175
|
-
this.
|
|
829
|
+
constructor(providers, orders) {
|
|
830
|
+
this.providers = providers;
|
|
176
831
|
this.orders = orders;
|
|
177
832
|
if (!orders) throw new Error("Order persistence must be configured");
|
|
178
833
|
}
|
|
@@ -189,8 +844,8 @@ var OrderService = class {
|
|
|
189
844
|
data: {
|
|
190
845
|
id: params.orderId,
|
|
191
846
|
providerId: params.providerId,
|
|
192
|
-
amount: params.payment.amount,
|
|
193
|
-
currency: params.payment.currency,
|
|
847
|
+
amount: params.payment.money.amount,
|
|
848
|
+
currency: params.payment.money.currency,
|
|
194
849
|
metadata: params.payment.metadata,
|
|
195
850
|
status: "created",
|
|
196
851
|
createdAt: now,
|
|
@@ -200,68 +855,99 @@ var OrderService = class {
|
|
|
200
855
|
return this.initiatePayment(order, params.payment);
|
|
201
856
|
}
|
|
202
857
|
async initiatePayment(order, payment) {
|
|
203
|
-
const
|
|
858
|
+
const session = await this.providers.get(order.providerId).createPayment(payment);
|
|
204
859
|
await this.transition(order.id, [
|
|
205
860
|
"created"
|
|
206
861
|
], "paying", {
|
|
207
|
-
paymentIntentId:
|
|
862
|
+
paymentIntentId: session.providerRef
|
|
208
863
|
});
|
|
209
864
|
return this.getOrder(order.id);
|
|
210
865
|
}
|
|
211
|
-
|
|
866
|
+
/**
|
|
867
|
+
* 扣款。幂等键由调用方给出——同一笔订单重试必须复用同一个键,否则渠道会认为是新支付。
|
|
868
|
+
* 判定不了的结果保持 confirming,交由只读对账解决,绝不在这里重复扣款。
|
|
869
|
+
*/
|
|
870
|
+
async confirmOrder(orderId, idempotencyKey) {
|
|
212
871
|
const order = await this.getOrder(orderId);
|
|
213
872
|
if (!order.paymentIntentId) throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentIntentId`);
|
|
214
873
|
await this.transition(orderId, [
|
|
215
874
|
"paying"
|
|
216
875
|
], "confirming");
|
|
217
|
-
const
|
|
218
|
-
|
|
876
|
+
const session = await this.providers.get(order.providerId).capture(order.paymentIntentId, idempotencyKey);
|
|
877
|
+
await this.applySettlement(orderId, "confirming", session.state, session.providerRef);
|
|
878
|
+
return session;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* 把渠道观察到的结算状态落到订单上。
|
|
882
|
+
* 迁移是否允许由 reconcile() 统一裁决,保证所有渠道遵守同一套安全性质
|
|
883
|
+
* (unknown 不覆盖已知状态、已收款不被迟到的失败回调回退)。
|
|
884
|
+
*/
|
|
885
|
+
async applySettlement(orderId, from, observed, paymentRef) {
|
|
886
|
+
const current = from === "paid" ? "captured" : from === "refunded" ? "refunded" : "created";
|
|
887
|
+
const decision = reconcile(current, observed);
|
|
888
|
+
if (!decision.changed) return;
|
|
889
|
+
if (decision.next === "captured") {
|
|
219
890
|
await this.transition(orderId, [
|
|
220
|
-
|
|
891
|
+
from
|
|
221
892
|
], "paid", {
|
|
222
|
-
paymentId:
|
|
223
|
-
paidAt:
|
|
893
|
+
paymentId: paymentRef,
|
|
894
|
+
paidAt: /* @__PURE__ */ new Date()
|
|
224
895
|
});
|
|
225
|
-
} else if (
|
|
896
|
+
} else if (decision.next === "failed" || decision.next === "canceled") {
|
|
226
897
|
await this.transition(orderId, [
|
|
227
|
-
|
|
898
|
+
from
|
|
228
899
|
], "failed");
|
|
229
|
-
} else if (
|
|
900
|
+
} else if (decision.next === "refunded" || decision.next === "partially_refunded") {
|
|
230
901
|
await this.transition(orderId, [
|
|
231
|
-
|
|
232
|
-
], "refunded"
|
|
902
|
+
from
|
|
903
|
+
], "refunded", {
|
|
904
|
+
refundedAt: /* @__PURE__ */ new Date()
|
|
905
|
+
});
|
|
233
906
|
}
|
|
234
|
-
return result;
|
|
235
907
|
}
|
|
236
|
-
async refundOrder(orderId,
|
|
908
|
+
async refundOrder(orderId, idempotencyKey, options = {}) {
|
|
237
909
|
const order = await this.getOrder(orderId);
|
|
238
910
|
if (!order.paymentId) throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentId`);
|
|
239
911
|
await this.transition(orderId, [
|
|
240
912
|
"paid"
|
|
241
913
|
], "refunding");
|
|
242
|
-
const result = await this.
|
|
243
|
-
|
|
914
|
+
const result = await this.providers.get(order.providerId).refund({
|
|
915
|
+
providerRef: order.paymentId,
|
|
916
|
+
money: options.money,
|
|
917
|
+
reason: options.reason,
|
|
918
|
+
idempotencyKey
|
|
919
|
+
});
|
|
920
|
+
if (result.state === "refunded") {
|
|
244
921
|
await this.transition(orderId, [
|
|
245
922
|
"refunding"
|
|
246
923
|
], "refunded", {
|
|
247
924
|
refundedAt: /* @__PURE__ */ new Date()
|
|
248
925
|
});
|
|
249
|
-
} else if (result.
|
|
926
|
+
} else if (result.state === "failed") {
|
|
250
927
|
await this.transition(orderId, [
|
|
251
928
|
"refunding"
|
|
252
929
|
], "paid");
|
|
253
930
|
}
|
|
254
931
|
return result;
|
|
255
932
|
}
|
|
256
|
-
|
|
933
|
+
/**
|
|
934
|
+
* 重试失败订单。
|
|
935
|
+
* idempotencyKey 必须与上一次不同:沿用旧键会让渠道原样返回那笔已失败的支付,
|
|
936
|
+
* 看起来"重试了"其实什么都没发生。
|
|
937
|
+
*/
|
|
938
|
+
async retryOrder(orderId, idempotencyKey) {
|
|
257
939
|
const order = await this.getOrder(orderId);
|
|
258
940
|
await this.transition(orderId, [
|
|
259
941
|
"failed"
|
|
260
942
|
], "created");
|
|
261
943
|
return this.initiatePayment(order, {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
944
|
+
reference: order.id,
|
|
945
|
+
money: {
|
|
946
|
+
amount: order.amount,
|
|
947
|
+
currency: order.currency
|
|
948
|
+
},
|
|
949
|
+
metadata: order.metadata,
|
|
950
|
+
idempotencyKey
|
|
265
951
|
});
|
|
266
952
|
}
|
|
267
953
|
async cancelOrder(orderId) {
|
|
@@ -300,9 +986,9 @@ var OrderService = class {
|
|
|
300
986
|
OrderService = _ts_decorate2([
|
|
301
987
|
Injectable2(),
|
|
302
988
|
_ts_param(1, Inject(ORDER_PERSISTENCE)),
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
typeof
|
|
989
|
+
_ts_metadata2("design:type", Function),
|
|
990
|
+
_ts_metadata2("design:paramtypes", [
|
|
991
|
+
typeof PaymentProviderRegistry === "undefined" ? Object : PaymentProviderRegistry,
|
|
306
992
|
typeof OrderPersistence === "undefined" ? Object : OrderPersistence
|
|
307
993
|
])
|
|
308
994
|
], OrderService);
|
|
@@ -336,12 +1022,12 @@ function _ts_decorate3(decorators, target, key, desc) {
|
|
|
336
1022
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
337
1023
|
}
|
|
338
1024
|
__name(_ts_decorate3, "_ts_decorate");
|
|
339
|
-
function
|
|
1025
|
+
function _ts_metadata3(metadataKey, metadataValue) {
|
|
340
1026
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
341
1027
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
342
1028
|
}
|
|
343
1029
|
}
|
|
344
|
-
__name(
|
|
1030
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
345
1031
|
function _ts_param2(paramIndex, decorator) {
|
|
346
1032
|
return function(target, key) {
|
|
347
1033
|
decorator(target, key, paramIndex);
|
|
@@ -452,10 +1138,11 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
|
|
|
452
1138
|
}
|
|
453
1139
|
}
|
|
454
1140
|
/**
|
|
455
|
-
* 重试失败订单 — 将 failed → created
|
|
1141
|
+
* 重试失败订单 — 将 failed → created 并重新发起支付。
|
|
1142
|
+
* 幂等键由调用方给出且必须与上次不同,否则渠道会原样返回那笔已失败的支付。
|
|
456
1143
|
*/
|
|
457
|
-
retryOrder(orderId) {
|
|
458
|
-
return this.orderService.retryOrder(orderId);
|
|
1144
|
+
retryOrder(orderId, idempotencyKey) {
|
|
1145
|
+
return this.orderService.retryOrder(orderId, idempotencyKey);
|
|
459
1146
|
}
|
|
460
1147
|
getRecoveryAttempts(orderId) {
|
|
461
1148
|
if (!this.attempts) throw new Error("Recovery attempt persistence must be configured");
|
|
@@ -478,8 +1165,8 @@ PaymentRecoveryService = _ts_decorate3([
|
|
|
478
1165
|
_ts_param2(4, Optional()),
|
|
479
1166
|
_ts_param2(4, Inject2(RECOVERY_URL_GENERATOR)),
|
|
480
1167
|
_ts_param2(5, Optional()),
|
|
481
|
-
|
|
482
|
-
|
|
1168
|
+
_ts_metadata3("design:type", Function),
|
|
1169
|
+
_ts_metadata3("design:paramtypes", [
|
|
483
1170
|
typeof OrderService === "undefined" ? Object : OrderService,
|
|
484
1171
|
Object,
|
|
485
1172
|
typeof RecoveryMailSender === "undefined" ? Object : RecoveryMailSender,
|
|
@@ -542,24 +1229,20 @@ function configuredProviders() {
|
|
|
542
1229
|
useFactory: /* @__PURE__ */ __name((options) => options.recoveryAttempts ?? null, "useFactory")
|
|
543
1230
|
},
|
|
544
1231
|
{
|
|
545
|
-
provide:
|
|
1232
|
+
provide: PaymentProviderRegistry,
|
|
546
1233
|
inject: [
|
|
547
1234
|
PAYMENT_OPTIONS
|
|
548
1235
|
],
|
|
549
|
-
useFactory: /* @__PURE__ */ __name((options) =>
|
|
550
|
-
const service = new PaymentService();
|
|
551
|
-
for (const provider of options.providers ?? []) service.register(provider);
|
|
552
|
-
return service;
|
|
553
|
-
}, "useFactory")
|
|
1236
|
+
useFactory: /* @__PURE__ */ __name((options) => new PaymentProviderRegistry(options.providers ?? []), "useFactory")
|
|
554
1237
|
},
|
|
555
1238
|
OrderService,
|
|
556
1239
|
PaymentRecoveryService,
|
|
557
1240
|
{
|
|
558
1241
|
provide: PAYMENT_PROVIDERS,
|
|
559
1242
|
inject: [
|
|
560
|
-
|
|
1243
|
+
PaymentProviderRegistry
|
|
561
1244
|
],
|
|
562
|
-
useFactory: /* @__PURE__ */ __name((
|
|
1245
|
+
useFactory: /* @__PURE__ */ __name((registry) => registry.list(), "useFactory")
|
|
563
1246
|
}
|
|
564
1247
|
];
|
|
565
1248
|
}
|
|
@@ -580,7 +1263,7 @@ var ZuckerPaymentModule = class _ZuckerPaymentModule {
|
|
|
580
1263
|
...configuredProviders()
|
|
581
1264
|
],
|
|
582
1265
|
exports: [
|
|
583
|
-
|
|
1266
|
+
PaymentProviderRegistry,
|
|
584
1267
|
OrderService,
|
|
585
1268
|
PaymentRecoveryService
|
|
586
1269
|
]
|
|
@@ -600,7 +1283,7 @@ var ZuckerPaymentModule = class _ZuckerPaymentModule {
|
|
|
600
1283
|
...configuredProviders()
|
|
601
1284
|
],
|
|
602
1285
|
exports: [
|
|
603
|
-
|
|
1286
|
+
PaymentProviderRegistry,
|
|
604
1287
|
OrderService,
|
|
605
1288
|
PaymentRecoveryService
|
|
606
1289
|
]
|
|
@@ -612,61 +1295,6 @@ ZuckerPaymentModule = _ts_decorate4([
|
|
|
612
1295
|
Module({})
|
|
613
1296
|
], ZuckerPaymentModule);
|
|
614
1297
|
|
|
615
|
-
// src/providers/stripe.ts
|
|
616
|
-
import { createRequire } from "module";
|
|
617
|
-
var requireSdk = createRequire(typeof __filename === "string" ? __filename : import.meta.url);
|
|
618
|
-
function createStripeClient(secretKey, options) {
|
|
619
|
-
const StripeSdk = requireSdk("stripe");
|
|
620
|
-
return new StripeSdk(secretKey, options);
|
|
621
|
-
}
|
|
622
|
-
__name(createStripeClient, "createStripeClient");
|
|
623
|
-
function verifyStripeWebhookEvent(client, payload, signature, webhookSecret) {
|
|
624
|
-
return client.webhooks.constructEvent(payload, signature, webhookSecret);
|
|
625
|
-
}
|
|
626
|
-
__name(verifyStripeWebhookEvent, "verifyStripeWebhookEvent");
|
|
627
|
-
function stripeResourceId(value) {
|
|
628
|
-
return typeof value === "string" ? value : value?.id;
|
|
629
|
-
}
|
|
630
|
-
__name(stripeResourceId, "stripeResourceId");
|
|
631
|
-
function observeStripeCheckoutSession(raw) {
|
|
632
|
-
return {
|
|
633
|
-
raw,
|
|
634
|
-
id: raw.id,
|
|
635
|
-
url: raw.url || void 0,
|
|
636
|
-
clientReferenceId: raw.client_reference_id || void 0,
|
|
637
|
-
paymentIntentId: stripeResourceId(raw.payment_intent),
|
|
638
|
-
subscriptionId: stripeResourceId(raw.subscription),
|
|
639
|
-
settlement: raw.payment_status === "paid" ? "paid" : raw.status === "expired" ? "unpaid" : "unknown"
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
__name(observeStripeCheckoutSession, "observeStripeCheckoutSession");
|
|
643
|
-
async function createStripeCheckoutSession(client, params) {
|
|
644
|
-
return observeStripeCheckoutSession(await client.checkout.sessions.create(params));
|
|
645
|
-
}
|
|
646
|
-
__name(createStripeCheckoutSession, "createStripeCheckoutSession");
|
|
647
|
-
async function retrieveStripeCheckoutSession(client, id) {
|
|
648
|
-
return observeStripeCheckoutSession(await client.checkout.sessions.retrieve(id));
|
|
649
|
-
}
|
|
650
|
-
__name(retrieveStripeCheckoutSession, "retrieveStripeCheckoutSession");
|
|
651
|
-
function observeStripePaymentIntent(raw) {
|
|
652
|
-
const charge = typeof raw.latest_charge === "object" ? raw.latest_charge : null;
|
|
653
|
-
return {
|
|
654
|
-
raw,
|
|
655
|
-
id: raw.id,
|
|
656
|
-
chargeId: stripeResourceId(raw.latest_charge) ?? null,
|
|
657
|
-
paymentMethodType: charge?.payment_method_details?.type,
|
|
658
|
-
failureCode: raw.last_payment_error?.code,
|
|
659
|
-
failureMessage: raw.last_payment_error?.message,
|
|
660
|
-
settlement: raw.status === "succeeded" ? "paid" : raw.status === "canceled" ? "unpaid" : "unknown"
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
__name(observeStripePaymentIntent, "observeStripePaymentIntent");
|
|
664
|
-
async function retrieveStripePaymentIntent(client, id, params) {
|
|
665
|
-
const raw = params ? await client.paymentIntents.retrieve(id, params) : await client.paymentIntents.retrieve(id);
|
|
666
|
-
return observeStripePaymentIntent(raw);
|
|
667
|
-
}
|
|
668
|
-
__name(retrieveStripePaymentIntent, "retrieveStripePaymentIntent");
|
|
669
|
-
|
|
670
1298
|
// src/providers/paypal.ts
|
|
671
1299
|
import { createRequire as createRequire2 } from "module";
|
|
672
1300
|
var requireSdk2 = createRequire2(typeof __filename === "string" ? __filename : import.meta.url);
|
|
@@ -680,6 +1308,22 @@ function observePayPalOrder(raw) {
|
|
|
680
1308
|
};
|
|
681
1309
|
}
|
|
682
1310
|
__name(observePayPalOrder, "observePayPalOrder");
|
|
1311
|
+
function captureLinkId(resource) {
|
|
1312
|
+
if (!Array.isArray(resource.links)) return void 0;
|
|
1313
|
+
for (const value of resource.links) {
|
|
1314
|
+
const link = asRecord(value);
|
|
1315
|
+
if (link?.rel !== "up" || typeof link.href !== "string") continue;
|
|
1316
|
+
try {
|
|
1317
|
+
const url = new URL(link.href);
|
|
1318
|
+
if (url.protocol !== "https:" || !/^api(?:-m)?(?:\.sandbox)?\.paypal\.com$/.test(url.hostname)) continue;
|
|
1319
|
+
const match = /^\/v2\/payments\/captures\/([A-Za-z0-9_-]+)$/.exec(url.pathname);
|
|
1320
|
+
if (match) return match[1];
|
|
1321
|
+
} catch {
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
return void 0;
|
|
1325
|
+
}
|
|
1326
|
+
__name(captureLinkId, "captureLinkId");
|
|
683
1327
|
function asRecord(value) {
|
|
684
1328
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
685
1329
|
}
|
|
@@ -711,6 +1355,42 @@ function decodePayPalWebhookEvent(payload) {
|
|
|
711
1355
|
};
|
|
712
1356
|
}
|
|
713
1357
|
__name(decodePayPalWebhookEvent, "decodePayPalWebhookEvent");
|
|
1358
|
+
function decodePayPalMoneyReturnedEvent(payload) {
|
|
1359
|
+
const raw = asRecord(JSON.parse(payload));
|
|
1360
|
+
if (!raw || typeof raw.event_type !== "string") throw new Error("Invalid PayPal webhook event");
|
|
1361
|
+
const type = raw.event_type;
|
|
1362
|
+
if (type === "PAYMENT.CAPTURE.REFUNDED" || type === "PAYMENT.CAPTURE.REVERSED") {
|
|
1363
|
+
const resource = asRecord(raw.resource);
|
|
1364
|
+
const amount = asRecord(resource?.amount);
|
|
1365
|
+
const related = asRecord(asRecord(resource?.supplementary_data)?.related_ids);
|
|
1366
|
+
const reversal = type === "PAYMENT.CAPTURE.REVERSED";
|
|
1367
|
+
if (!resource || typeof resource.id !== "string" || !resource.id || typeof amount?.value !== "string" || !/^\d+(?:\.\d{1,2})?$/.test(amount.value) || typeof amount.currency_code !== "string" || !/^[A-Z]{3}$/.test(amount.currency_code)) {
|
|
1368
|
+
throw new Error("Invalid PayPal money-returned event");
|
|
1369
|
+
}
|
|
1370
|
+
const status = reversal ? "COMPLETED" : resource.status;
|
|
1371
|
+
if (status !== "PENDING" && status !== "COMPLETED" && status !== "FAILED" && status !== "CANCELLED") {
|
|
1372
|
+
throw new Error("Unknown PayPal refund status");
|
|
1373
|
+
}
|
|
1374
|
+
const captureId = reversal ? resource.id : typeof related?.capture_id === "string" ? related.capture_id : captureLinkId(resource);
|
|
1375
|
+
const orderId = typeof related?.order_id === "string" ? related.order_id : void 0;
|
|
1376
|
+
if (!captureId && !orderId) throw new Error("PayPal refund event has no capture or order identity");
|
|
1377
|
+
return {
|
|
1378
|
+
raw,
|
|
1379
|
+
type,
|
|
1380
|
+
kind: reversal ? "capture-reversed" : "capture-refunded",
|
|
1381
|
+
returned: {
|
|
1382
|
+
id: reversal ? `reversal:${resource.id}` : resource.id,
|
|
1383
|
+
captureId,
|
|
1384
|
+
orderId,
|
|
1385
|
+
amount: amount.value,
|
|
1386
|
+
currency: amount.currency_code,
|
|
1387
|
+
status
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
__name(decodePayPalMoneyReturnedEvent, "decodePayPalMoneyReturnedEvent");
|
|
714
1394
|
var PayPalPaymentTransport = class {
|
|
715
1395
|
static {
|
|
716
1396
|
__name(this, "PayPalPaymentTransport");
|
|
@@ -743,17 +1423,17 @@ var PayPalPaymentTransport = class {
|
|
|
743
1423
|
this.baseUrl = options.mode === "live" ? "https://api.paypal.com" : "https://api.sandbox.paypal.com";
|
|
744
1424
|
this.fetcher = options.fetcher ?? fetch;
|
|
745
1425
|
}
|
|
746
|
-
async createOrder(
|
|
747
|
-
const { result } = await this.orders.createOrder(
|
|
1426
|
+
async createOrder(input2) {
|
|
1427
|
+
const { result } = await this.orders.createOrder(input2);
|
|
748
1428
|
return observePayPalOrder(result);
|
|
749
1429
|
}
|
|
750
1430
|
/** An explicit capture command; never invoked by getOrder or status observation. */
|
|
751
|
-
async captureOrder(
|
|
752
|
-
const { result } = await this.orders.captureOrder(
|
|
1431
|
+
async captureOrder(input2) {
|
|
1432
|
+
const { result } = await this.orders.captureOrder(input2);
|
|
753
1433
|
return observePayPalOrder(result);
|
|
754
1434
|
}
|
|
755
|
-
async getOrder(
|
|
756
|
-
const { result } = await this.orders.getOrder(
|
|
1435
|
+
async getOrder(input2) {
|
|
1436
|
+
const { result } = await this.orders.getOrder(input2);
|
|
757
1437
|
return observePayPalOrder(result);
|
|
758
1438
|
}
|
|
759
1439
|
async getAccessToken() {
|
|
@@ -806,18 +1486,21 @@ var PayPalPaymentTransport = class {
|
|
|
806
1486
|
return false;
|
|
807
1487
|
}
|
|
808
1488
|
}
|
|
809
|
-
async refundCapture(captureId, request) {
|
|
1489
|
+
async refundCapture(captureId, request, requestId) {
|
|
810
1490
|
const accessToken = await this.getAccessToken();
|
|
811
1491
|
const response = await this.fetcher(`${this.baseUrl}/v2/payments/captures/${encodeURIComponent(captureId)}/refund`, {
|
|
812
1492
|
method: "POST",
|
|
813
1493
|
headers: {
|
|
814
1494
|
"Content-Type": "application/json",
|
|
815
|
-
Authorization: `Bearer ${accessToken}
|
|
1495
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1496
|
+
...requestId ? {
|
|
1497
|
+
"PayPal-Request-Id": requestId
|
|
1498
|
+
} : {}
|
|
816
1499
|
},
|
|
817
1500
|
body: JSON.stringify(request)
|
|
818
1501
|
});
|
|
819
1502
|
const result = await response.json();
|
|
820
|
-
if (!response.ok
|
|
1503
|
+
if (!response.ok) throw new Error(`PayPal refund request failed (${response.status})`);
|
|
821
1504
|
return result;
|
|
822
1505
|
}
|
|
823
1506
|
};
|
|
@@ -827,24 +1510,40 @@ export {
|
|
|
827
1510
|
OrderNotFoundError,
|
|
828
1511
|
OrderService,
|
|
829
1512
|
PAYMENT_PROVIDERS,
|
|
1513
|
+
PAYPAL_SUPPORTED_CURRENCIES,
|
|
830
1514
|
PayPalPaymentTransport,
|
|
831
1515
|
PaymentEvents,
|
|
1516
|
+
PaymentProviderRegistry,
|
|
832
1517
|
PaymentRecoveryService,
|
|
833
|
-
PaymentService,
|
|
834
1518
|
RECOVERY_ATTEMPT_STORE,
|
|
835
1519
|
RECOVERY_MAIL_SENDER,
|
|
836
1520
|
RECOVERY_URL_GENERATOR,
|
|
837
1521
|
RECOVERY_USER_LOOKUP,
|
|
1522
|
+
SETTLEMENT_STATES,
|
|
838
1523
|
ZuckerPaymentModule,
|
|
1524
|
+
adapterSupportsCurrency,
|
|
1525
|
+
checkPaymentAdapterContract,
|
|
1526
|
+
createFakeAdapter,
|
|
1527
|
+
createPayPalAdapter,
|
|
1528
|
+
createStripeAdapter,
|
|
839
1529
|
createStripeCheckoutSession,
|
|
840
1530
|
createStripeClient,
|
|
1531
|
+
currencyExponent,
|
|
1532
|
+
decodePayPalMoneyReturnedEvent,
|
|
841
1533
|
decodePayPalWebhookEvent,
|
|
842
1534
|
friendlyErrorReason,
|
|
1535
|
+
fromMinorUnits,
|
|
1536
|
+
isMoneyMoved,
|
|
1537
|
+
isTerminal,
|
|
843
1538
|
observePayPalOrder,
|
|
1539
|
+
observePayPalState,
|
|
844
1540
|
observeStripeCheckoutSession,
|
|
845
1541
|
observeStripePaymentIntent,
|
|
1542
|
+
observeStripeState,
|
|
1543
|
+
reconcile,
|
|
846
1544
|
retrieveStripeCheckoutSession,
|
|
847
1545
|
retrieveStripePaymentIntent,
|
|
848
1546
|
stripeResourceId,
|
|
1547
|
+
toMinorUnits,
|
|
849
1548
|
verifyStripeWebhookEvent
|
|
850
1549
|
};
|