@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.js
CHANGED
|
@@ -26,30 +26,189 @@ __export(index_exports, {
|
|
|
26
26
|
OrderNotFoundError: () => OrderNotFoundError,
|
|
27
27
|
OrderService: () => OrderService,
|
|
28
28
|
PAYMENT_PROVIDERS: () => PAYMENT_PROVIDERS,
|
|
29
|
+
PAYPAL_SUPPORTED_CURRENCIES: () => PAYPAL_SUPPORTED_CURRENCIES,
|
|
29
30
|
PayPalPaymentTransport: () => PayPalPaymentTransport,
|
|
30
31
|
PaymentEvents: () => PaymentEvents,
|
|
32
|
+
PaymentProviderRegistry: () => PaymentProviderRegistry,
|
|
31
33
|
PaymentRecoveryService: () => PaymentRecoveryService,
|
|
32
|
-
PaymentService: () => PaymentService,
|
|
33
34
|
RECOVERY_ATTEMPT_STORE: () => RECOVERY_ATTEMPT_STORE,
|
|
34
35
|
RECOVERY_MAIL_SENDER: () => RECOVERY_MAIL_SENDER,
|
|
35
36
|
RECOVERY_URL_GENERATOR: () => RECOVERY_URL_GENERATOR,
|
|
36
37
|
RECOVERY_USER_LOOKUP: () => RECOVERY_USER_LOOKUP,
|
|
38
|
+
SETTLEMENT_STATES: () => SETTLEMENT_STATES,
|
|
37
39
|
ZuckerPaymentModule: () => ZuckerPaymentModule,
|
|
40
|
+
adapterSupportsCurrency: () => adapterSupportsCurrency,
|
|
41
|
+
checkPaymentAdapterContract: () => checkPaymentAdapterContract,
|
|
42
|
+
createFakeAdapter: () => createFakeAdapter,
|
|
43
|
+
createPayPalAdapter: () => createPayPalAdapter,
|
|
44
|
+
createStripeAdapter: () => createStripeAdapter,
|
|
38
45
|
createStripeCheckoutSession: () => createStripeCheckoutSession,
|
|
39
46
|
createStripeClient: () => createStripeClient,
|
|
47
|
+
currencyExponent: () => currencyExponent,
|
|
48
|
+
decodePayPalMoneyReturnedEvent: () => decodePayPalMoneyReturnedEvent,
|
|
40
49
|
decodePayPalWebhookEvent: () => decodePayPalWebhookEvent,
|
|
41
50
|
friendlyErrorReason: () => friendlyErrorReason,
|
|
51
|
+
fromMinorUnits: () => fromMinorUnits,
|
|
52
|
+
isMoneyMoved: () => isMoneyMoved,
|
|
53
|
+
isTerminal: () => isTerminal,
|
|
42
54
|
observePayPalOrder: () => observePayPalOrder,
|
|
55
|
+
observePayPalState: () => observePayPalState,
|
|
43
56
|
observeStripeCheckoutSession: () => observeStripeCheckoutSession,
|
|
44
57
|
observeStripePaymentIntent: () => observeStripePaymentIntent,
|
|
58
|
+
observeStripeState: () => observeStripeState,
|
|
59
|
+
reconcile: () => reconcile,
|
|
45
60
|
retrieveStripeCheckoutSession: () => retrieveStripeCheckoutSession,
|
|
46
61
|
retrieveStripePaymentIntent: () => retrieveStripePaymentIntent,
|
|
47
62
|
stripeResourceId: () => stripeResourceId,
|
|
63
|
+
toMinorUnits: () => toMinorUnits,
|
|
48
64
|
verifyStripeWebhookEvent: () => verifyStripeWebhookEvent
|
|
49
65
|
});
|
|
50
66
|
module.exports = __toCommonJS(index_exports);
|
|
51
67
|
|
|
52
|
-
// src/payment.
|
|
68
|
+
// src/contract/payment-contract.ts
|
|
69
|
+
function adapterSupportsCurrency(adapter, currency) {
|
|
70
|
+
return !adapter.supportedCurrencies || adapter.supportedCurrencies.includes(currency.toUpperCase());
|
|
71
|
+
}
|
|
72
|
+
__name(adapterSupportsCurrency, "adapterSupportsCurrency");
|
|
73
|
+
|
|
74
|
+
// src/contract/settlement.ts
|
|
75
|
+
var SETTLEMENT_STATES = [
|
|
76
|
+
/** 已创建,用户尚未授权 */
|
|
77
|
+
"created",
|
|
78
|
+
/** 用户已授权但尚未扣款(PayPal APPROVED / Stripe requires_capture) */
|
|
79
|
+
"authorized",
|
|
80
|
+
/** 已扣款 */
|
|
81
|
+
"captured",
|
|
82
|
+
/** 已全额退款 */
|
|
83
|
+
"refunded",
|
|
84
|
+
/** 已部分退款 */
|
|
85
|
+
"partially_refunded",
|
|
86
|
+
/** 终态失败 */
|
|
87
|
+
"failed",
|
|
88
|
+
/** 终态取消(用户放弃 / 商户撤单) */
|
|
89
|
+
"canceled",
|
|
90
|
+
/**
|
|
91
|
+
* 无法判定。
|
|
92
|
+
* 不是"失败",也不是一个可持久化的终态——查询异常、供应商返回中间态、
|
|
93
|
+
* 回调迟到都会落到这里,必须保留原状继续观察。
|
|
94
|
+
*/
|
|
95
|
+
"unknown"
|
|
96
|
+
];
|
|
97
|
+
var MONEY_MOVED = /* @__PURE__ */ new Set([
|
|
98
|
+
"captured",
|
|
99
|
+
"refunded",
|
|
100
|
+
"partially_refunded"
|
|
101
|
+
]);
|
|
102
|
+
var TERMINAL = /* @__PURE__ */ new Set([
|
|
103
|
+
"refunded",
|
|
104
|
+
"failed",
|
|
105
|
+
"canceled"
|
|
106
|
+
]);
|
|
107
|
+
function isMoneyMoved(state) {
|
|
108
|
+
return MONEY_MOVED.has(state);
|
|
109
|
+
}
|
|
110
|
+
__name(isMoneyMoved, "isMoneyMoved");
|
|
111
|
+
function isTerminal(state) {
|
|
112
|
+
return TERMINAL.has(state);
|
|
113
|
+
}
|
|
114
|
+
__name(isTerminal, "isTerminal");
|
|
115
|
+
function reconcile(current, observed) {
|
|
116
|
+
if (observed === "unknown") {
|
|
117
|
+
return {
|
|
118
|
+
next: current,
|
|
119
|
+
changed: false,
|
|
120
|
+
rejected: "not-observable"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (observed === current) {
|
|
124
|
+
return {
|
|
125
|
+
next: current,
|
|
126
|
+
changed: false
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (isMoneyMoved(current) && (observed === "failed" || observed === "canceled")) {
|
|
130
|
+
return {
|
|
131
|
+
next: current,
|
|
132
|
+
changed: false,
|
|
133
|
+
rejected: "money-already-moved"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (current === "refunded") {
|
|
137
|
+
return {
|
|
138
|
+
next: current,
|
|
139
|
+
changed: false,
|
|
140
|
+
rejected: "already-terminal"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (current === "partially_refunded" && observed === "captured") {
|
|
144
|
+
return {
|
|
145
|
+
next: current,
|
|
146
|
+
changed: false,
|
|
147
|
+
rejected: "already-terminal"
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (isTerminal(current) && !isMoneyMoved(observed)) {
|
|
151
|
+
return {
|
|
152
|
+
next: current,
|
|
153
|
+
changed: false,
|
|
154
|
+
rejected: "already-terminal"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
next: observed,
|
|
159
|
+
changed: true
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
__name(reconcile, "reconcile");
|
|
163
|
+
|
|
164
|
+
// src/contract/money.ts
|
|
165
|
+
var ZERO_DECIMAL = /* @__PURE__ */ new Set([
|
|
166
|
+
"BIF",
|
|
167
|
+
"CLP",
|
|
168
|
+
"DJF",
|
|
169
|
+
"GNF",
|
|
170
|
+
"JPY",
|
|
171
|
+
"KMF",
|
|
172
|
+
"KRW",
|
|
173
|
+
"MGA",
|
|
174
|
+
"PYG",
|
|
175
|
+
"RWF",
|
|
176
|
+
"UGX",
|
|
177
|
+
"VND",
|
|
178
|
+
"VUV",
|
|
179
|
+
"XAF",
|
|
180
|
+
"XOF",
|
|
181
|
+
"XPF"
|
|
182
|
+
]);
|
|
183
|
+
function currencyExponent(currency) {
|
|
184
|
+
return ZERO_DECIMAL.has(currency.toUpperCase()) ? 0 : 2;
|
|
185
|
+
}
|
|
186
|
+
__name(currencyExponent, "currencyExponent");
|
|
187
|
+
function toMinorUnits(amount, currency) {
|
|
188
|
+
const exponent = currencyExponent(currency);
|
|
189
|
+
const match = /^(-?)(\d+)(?:\.(\d*))?$/.exec(amount.trim());
|
|
190
|
+
if (!match) throw new Error(`\u91D1\u989D\u683C\u5F0F\u975E\u6CD5\uFF1A${amount}`);
|
|
191
|
+
const [, sign, whole, fraction = ""] = match;
|
|
192
|
+
if (fraction.length > exponent && /[^0]/.test(fraction.slice(exponent))) {
|
|
193
|
+
throw new Error(`${currency} \u6700\u591A\u652F\u6301 ${exponent} \u4F4D\u5C0F\u6570\uFF0C\u6536\u5230 ${amount}`);
|
|
194
|
+
}
|
|
195
|
+
const padded = (fraction + "0".repeat(exponent)).slice(0, exponent);
|
|
196
|
+
const value = Number(`${whole}${padded}`);
|
|
197
|
+
if (!Number.isSafeInteger(value)) throw new Error(`\u91D1\u989D\u8D85\u51FA\u5B89\u5168\u6574\u6570\u8303\u56F4\uFF1A${amount}`);
|
|
198
|
+
return sign === "-" ? -value : value;
|
|
199
|
+
}
|
|
200
|
+
__name(toMinorUnits, "toMinorUnits");
|
|
201
|
+
function fromMinorUnits(value, currency) {
|
|
202
|
+
if (!Number.isInteger(value)) throw new Error(`\u6700\u5C0F\u5355\u4F4D\u5FC5\u987B\u662F\u6574\u6570\uFF1A${value}`);
|
|
203
|
+
const exponent = currencyExponent(currency);
|
|
204
|
+
const sign = value < 0 ? "-" : "";
|
|
205
|
+
const digits = String(Math.abs(value)).padStart(exponent + 1, "0");
|
|
206
|
+
if (exponent === 0) return `${sign}${digits}`;
|
|
207
|
+
return `${sign}${digits.slice(0, -exponent)}.${digits.slice(-exponent)}`;
|
|
208
|
+
}
|
|
209
|
+
__name(fromMinorUnits, "fromMinorUnits");
|
|
210
|
+
|
|
211
|
+
// src/contract/registry.ts
|
|
53
212
|
var import_common = require("@nestjs/common");
|
|
54
213
|
function _ts_decorate(decorators, target, key, desc) {
|
|
55
214
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -65,73 +224,586 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
65
224
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
66
225
|
}
|
|
67
226
|
__name(_ts_decorate, "_ts_decorate");
|
|
68
|
-
|
|
227
|
+
function _ts_metadata(metadataKey, metadataValue) {
|
|
228
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
229
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
233
|
+
var PaymentProviderRegistry = class _PaymentProviderRegistry {
|
|
69
234
|
static {
|
|
70
|
-
__name(this, "
|
|
71
|
-
}
|
|
72
|
-
logger = new import_common.Logger(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const provider = this.getProvider(providerId);
|
|
105
|
-
this.logger.log(`[${providerId}] \u521B\u5EFA\u652F\u4ED8\u610F\u56FE: ${params.amount} ${params.currency}`);
|
|
106
|
-
return provider.createPaymentIntent(params);
|
|
107
|
-
}
|
|
108
|
-
/** 确认支付 */
|
|
109
|
-
async confirmPayment(providerId, intentId) {
|
|
110
|
-
const provider = this.getProvider(providerId);
|
|
111
|
-
this.logger.log(`[${providerId}] \u786E\u8BA4\u652F\u4ED8: ${intentId}`);
|
|
112
|
-
return provider.confirmPayment(intentId);
|
|
113
|
-
}
|
|
114
|
-
/** 退款 */
|
|
115
|
-
async refund(providerId, paymentId, amount, reason) {
|
|
116
|
-
const provider = this.getProvider(providerId);
|
|
117
|
-
this.logger.log(`[${providerId}] \u9000\u6B3E: ${paymentId}, amount=${amount ?? "full"}`);
|
|
118
|
-
return provider.refund(paymentId, amount, reason);
|
|
119
|
-
}
|
|
120
|
-
/** 查询支付状态 */
|
|
121
|
-
async getPaymentStatus(providerId, intentId) {
|
|
122
|
-
const provider = this.getProvider(providerId);
|
|
123
|
-
return provider.getPaymentStatus(intentId);
|
|
124
|
-
}
|
|
125
|
-
/** 处理 Webhook */
|
|
126
|
-
async handleWebhook(providerId, payload, signature) {
|
|
127
|
-
const provider = this.getProvider(providerId);
|
|
128
|
-
this.logger.log(`[${providerId}] \u5904\u7406 Webhook`);
|
|
129
|
-
return provider.handleWebhook(payload, signature);
|
|
235
|
+
__name(this, "PaymentProviderRegistry");
|
|
236
|
+
}
|
|
237
|
+
logger = new import_common.Logger(_PaymentProviderRegistry.name);
|
|
238
|
+
adapters = /* @__PURE__ */ new Map();
|
|
239
|
+
constructor(adapters = []) {
|
|
240
|
+
for (const adapter of adapters) this.register(adapter);
|
|
241
|
+
}
|
|
242
|
+
register(adapter) {
|
|
243
|
+
if (this.adapters.has(adapter.id)) {
|
|
244
|
+
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`);
|
|
245
|
+
}
|
|
246
|
+
this.adapters.set(adapter.id, adapter);
|
|
247
|
+
this.logger.log(`\u5DF2\u6CE8\u518C\u652F\u4ED8\u6E20\u9053 "${adapter.id}"`);
|
|
248
|
+
}
|
|
249
|
+
has(id) {
|
|
250
|
+
return this.adapters.has(id);
|
|
251
|
+
}
|
|
252
|
+
get(id) {
|
|
253
|
+
const adapter = this.adapters.get(id);
|
|
254
|
+
if (!adapter) {
|
|
255
|
+
throw new Error(`\u6E20\u9053 "${id}" \u672A\u6CE8\u518C\u3002\u5DF2\u6CE8\u518C\uFF1A[${[
|
|
256
|
+
...this.adapters.keys()
|
|
257
|
+
].join(", ")}]`);
|
|
258
|
+
}
|
|
259
|
+
return adapter;
|
|
260
|
+
}
|
|
261
|
+
list() {
|
|
262
|
+
return [
|
|
263
|
+
...this.adapters.values()
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
/** 能处理该币种的渠道;"本产品卖不卖这个币种"由上层决定,不在这里过滤。 */
|
|
267
|
+
listForCurrency(currency) {
|
|
268
|
+
return this.list().filter((adapter) => adapterSupportsCurrency(adapter, currency));
|
|
130
269
|
}
|
|
131
270
|
};
|
|
132
|
-
|
|
133
|
-
(0, import_common.Injectable)()
|
|
134
|
-
|
|
271
|
+
PaymentProviderRegistry = _ts_decorate([
|
|
272
|
+
(0, import_common.Injectable)(),
|
|
273
|
+
_ts_metadata("design:type", Function),
|
|
274
|
+
_ts_metadata("design:paramtypes", [
|
|
275
|
+
Object
|
|
276
|
+
])
|
|
277
|
+
], PaymentProviderRegistry);
|
|
278
|
+
|
|
279
|
+
// src/contract/adapters/paypal.adapter.ts
|
|
280
|
+
var CAPTURE_INTENT = "CAPTURE";
|
|
281
|
+
var PAY_NOW = "PAY_NOW";
|
|
282
|
+
var NO_SHIPPING = "NO_SHIPPING";
|
|
283
|
+
var PAYPAL_SUPPORTED_CURRENCIES = [
|
|
284
|
+
"AUD",
|
|
285
|
+
"BRL",
|
|
286
|
+
"CAD",
|
|
287
|
+
"CNY",
|
|
288
|
+
"CZK",
|
|
289
|
+
"DKK",
|
|
290
|
+
"EUR",
|
|
291
|
+
"HKD",
|
|
292
|
+
"HUF",
|
|
293
|
+
"ILS",
|
|
294
|
+
"JPY",
|
|
295
|
+
"MYR",
|
|
296
|
+
"MXN",
|
|
297
|
+
"TWD",
|
|
298
|
+
"NZD",
|
|
299
|
+
"NOK",
|
|
300
|
+
"PHP",
|
|
301
|
+
"PLN",
|
|
302
|
+
"GBP",
|
|
303
|
+
"RUB",
|
|
304
|
+
"SGD",
|
|
305
|
+
"SEK",
|
|
306
|
+
"CHF",
|
|
307
|
+
"THB",
|
|
308
|
+
"USD"
|
|
309
|
+
];
|
|
310
|
+
function observePayPalState(order) {
|
|
311
|
+
const captures = order.purchaseUnits?.[0]?.payments?.captures ?? [];
|
|
312
|
+
switch (order.status) {
|
|
313
|
+
case "CREATED":
|
|
314
|
+
case "SAVED":
|
|
315
|
+
case "PAYER_ACTION_REQUIRED":
|
|
316
|
+
return "created";
|
|
317
|
+
case "APPROVED":
|
|
318
|
+
return "authorized";
|
|
319
|
+
case "VOIDED":
|
|
320
|
+
return "canceled";
|
|
321
|
+
case "COMPLETED": {
|
|
322
|
+
if (captures.some((capture) => capture.status === "COMPLETED")) return "captured";
|
|
323
|
+
if (captures.some((c) => c.status === "DECLINED" || c.status === "FAILED")) return "failed";
|
|
324
|
+
return "authorized";
|
|
325
|
+
}
|
|
326
|
+
default:
|
|
327
|
+
return "unknown";
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
__name(observePayPalState, "observePayPalState");
|
|
331
|
+
function createPayPalAdapter(options) {
|
|
332
|
+
const { transport } = options;
|
|
333
|
+
const toSession = /* @__PURE__ */ __name((order) => {
|
|
334
|
+
const unit = order.purchaseUnits?.[0];
|
|
335
|
+
const approve = order.links?.find((link) => link.rel === "approve")?.href;
|
|
336
|
+
return {
|
|
337
|
+
providerRef: order.id ?? "",
|
|
338
|
+
nextAction: approve ? {
|
|
339
|
+
kind: "redirect",
|
|
340
|
+
url: approve
|
|
341
|
+
} : {
|
|
342
|
+
kind: "none"
|
|
343
|
+
},
|
|
344
|
+
state: observePayPalState(order),
|
|
345
|
+
money: {
|
|
346
|
+
amount: unit?.amount?.value ?? "0",
|
|
347
|
+
currency: unit?.amount?.currencyCode ?? ""
|
|
348
|
+
},
|
|
349
|
+
raw: order
|
|
350
|
+
};
|
|
351
|
+
}, "toSession");
|
|
352
|
+
return {
|
|
353
|
+
id: "paypal",
|
|
354
|
+
supportedCurrencies: PAYPAL_SUPPORTED_CURRENCIES,
|
|
355
|
+
async createPayment(input2) {
|
|
356
|
+
const payment = await transport.createOrder({
|
|
357
|
+
body: {
|
|
358
|
+
intent: CAPTURE_INTENT,
|
|
359
|
+
purchaseUnits: [
|
|
360
|
+
{
|
|
361
|
+
referenceId: input2.reference,
|
|
362
|
+
amount: {
|
|
363
|
+
currencyCode: input2.money.currency,
|
|
364
|
+
// 原样透传十进制字符串,不经过 Number
|
|
365
|
+
value: input2.money.amount
|
|
366
|
+
},
|
|
367
|
+
description: input2.description
|
|
368
|
+
}
|
|
369
|
+
],
|
|
370
|
+
applicationContext: {
|
|
371
|
+
returnUrl: input2.returnUrl,
|
|
372
|
+
cancelUrl: input2.cancelUrl,
|
|
373
|
+
userAction: PAY_NOW,
|
|
374
|
+
shippingPreference: NO_SHIPPING,
|
|
375
|
+
brandName: options.brandName
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
// 幂等:同一个键重试,PayPal 返回同一笔订单
|
|
379
|
+
paypalRequestId: input2.idempotencyKey,
|
|
380
|
+
prefer: "return=representation"
|
|
381
|
+
});
|
|
382
|
+
return toSession(payment.raw);
|
|
383
|
+
},
|
|
384
|
+
async capture(providerRef, idempotencyKey) {
|
|
385
|
+
const payment = await transport.captureOrder({
|
|
386
|
+
id: providerRef,
|
|
387
|
+
paypalRequestId: idempotencyKey,
|
|
388
|
+
prefer: "return=representation"
|
|
389
|
+
});
|
|
390
|
+
return toSession(payment.raw);
|
|
391
|
+
},
|
|
392
|
+
/** 只读。对账会反复调用它,绝不能在这里触发 captureOrder。 */
|
|
393
|
+
async retrieve(providerRef) {
|
|
394
|
+
const payment = await transport.getOrder({
|
|
395
|
+
id: providerRef
|
|
396
|
+
});
|
|
397
|
+
return toSession(payment.raw);
|
|
398
|
+
},
|
|
399
|
+
async refund(input2) {
|
|
400
|
+
const refund = await transport.refundCapture(input2.providerRef, {
|
|
401
|
+
...input2.money ? {
|
|
402
|
+
amount: {
|
|
403
|
+
value: input2.money.amount,
|
|
404
|
+
currency_code: input2.money.currency
|
|
405
|
+
}
|
|
406
|
+
} : {},
|
|
407
|
+
note_to_payer: input2.reason
|
|
408
|
+
}, input2.idempotencyKey);
|
|
409
|
+
return {
|
|
410
|
+
refundRef: refund.id ?? "",
|
|
411
|
+
state: refund.status === "COMPLETED" ? "refunded" : "unknown"
|
|
412
|
+
};
|
|
413
|
+
},
|
|
414
|
+
async parseWebhook(request) {
|
|
415
|
+
if (!options.webhookId) {
|
|
416
|
+
throw new Error("PayPal webhookId is not configured; refusing to trust the request");
|
|
417
|
+
}
|
|
418
|
+
const body = typeof request.rawBody === "string" ? request.rawBody : new TextDecoder().decode(request.rawBody);
|
|
419
|
+
const valid = await transport.verifyWebhookSignature(body, request.headers, options.webhookId);
|
|
420
|
+
if (!valid) throw new Error("PayPal webhook signature verification failed");
|
|
421
|
+
const event = JSON.parse(body);
|
|
422
|
+
const type = event.event_type ?? "unknown";
|
|
423
|
+
const providerRef = event.resource?.supplementary_data?.related_ids?.order_id;
|
|
424
|
+
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";
|
|
425
|
+
return {
|
|
426
|
+
type,
|
|
427
|
+
providerRef,
|
|
428
|
+
state,
|
|
429
|
+
raw: event
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
__name(createPayPalAdapter, "createPayPalAdapter");
|
|
435
|
+
|
|
436
|
+
// src/providers/stripe.ts
|
|
437
|
+
var import_node_module = require("module");
|
|
438
|
+
var import_meta = {};
|
|
439
|
+
var requireSdk = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta.url);
|
|
440
|
+
function createStripeClient(secretKey, options) {
|
|
441
|
+
const StripeSdk = requireSdk("stripe");
|
|
442
|
+
return new StripeSdk(secretKey, options);
|
|
443
|
+
}
|
|
444
|
+
__name(createStripeClient, "createStripeClient");
|
|
445
|
+
function verifyStripeWebhookEvent(client, payload, signature, webhookSecret) {
|
|
446
|
+
return client.webhooks.constructEvent(payload, signature, webhookSecret);
|
|
447
|
+
}
|
|
448
|
+
__name(verifyStripeWebhookEvent, "verifyStripeWebhookEvent");
|
|
449
|
+
function stripeResourceId(value) {
|
|
450
|
+
return typeof value === "string" ? value : value?.id;
|
|
451
|
+
}
|
|
452
|
+
__name(stripeResourceId, "stripeResourceId");
|
|
453
|
+
function observeStripeCheckoutSession(raw) {
|
|
454
|
+
return {
|
|
455
|
+
raw,
|
|
456
|
+
id: raw.id,
|
|
457
|
+
url: raw.url || void 0,
|
|
458
|
+
clientReferenceId: raw.client_reference_id || void 0,
|
|
459
|
+
paymentIntentId: stripeResourceId(raw.payment_intent),
|
|
460
|
+
subscriptionId: stripeResourceId(raw.subscription),
|
|
461
|
+
settlement: raw.payment_status === "paid" ? "paid" : raw.status === "expired" ? "unpaid" : "unknown"
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
__name(observeStripeCheckoutSession, "observeStripeCheckoutSession");
|
|
465
|
+
async function createStripeCheckoutSession(client, params, options) {
|
|
466
|
+
return observeStripeCheckoutSession(options ? await client.checkout.sessions.create(params, options) : await client.checkout.sessions.create(params));
|
|
467
|
+
}
|
|
468
|
+
__name(createStripeCheckoutSession, "createStripeCheckoutSession");
|
|
469
|
+
async function retrieveStripeCheckoutSession(client, id) {
|
|
470
|
+
return observeStripeCheckoutSession(await client.checkout.sessions.retrieve(id));
|
|
471
|
+
}
|
|
472
|
+
__name(retrieveStripeCheckoutSession, "retrieveStripeCheckoutSession");
|
|
473
|
+
function observeStripePaymentIntent(raw) {
|
|
474
|
+
const charge = typeof raw.latest_charge === "object" ? raw.latest_charge : null;
|
|
475
|
+
return {
|
|
476
|
+
raw,
|
|
477
|
+
id: raw.id,
|
|
478
|
+
chargeId: stripeResourceId(raw.latest_charge) ?? null,
|
|
479
|
+
paymentMethodType: charge?.payment_method_details?.type,
|
|
480
|
+
failureCode: raw.last_payment_error?.code,
|
|
481
|
+
failureMessage: raw.last_payment_error?.message,
|
|
482
|
+
settlement: raw.status === "succeeded" ? "paid" : raw.status === "canceled" ? "unpaid" : "unknown"
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
__name(observeStripePaymentIntent, "observeStripePaymentIntent");
|
|
486
|
+
async function retrieveStripePaymentIntent(client, id, params) {
|
|
487
|
+
const raw = params ? await client.paymentIntents.retrieve(id, params) : await client.paymentIntents.retrieve(id);
|
|
488
|
+
return observeStripePaymentIntent(raw);
|
|
489
|
+
}
|
|
490
|
+
__name(retrieveStripePaymentIntent, "retrieveStripePaymentIntent");
|
|
491
|
+
|
|
492
|
+
// src/contract/adapters/stripe.adapter.ts
|
|
493
|
+
function observeStripeState(session) {
|
|
494
|
+
if (session.payment_status === "paid" || session.payment_status === "no_payment_required") {
|
|
495
|
+
return "captured";
|
|
496
|
+
}
|
|
497
|
+
if (session.status === "expired") return "canceled";
|
|
498
|
+
if (session.status === "open") return "created";
|
|
499
|
+
return "unknown";
|
|
500
|
+
}
|
|
501
|
+
__name(observeStripeState, "observeStripeState");
|
|
502
|
+
function createStripeAdapter(options) {
|
|
503
|
+
const { client } = options;
|
|
504
|
+
const toSession = /* @__PURE__ */ __name((raw) => ({
|
|
505
|
+
providerRef: raw.id,
|
|
506
|
+
nextAction: raw.url ? {
|
|
507
|
+
kind: "redirect",
|
|
508
|
+
url: raw.url
|
|
509
|
+
} : {
|
|
510
|
+
kind: "none"
|
|
511
|
+
},
|
|
512
|
+
state: observeStripeState(raw),
|
|
513
|
+
money: {
|
|
514
|
+
amount: fromMinorUnits(raw.amount_total ?? 0, raw.currency ?? "usd"),
|
|
515
|
+
currency: (raw.currency ?? "").toUpperCase()
|
|
516
|
+
},
|
|
517
|
+
raw
|
|
518
|
+
}), "toSession");
|
|
519
|
+
return {
|
|
520
|
+
id: "stripe",
|
|
521
|
+
// Stripe 支持的币种远超本契约会用到的范围,不声明限制;
|
|
522
|
+
// 不受支持的币种由 Stripe 自己拒绝,胡乱穷举一份只会过期。
|
|
523
|
+
supportedCurrencies: void 0,
|
|
524
|
+
async createPayment(input2) {
|
|
525
|
+
const observation = await createStripeCheckoutSession(client, {
|
|
526
|
+
mode: "payment",
|
|
527
|
+
line_items: [
|
|
528
|
+
{
|
|
529
|
+
price_data: {
|
|
530
|
+
currency: input2.money.currency.toLowerCase(),
|
|
531
|
+
product_data: {
|
|
532
|
+
name: input2.description || input2.reference
|
|
533
|
+
},
|
|
534
|
+
unit_amount: toMinorUnits(input2.money.amount, input2.money.currency)
|
|
535
|
+
},
|
|
536
|
+
quantity: 1
|
|
537
|
+
}
|
|
538
|
+
],
|
|
539
|
+
success_url: input2.returnUrl,
|
|
540
|
+
cancel_url: input2.cancelUrl,
|
|
541
|
+
client_reference_id: input2.reference,
|
|
542
|
+
metadata: input2.metadata,
|
|
543
|
+
...options.paymentMethodConfiguration ? {
|
|
544
|
+
payment_method_configuration: options.paymentMethodConfiguration
|
|
545
|
+
} : {}
|
|
546
|
+
}, {
|
|
547
|
+
idempotencyKey: input2.idempotencyKey
|
|
548
|
+
});
|
|
549
|
+
return toSession(observation.raw);
|
|
550
|
+
},
|
|
551
|
+
/**
|
|
552
|
+
* Stripe Checkout 默认自动扣款,没有独立的 capture 步骤。
|
|
553
|
+
* 按契约仍需实现:返回当前真实状态即可,不得伪造成已扣款。
|
|
554
|
+
*/
|
|
555
|
+
async capture(providerRef) {
|
|
556
|
+
const observation = await retrieveStripeCheckoutSession(client, providerRef);
|
|
557
|
+
return toSession(observation.raw);
|
|
558
|
+
},
|
|
559
|
+
async retrieve(providerRef) {
|
|
560
|
+
const observation = await retrieveStripeCheckoutSession(client, providerRef);
|
|
561
|
+
return toSession(observation.raw);
|
|
562
|
+
},
|
|
563
|
+
async refund(input2) {
|
|
564
|
+
const session = await retrieveStripeCheckoutSession(client, input2.providerRef);
|
|
565
|
+
const paymentIntent = session.paymentIntentId;
|
|
566
|
+
if (!paymentIntent) throw new Error("\u8BE5 Checkout \u4F1A\u8BDD\u6CA1\u6709\u53EF\u9000\u6B3E\u7684 PaymentIntent");
|
|
567
|
+
const refund = await client.refunds.create({
|
|
568
|
+
payment_intent: paymentIntent,
|
|
569
|
+
...input2.money ? {
|
|
570
|
+
amount: toMinorUnits(input2.money.amount, input2.money.currency)
|
|
571
|
+
} : {}
|
|
572
|
+
}, {
|
|
573
|
+
idempotencyKey: input2.idempotencyKey
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
refundRef: refund.id,
|
|
577
|
+
state: refund.status === "succeeded" ? "refunded" : "unknown"
|
|
578
|
+
};
|
|
579
|
+
},
|
|
580
|
+
async parseWebhook(request) {
|
|
581
|
+
if (!options.webhookSecret) {
|
|
582
|
+
throw new Error("Stripe webhookSecret is not configured; refusing to trust the request");
|
|
583
|
+
}
|
|
584
|
+
const signature = request.headers["stripe-signature"];
|
|
585
|
+
if (!signature) throw new Error("Stripe webhook is missing the stripe-signature header");
|
|
586
|
+
const event = verifyStripeWebhookEvent(client, typeof request.rawBody === "string" ? request.rawBody : Buffer.from(request.rawBody), signature, options.webhookSecret);
|
|
587
|
+
const object = event.data.object;
|
|
588
|
+
const state = event.type === "checkout.session.completed" ? observeStripeState(event.data.object) : event.type === "checkout.session.expired" ? "canceled" : event.type === "charge.refunded" ? "refunded" : "unknown";
|
|
589
|
+
return {
|
|
590
|
+
type: event.type,
|
|
591
|
+
providerRef: object.id,
|
|
592
|
+
state,
|
|
593
|
+
raw: event
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
__name(createStripeAdapter, "createStripeAdapter");
|
|
599
|
+
|
|
600
|
+
// src/contract/contract-suite.ts
|
|
601
|
+
var MUTATING = [
|
|
602
|
+
"capture",
|
|
603
|
+
"refund",
|
|
604
|
+
"cancel"
|
|
605
|
+
];
|
|
606
|
+
async function checkPaymentAdapterContract(probe) {
|
|
607
|
+
const checks = [];
|
|
608
|
+
const captureNames = probe.captureCallNames ?? MUTATING;
|
|
609
|
+
const run = /* @__PURE__ */ __name(async (name, fn) => {
|
|
610
|
+
try {
|
|
611
|
+
const detail = await fn();
|
|
612
|
+
checks.push({
|
|
613
|
+
name,
|
|
614
|
+
ok: detail === void 0,
|
|
615
|
+
detail
|
|
616
|
+
});
|
|
617
|
+
} catch (error) {
|
|
618
|
+
checks.push({
|
|
619
|
+
name,
|
|
620
|
+
ok: false,
|
|
621
|
+
detail: `\u629B\u51FA\u5F02\u5E38\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}, "run");
|
|
625
|
+
await run("retrieve \u5FC5\u987B\u53EA\u8BFB", async () => {
|
|
626
|
+
const { adapter, mutatingCalls } = probe.create();
|
|
627
|
+
const created = await adapter.createPayment(input(probe.supportedCurrency));
|
|
628
|
+
const before = mutatingCalls().filter((c) => captureNames.includes(c)).length;
|
|
629
|
+
await adapter.retrieve(created.providerRef);
|
|
630
|
+
const after = mutatingCalls().filter((c) => captureNames.includes(c)).length;
|
|
631
|
+
return after > before ? `retrieve \u89E6\u53D1\u4E86\u5199\u64CD\u4F5C\uFF1A${mutatingCalls().join(", ")}` : void 0;
|
|
632
|
+
});
|
|
633
|
+
await run("\u76F8\u540C\u5E42\u7B49\u952E\u4E0D\u5F97\u4EA7\u751F\u7B2C\u4E8C\u7B14\u652F\u4ED8", async () => {
|
|
634
|
+
const { adapter } = probe.create();
|
|
635
|
+
const key = "contract-idempotency-key";
|
|
636
|
+
const first = await adapter.createPayment(input(probe.supportedCurrency, key));
|
|
637
|
+
const second = await adapter.createPayment(input(probe.supportedCurrency, key));
|
|
638
|
+
return first.providerRef === second.providerRef ? void 0 : `\u4EA7\u751F\u4E86\u4E24\u7B14\uFF1A${first.providerRef} \u4E0E ${second.providerRef}`;
|
|
639
|
+
});
|
|
640
|
+
await run("\u9A8C\u7B7E\u5931\u8D25\u5FC5\u987B\u629B\u9519\u800C\u975E\u8FD4\u56DE\u672A\u77E5\u4E8B\u4EF6", async () => {
|
|
641
|
+
const { adapter } = probe.create();
|
|
642
|
+
try {
|
|
643
|
+
const observation = await adapter.parseWebhook(probe.invalidWebhook);
|
|
644
|
+
return `\u672A\u629B\u9519\uFF0C\u8FD4\u56DE\u4E86 ${JSON.stringify(observation.type)}`;
|
|
645
|
+
} catch {
|
|
646
|
+
return void 0;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
await run("\u91D1\u989D\u5FC5\u987B\u4EE5\u5341\u8FDB\u5236\u5B57\u7B26\u4E32\u539F\u6837\u900F\u4F20", async () => {
|
|
650
|
+
const { adapter } = probe.create();
|
|
651
|
+
const amount = "29.00";
|
|
652
|
+
const created = await adapter.createPayment({
|
|
653
|
+
...input(probe.supportedCurrency),
|
|
654
|
+
money: {
|
|
655
|
+
amount,
|
|
656
|
+
currency: probe.supportedCurrency
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
return created.money.amount === amount ? void 0 : `\u671F\u671B "${amount}"\uFF0C\u5B9E\u9645 ${JSON.stringify(created.money.amount)}`;
|
|
660
|
+
});
|
|
661
|
+
await run("\u5728\u9014\u652F\u4ED8\u4E0D\u5F97\u88AB\u5224\u4E3A failed", async () => {
|
|
662
|
+
const { adapter } = probe.create();
|
|
663
|
+
const created = await adapter.createPayment(input(probe.supportedCurrency));
|
|
664
|
+
const retrieved = await adapter.retrieve(created.providerRef);
|
|
665
|
+
return retrieved.state === "failed" ? "\u521A\u521B\u5EFA\u3001\u7528\u6237\u5C1A\u672A\u64CD\u4F5C\u7684\u652F\u4ED8\u88AB\u5224\u4E3A failed" : void 0;
|
|
666
|
+
});
|
|
667
|
+
await run("nextAction \u5FC5\u987B\u662F\u5951\u7EA6\u5185\u7684 kind", async () => {
|
|
668
|
+
const { adapter } = probe.create();
|
|
669
|
+
const created = await adapter.createPayment(input(probe.supportedCurrency));
|
|
670
|
+
const known = [
|
|
671
|
+
"redirect",
|
|
672
|
+
"qrcode",
|
|
673
|
+
"form_post",
|
|
674
|
+
"sdk_params",
|
|
675
|
+
"none"
|
|
676
|
+
];
|
|
677
|
+
return known.includes(created.nextAction.kind) ? void 0 : `\u672A\u77E5 kind\uFF1A${created.nextAction.kind}`;
|
|
678
|
+
});
|
|
679
|
+
await run("\u5E01\u79CD\u80FD\u529B\u58F0\u660E\u4E0E\u5B9E\u9645\u4E00\u81F4", async () => {
|
|
680
|
+
const { adapter } = probe.create();
|
|
681
|
+
if (!adapter.supportedCurrencies) return void 0;
|
|
682
|
+
if (!adapterSupportsCurrency(adapter, probe.supportedCurrency)) {
|
|
683
|
+
return `\u58F0\u660E\u4E0D\u652F\u6301 ${probe.supportedCurrency}\uFF0C\u4F46\u63A2\u9488\u79F0\u5176\u652F\u6301`;
|
|
684
|
+
}
|
|
685
|
+
if (probe.unsupportedCurrency && adapterSupportsCurrency(adapter, probe.unsupportedCurrency)) {
|
|
686
|
+
return `\u58F0\u660E\u652F\u6301 ${probe.unsupportedCurrency}\uFF0C\u4F46\u63A2\u9488\u79F0\u5176\u4E0D\u652F\u6301`;
|
|
687
|
+
}
|
|
688
|
+
return void 0;
|
|
689
|
+
});
|
|
690
|
+
return checks;
|
|
691
|
+
}
|
|
692
|
+
__name(checkPaymentAdapterContract, "checkPaymentAdapterContract");
|
|
693
|
+
function input(currency, idempotencyKey = "contract-default-key") {
|
|
694
|
+
return {
|
|
695
|
+
reference: "contract-order",
|
|
696
|
+
money: {
|
|
697
|
+
amount: "10.00",
|
|
698
|
+
currency
|
|
699
|
+
},
|
|
700
|
+
idempotencyKey
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
__name(input, "input");
|
|
704
|
+
|
|
705
|
+
// src/contract/fake-adapter.ts
|
|
706
|
+
function createFakeAdapter(options = {}) {
|
|
707
|
+
const defects = options.defects ?? {};
|
|
708
|
+
const payments = /* @__PURE__ */ new Map();
|
|
709
|
+
const byIdempotencyKey = /* @__PURE__ */ new Map();
|
|
710
|
+
const mutatingCalls = [];
|
|
711
|
+
let counter = 0;
|
|
712
|
+
const adapter = {
|
|
713
|
+
id: options.id ?? "fake",
|
|
714
|
+
supportedCurrencies: options.supportedCurrencies,
|
|
715
|
+
async createPayment(input2) {
|
|
716
|
+
mutatingCalls.push("createPayment");
|
|
717
|
+
const existing = defects.ignoreIdempotency ? void 0 : byIdempotencyKey.get(input2.idempotencyKey);
|
|
718
|
+
if (existing) {
|
|
719
|
+
const payment2 = payments.get(existing);
|
|
720
|
+
return session(payment2);
|
|
721
|
+
}
|
|
722
|
+
const ref = `fake_${++counter}`;
|
|
723
|
+
const amount = defects.mangleAmount ? String(Number(input2.money.amount)) : input2.money.amount;
|
|
724
|
+
const payment = {
|
|
725
|
+
ref,
|
|
726
|
+
state: "created",
|
|
727
|
+
amount,
|
|
728
|
+
currency: input2.money.currency
|
|
729
|
+
};
|
|
730
|
+
payments.set(ref, payment);
|
|
731
|
+
byIdempotencyKey.set(input2.idempotencyKey, ref);
|
|
732
|
+
return session(payment);
|
|
733
|
+
},
|
|
734
|
+
async capture(providerRef) {
|
|
735
|
+
mutatingCalls.push("capture");
|
|
736
|
+
const payment = expect(providerRef);
|
|
737
|
+
payment.state = "captured";
|
|
738
|
+
return session(payment);
|
|
739
|
+
},
|
|
740
|
+
async retrieve(providerRef) {
|
|
741
|
+
if (defects.retrieveCaptures) {
|
|
742
|
+
mutatingCalls.push("capture");
|
|
743
|
+
expect(providerRef).state = "captured";
|
|
744
|
+
}
|
|
745
|
+
const payment = expect(providerRef);
|
|
746
|
+
if (defects.guessesPendingAsFailed && payment.state === "created") {
|
|
747
|
+
return {
|
|
748
|
+
...session(payment),
|
|
749
|
+
state: "failed"
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return session(payment);
|
|
753
|
+
},
|
|
754
|
+
async refund(input2) {
|
|
755
|
+
mutatingCalls.push("refund");
|
|
756
|
+
const payment = expect(input2.providerRef);
|
|
757
|
+
payment.state = input2.money ? "partially_refunded" : "refunded";
|
|
758
|
+
return {
|
|
759
|
+
refundRef: `refund_${payment.ref}`,
|
|
760
|
+
state: payment.state
|
|
761
|
+
};
|
|
762
|
+
},
|
|
763
|
+
async parseWebhook(request) {
|
|
764
|
+
const valid = request.headers["x-fake-signature"] === "valid";
|
|
765
|
+
if (!valid && !defects.acceptsBadSignature) {
|
|
766
|
+
throw new Error("webhook signature verification failed");
|
|
767
|
+
}
|
|
768
|
+
const ref = request.headers["x-fake-ref"];
|
|
769
|
+
return {
|
|
770
|
+
type: "fake.payment.updated",
|
|
771
|
+
providerRef: ref,
|
|
772
|
+
state: ref && payments.has(ref) ? payments.get(ref).state : "unknown",
|
|
773
|
+
raw: {}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
function expect(ref) {
|
|
778
|
+
const payment = payments.get(ref);
|
|
779
|
+
if (!payment) throw new Error(`unknown payment ${ref}`);
|
|
780
|
+
return payment;
|
|
781
|
+
}
|
|
782
|
+
__name(expect, "expect");
|
|
783
|
+
function session(payment) {
|
|
784
|
+
return {
|
|
785
|
+
providerRef: payment.ref,
|
|
786
|
+
nextAction: {
|
|
787
|
+
kind: "redirect",
|
|
788
|
+
url: `https://fake.test/pay/${payment.ref}`
|
|
789
|
+
},
|
|
790
|
+
state: payment.state,
|
|
791
|
+
money: {
|
|
792
|
+
amount: payment.amount,
|
|
793
|
+
currency: payment.currency
|
|
794
|
+
},
|
|
795
|
+
raw: {}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
__name(session, "session");
|
|
799
|
+
return {
|
|
800
|
+
adapter,
|
|
801
|
+
mutatingCalls: /* @__PURE__ */ __name(() => [
|
|
802
|
+
...mutatingCalls
|
|
803
|
+
], "mutatingCalls")
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
__name(createFakeAdapter, "createFakeAdapter");
|
|
135
807
|
|
|
136
808
|
// src/order.service.ts
|
|
137
809
|
var import_common2 = require("@nestjs/common");
|
|
@@ -191,12 +863,12 @@ function _ts_decorate2(decorators, target, key, desc) {
|
|
|
191
863
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
192
864
|
}
|
|
193
865
|
__name(_ts_decorate2, "_ts_decorate");
|
|
194
|
-
function
|
|
866
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
195
867
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
196
868
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
197
869
|
}
|
|
198
870
|
}
|
|
199
|
-
__name(
|
|
871
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
200
872
|
function _ts_param(paramIndex, decorator) {
|
|
201
873
|
return function(target, key) {
|
|
202
874
|
decorator(target, key, paramIndex);
|
|
@@ -217,10 +889,10 @@ var OrderService = class {
|
|
|
217
889
|
static {
|
|
218
890
|
__name(this, "OrderService");
|
|
219
891
|
}
|
|
220
|
-
|
|
892
|
+
providers;
|
|
221
893
|
orders;
|
|
222
|
-
constructor(
|
|
223
|
-
this.
|
|
894
|
+
constructor(providers, orders) {
|
|
895
|
+
this.providers = providers;
|
|
224
896
|
this.orders = orders;
|
|
225
897
|
if (!orders) throw new Error("Order persistence must be configured");
|
|
226
898
|
}
|
|
@@ -237,8 +909,8 @@ var OrderService = class {
|
|
|
237
909
|
data: {
|
|
238
910
|
id: params.orderId,
|
|
239
911
|
providerId: params.providerId,
|
|
240
|
-
amount: params.payment.amount,
|
|
241
|
-
currency: params.payment.currency,
|
|
912
|
+
amount: params.payment.money.amount,
|
|
913
|
+
currency: params.payment.money.currency,
|
|
242
914
|
metadata: params.payment.metadata,
|
|
243
915
|
status: "created",
|
|
244
916
|
createdAt: now,
|
|
@@ -248,68 +920,99 @@ var OrderService = class {
|
|
|
248
920
|
return this.initiatePayment(order, params.payment);
|
|
249
921
|
}
|
|
250
922
|
async initiatePayment(order, payment) {
|
|
251
|
-
const
|
|
923
|
+
const session = await this.providers.get(order.providerId).createPayment(payment);
|
|
252
924
|
await this.transition(order.id, [
|
|
253
925
|
"created"
|
|
254
926
|
], "paying", {
|
|
255
|
-
paymentIntentId:
|
|
927
|
+
paymentIntentId: session.providerRef
|
|
256
928
|
});
|
|
257
929
|
return this.getOrder(order.id);
|
|
258
930
|
}
|
|
259
|
-
|
|
931
|
+
/**
|
|
932
|
+
* 扣款。幂等键由调用方给出——同一笔订单重试必须复用同一个键,否则渠道会认为是新支付。
|
|
933
|
+
* 判定不了的结果保持 confirming,交由只读对账解决,绝不在这里重复扣款。
|
|
934
|
+
*/
|
|
935
|
+
async confirmOrder(orderId, idempotencyKey) {
|
|
260
936
|
const order = await this.getOrder(orderId);
|
|
261
937
|
if (!order.paymentIntentId) throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentIntentId`);
|
|
262
938
|
await this.transition(orderId, [
|
|
263
939
|
"paying"
|
|
264
940
|
], "confirming");
|
|
265
|
-
const
|
|
266
|
-
|
|
941
|
+
const session = await this.providers.get(order.providerId).capture(order.paymentIntentId, idempotencyKey);
|
|
942
|
+
await this.applySettlement(orderId, "confirming", session.state, session.providerRef);
|
|
943
|
+
return session;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* 把渠道观察到的结算状态落到订单上。
|
|
947
|
+
* 迁移是否允许由 reconcile() 统一裁决,保证所有渠道遵守同一套安全性质
|
|
948
|
+
* (unknown 不覆盖已知状态、已收款不被迟到的失败回调回退)。
|
|
949
|
+
*/
|
|
950
|
+
async applySettlement(orderId, from, observed, paymentRef) {
|
|
951
|
+
const current = from === "paid" ? "captured" : from === "refunded" ? "refunded" : "created";
|
|
952
|
+
const decision = reconcile(current, observed);
|
|
953
|
+
if (!decision.changed) return;
|
|
954
|
+
if (decision.next === "captured") {
|
|
267
955
|
await this.transition(orderId, [
|
|
268
|
-
|
|
956
|
+
from
|
|
269
957
|
], "paid", {
|
|
270
|
-
paymentId:
|
|
271
|
-
paidAt:
|
|
958
|
+
paymentId: paymentRef,
|
|
959
|
+
paidAt: /* @__PURE__ */ new Date()
|
|
272
960
|
});
|
|
273
|
-
} else if (
|
|
961
|
+
} else if (decision.next === "failed" || decision.next === "canceled") {
|
|
274
962
|
await this.transition(orderId, [
|
|
275
|
-
|
|
963
|
+
from
|
|
276
964
|
], "failed");
|
|
277
|
-
} else if (
|
|
965
|
+
} else if (decision.next === "refunded" || decision.next === "partially_refunded") {
|
|
278
966
|
await this.transition(orderId, [
|
|
279
|
-
|
|
280
|
-
], "refunded"
|
|
967
|
+
from
|
|
968
|
+
], "refunded", {
|
|
969
|
+
refundedAt: /* @__PURE__ */ new Date()
|
|
970
|
+
});
|
|
281
971
|
}
|
|
282
|
-
return result;
|
|
283
972
|
}
|
|
284
|
-
async refundOrder(orderId,
|
|
973
|
+
async refundOrder(orderId, idempotencyKey, options = {}) {
|
|
285
974
|
const order = await this.getOrder(orderId);
|
|
286
975
|
if (!order.paymentId) throw new Error(`\u8BA2\u5355 "${orderId}" \u7F3A\u5C11 paymentId`);
|
|
287
976
|
await this.transition(orderId, [
|
|
288
977
|
"paid"
|
|
289
978
|
], "refunding");
|
|
290
|
-
const result = await this.
|
|
291
|
-
|
|
979
|
+
const result = await this.providers.get(order.providerId).refund({
|
|
980
|
+
providerRef: order.paymentId,
|
|
981
|
+
money: options.money,
|
|
982
|
+
reason: options.reason,
|
|
983
|
+
idempotencyKey
|
|
984
|
+
});
|
|
985
|
+
if (result.state === "refunded") {
|
|
292
986
|
await this.transition(orderId, [
|
|
293
987
|
"refunding"
|
|
294
988
|
], "refunded", {
|
|
295
989
|
refundedAt: /* @__PURE__ */ new Date()
|
|
296
990
|
});
|
|
297
|
-
} else if (result.
|
|
991
|
+
} else if (result.state === "failed") {
|
|
298
992
|
await this.transition(orderId, [
|
|
299
993
|
"refunding"
|
|
300
994
|
], "paid");
|
|
301
995
|
}
|
|
302
996
|
return result;
|
|
303
997
|
}
|
|
304
|
-
|
|
998
|
+
/**
|
|
999
|
+
* 重试失败订单。
|
|
1000
|
+
* idempotencyKey 必须与上一次不同:沿用旧键会让渠道原样返回那笔已失败的支付,
|
|
1001
|
+
* 看起来"重试了"其实什么都没发生。
|
|
1002
|
+
*/
|
|
1003
|
+
async retryOrder(orderId, idempotencyKey) {
|
|
305
1004
|
const order = await this.getOrder(orderId);
|
|
306
1005
|
await this.transition(orderId, [
|
|
307
1006
|
"failed"
|
|
308
1007
|
], "created");
|
|
309
1008
|
return this.initiatePayment(order, {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
1009
|
+
reference: order.id,
|
|
1010
|
+
money: {
|
|
1011
|
+
amount: order.amount,
|
|
1012
|
+
currency: order.currency
|
|
1013
|
+
},
|
|
1014
|
+
metadata: order.metadata,
|
|
1015
|
+
idempotencyKey
|
|
313
1016
|
});
|
|
314
1017
|
}
|
|
315
1018
|
async cancelOrder(orderId) {
|
|
@@ -348,9 +1051,9 @@ var OrderService = class {
|
|
|
348
1051
|
OrderService = _ts_decorate2([
|
|
349
1052
|
(0, import_common2.Injectable)(),
|
|
350
1053
|
_ts_param(1, (0, import_common2.Inject)(ORDER_PERSISTENCE)),
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
typeof
|
|
1054
|
+
_ts_metadata2("design:type", Function),
|
|
1055
|
+
_ts_metadata2("design:paramtypes", [
|
|
1056
|
+
typeof PaymentProviderRegistry === "undefined" ? Object : PaymentProviderRegistry,
|
|
354
1057
|
typeof OrderPersistence === "undefined" ? Object : OrderPersistence
|
|
355
1058
|
])
|
|
356
1059
|
], OrderService);
|
|
@@ -384,12 +1087,12 @@ function _ts_decorate3(decorators, target, key, desc) {
|
|
|
384
1087
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
385
1088
|
}
|
|
386
1089
|
__name(_ts_decorate3, "_ts_decorate");
|
|
387
|
-
function
|
|
1090
|
+
function _ts_metadata3(metadataKey, metadataValue) {
|
|
388
1091
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
389
1092
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
390
1093
|
}
|
|
391
1094
|
}
|
|
392
|
-
__name(
|
|
1095
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
393
1096
|
function _ts_param2(paramIndex, decorator) {
|
|
394
1097
|
return function(target, key) {
|
|
395
1098
|
decorator(target, key, paramIndex);
|
|
@@ -500,10 +1203,11 @@ var PaymentRecoveryService = class _PaymentRecoveryService {
|
|
|
500
1203
|
}
|
|
501
1204
|
}
|
|
502
1205
|
/**
|
|
503
|
-
* 重试失败订单 — 将 failed → created
|
|
1206
|
+
* 重试失败订单 — 将 failed → created 并重新发起支付。
|
|
1207
|
+
* 幂等键由调用方给出且必须与上次不同,否则渠道会原样返回那笔已失败的支付。
|
|
504
1208
|
*/
|
|
505
|
-
retryOrder(orderId) {
|
|
506
|
-
return this.orderService.retryOrder(orderId);
|
|
1209
|
+
retryOrder(orderId, idempotencyKey) {
|
|
1210
|
+
return this.orderService.retryOrder(orderId, idempotencyKey);
|
|
507
1211
|
}
|
|
508
1212
|
getRecoveryAttempts(orderId) {
|
|
509
1213
|
if (!this.attempts) throw new Error("Recovery attempt persistence must be configured");
|
|
@@ -526,8 +1230,8 @@ PaymentRecoveryService = _ts_decorate3([
|
|
|
526
1230
|
_ts_param2(4, (0, import_common3.Optional)()),
|
|
527
1231
|
_ts_param2(4, (0, import_common3.Inject)(RECOVERY_URL_GENERATOR)),
|
|
528
1232
|
_ts_param2(5, (0, import_common3.Optional)()),
|
|
529
|
-
|
|
530
|
-
|
|
1233
|
+
_ts_metadata3("design:type", Function),
|
|
1234
|
+
_ts_metadata3("design:paramtypes", [
|
|
531
1235
|
typeof OrderService === "undefined" ? Object : OrderService,
|
|
532
1236
|
Object,
|
|
533
1237
|
typeof RecoveryMailSender === "undefined" ? Object : RecoveryMailSender,
|
|
@@ -590,24 +1294,20 @@ function configuredProviders() {
|
|
|
590
1294
|
useFactory: /* @__PURE__ */ __name((options) => options.recoveryAttempts ?? null, "useFactory")
|
|
591
1295
|
},
|
|
592
1296
|
{
|
|
593
|
-
provide:
|
|
1297
|
+
provide: PaymentProviderRegistry,
|
|
594
1298
|
inject: [
|
|
595
1299
|
PAYMENT_OPTIONS
|
|
596
1300
|
],
|
|
597
|
-
useFactory: /* @__PURE__ */ __name((options) =>
|
|
598
|
-
const service = new PaymentService();
|
|
599
|
-
for (const provider of options.providers ?? []) service.register(provider);
|
|
600
|
-
return service;
|
|
601
|
-
}, "useFactory")
|
|
1301
|
+
useFactory: /* @__PURE__ */ __name((options) => new PaymentProviderRegistry(options.providers ?? []), "useFactory")
|
|
602
1302
|
},
|
|
603
1303
|
OrderService,
|
|
604
1304
|
PaymentRecoveryService,
|
|
605
1305
|
{
|
|
606
1306
|
provide: PAYMENT_PROVIDERS,
|
|
607
1307
|
inject: [
|
|
608
|
-
|
|
1308
|
+
PaymentProviderRegistry
|
|
609
1309
|
],
|
|
610
|
-
useFactory: /* @__PURE__ */ __name((
|
|
1310
|
+
useFactory: /* @__PURE__ */ __name((registry) => registry.list(), "useFactory")
|
|
611
1311
|
}
|
|
612
1312
|
];
|
|
613
1313
|
}
|
|
@@ -628,7 +1328,7 @@ var ZuckerPaymentModule = class _ZuckerPaymentModule {
|
|
|
628
1328
|
...configuredProviders()
|
|
629
1329
|
],
|
|
630
1330
|
exports: [
|
|
631
|
-
|
|
1331
|
+
PaymentProviderRegistry,
|
|
632
1332
|
OrderService,
|
|
633
1333
|
PaymentRecoveryService
|
|
634
1334
|
]
|
|
@@ -648,7 +1348,7 @@ var ZuckerPaymentModule = class _ZuckerPaymentModule {
|
|
|
648
1348
|
...configuredProviders()
|
|
649
1349
|
],
|
|
650
1350
|
exports: [
|
|
651
|
-
|
|
1351
|
+
PaymentProviderRegistry,
|
|
652
1352
|
OrderService,
|
|
653
1353
|
PaymentRecoveryService
|
|
654
1354
|
]
|
|
@@ -660,62 +1360,6 @@ ZuckerPaymentModule = _ts_decorate4([
|
|
|
660
1360
|
(0, import_common4.Module)({})
|
|
661
1361
|
], ZuckerPaymentModule);
|
|
662
1362
|
|
|
663
|
-
// src/providers/stripe.ts
|
|
664
|
-
var import_node_module = require("module");
|
|
665
|
-
var import_meta = {};
|
|
666
|
-
var requireSdk = (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta.url);
|
|
667
|
-
function createStripeClient(secretKey, options) {
|
|
668
|
-
const StripeSdk = requireSdk("stripe");
|
|
669
|
-
return new StripeSdk(secretKey, options);
|
|
670
|
-
}
|
|
671
|
-
__name(createStripeClient, "createStripeClient");
|
|
672
|
-
function verifyStripeWebhookEvent(client, payload, signature, webhookSecret) {
|
|
673
|
-
return client.webhooks.constructEvent(payload, signature, webhookSecret);
|
|
674
|
-
}
|
|
675
|
-
__name(verifyStripeWebhookEvent, "verifyStripeWebhookEvent");
|
|
676
|
-
function stripeResourceId(value) {
|
|
677
|
-
return typeof value === "string" ? value : value?.id;
|
|
678
|
-
}
|
|
679
|
-
__name(stripeResourceId, "stripeResourceId");
|
|
680
|
-
function observeStripeCheckoutSession(raw) {
|
|
681
|
-
return {
|
|
682
|
-
raw,
|
|
683
|
-
id: raw.id,
|
|
684
|
-
url: raw.url || void 0,
|
|
685
|
-
clientReferenceId: raw.client_reference_id || void 0,
|
|
686
|
-
paymentIntentId: stripeResourceId(raw.payment_intent),
|
|
687
|
-
subscriptionId: stripeResourceId(raw.subscription),
|
|
688
|
-
settlement: raw.payment_status === "paid" ? "paid" : raw.status === "expired" ? "unpaid" : "unknown"
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
__name(observeStripeCheckoutSession, "observeStripeCheckoutSession");
|
|
692
|
-
async function createStripeCheckoutSession(client, params) {
|
|
693
|
-
return observeStripeCheckoutSession(await client.checkout.sessions.create(params));
|
|
694
|
-
}
|
|
695
|
-
__name(createStripeCheckoutSession, "createStripeCheckoutSession");
|
|
696
|
-
async function retrieveStripeCheckoutSession(client, id) {
|
|
697
|
-
return observeStripeCheckoutSession(await client.checkout.sessions.retrieve(id));
|
|
698
|
-
}
|
|
699
|
-
__name(retrieveStripeCheckoutSession, "retrieveStripeCheckoutSession");
|
|
700
|
-
function observeStripePaymentIntent(raw) {
|
|
701
|
-
const charge = typeof raw.latest_charge === "object" ? raw.latest_charge : null;
|
|
702
|
-
return {
|
|
703
|
-
raw,
|
|
704
|
-
id: raw.id,
|
|
705
|
-
chargeId: stripeResourceId(raw.latest_charge) ?? null,
|
|
706
|
-
paymentMethodType: charge?.payment_method_details?.type,
|
|
707
|
-
failureCode: raw.last_payment_error?.code,
|
|
708
|
-
failureMessage: raw.last_payment_error?.message,
|
|
709
|
-
settlement: raw.status === "succeeded" ? "paid" : raw.status === "canceled" ? "unpaid" : "unknown"
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
__name(observeStripePaymentIntent, "observeStripePaymentIntent");
|
|
713
|
-
async function retrieveStripePaymentIntent(client, id, params) {
|
|
714
|
-
const raw = params ? await client.paymentIntents.retrieve(id, params) : await client.paymentIntents.retrieve(id);
|
|
715
|
-
return observeStripePaymentIntent(raw);
|
|
716
|
-
}
|
|
717
|
-
__name(retrieveStripePaymentIntent, "retrieveStripePaymentIntent");
|
|
718
|
-
|
|
719
1363
|
// src/providers/paypal.ts
|
|
720
1364
|
var import_node_module2 = require("module");
|
|
721
1365
|
var import_meta2 = {};
|
|
@@ -730,6 +1374,22 @@ function observePayPalOrder(raw) {
|
|
|
730
1374
|
};
|
|
731
1375
|
}
|
|
732
1376
|
__name(observePayPalOrder, "observePayPalOrder");
|
|
1377
|
+
function captureLinkId(resource) {
|
|
1378
|
+
if (!Array.isArray(resource.links)) return void 0;
|
|
1379
|
+
for (const value of resource.links) {
|
|
1380
|
+
const link = asRecord(value);
|
|
1381
|
+
if (link?.rel !== "up" || typeof link.href !== "string") continue;
|
|
1382
|
+
try {
|
|
1383
|
+
const url = new URL(link.href);
|
|
1384
|
+
if (url.protocol !== "https:" || !/^api(?:-m)?(?:\.sandbox)?\.paypal\.com$/.test(url.hostname)) continue;
|
|
1385
|
+
const match = /^\/v2\/payments\/captures\/([A-Za-z0-9_-]+)$/.exec(url.pathname);
|
|
1386
|
+
if (match) return match[1];
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return void 0;
|
|
1391
|
+
}
|
|
1392
|
+
__name(captureLinkId, "captureLinkId");
|
|
733
1393
|
function asRecord(value) {
|
|
734
1394
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
735
1395
|
}
|
|
@@ -761,6 +1421,42 @@ function decodePayPalWebhookEvent(payload) {
|
|
|
761
1421
|
};
|
|
762
1422
|
}
|
|
763
1423
|
__name(decodePayPalWebhookEvent, "decodePayPalWebhookEvent");
|
|
1424
|
+
function decodePayPalMoneyReturnedEvent(payload) {
|
|
1425
|
+
const raw = asRecord(JSON.parse(payload));
|
|
1426
|
+
if (!raw || typeof raw.event_type !== "string") throw new Error("Invalid PayPal webhook event");
|
|
1427
|
+
const type = raw.event_type;
|
|
1428
|
+
if (type === "PAYMENT.CAPTURE.REFUNDED" || type === "PAYMENT.CAPTURE.REVERSED") {
|
|
1429
|
+
const resource = asRecord(raw.resource);
|
|
1430
|
+
const amount = asRecord(resource?.amount);
|
|
1431
|
+
const related = asRecord(asRecord(resource?.supplementary_data)?.related_ids);
|
|
1432
|
+
const reversal = type === "PAYMENT.CAPTURE.REVERSED";
|
|
1433
|
+
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)) {
|
|
1434
|
+
throw new Error("Invalid PayPal money-returned event");
|
|
1435
|
+
}
|
|
1436
|
+
const status = reversal ? "COMPLETED" : resource.status;
|
|
1437
|
+
if (status !== "PENDING" && status !== "COMPLETED" && status !== "FAILED" && status !== "CANCELLED") {
|
|
1438
|
+
throw new Error("Unknown PayPal refund status");
|
|
1439
|
+
}
|
|
1440
|
+
const captureId = reversal ? resource.id : typeof related?.capture_id === "string" ? related.capture_id : captureLinkId(resource);
|
|
1441
|
+
const orderId = typeof related?.order_id === "string" ? related.order_id : void 0;
|
|
1442
|
+
if (!captureId && !orderId) throw new Error("PayPal refund event has no capture or order identity");
|
|
1443
|
+
return {
|
|
1444
|
+
raw,
|
|
1445
|
+
type,
|
|
1446
|
+
kind: reversal ? "capture-reversed" : "capture-refunded",
|
|
1447
|
+
returned: {
|
|
1448
|
+
id: reversal ? `reversal:${resource.id}` : resource.id,
|
|
1449
|
+
captureId,
|
|
1450
|
+
orderId,
|
|
1451
|
+
amount: amount.value,
|
|
1452
|
+
currency: amount.currency_code,
|
|
1453
|
+
status
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
__name(decodePayPalMoneyReturnedEvent, "decodePayPalMoneyReturnedEvent");
|
|
764
1460
|
var PayPalPaymentTransport = class {
|
|
765
1461
|
static {
|
|
766
1462
|
__name(this, "PayPalPaymentTransport");
|
|
@@ -793,17 +1489,17 @@ var PayPalPaymentTransport = class {
|
|
|
793
1489
|
this.baseUrl = options.mode === "live" ? "https://api.paypal.com" : "https://api.sandbox.paypal.com";
|
|
794
1490
|
this.fetcher = options.fetcher ?? fetch;
|
|
795
1491
|
}
|
|
796
|
-
async createOrder(
|
|
797
|
-
const { result } = await this.orders.createOrder(
|
|
1492
|
+
async createOrder(input2) {
|
|
1493
|
+
const { result } = await this.orders.createOrder(input2);
|
|
798
1494
|
return observePayPalOrder(result);
|
|
799
1495
|
}
|
|
800
1496
|
/** An explicit capture command; never invoked by getOrder or status observation. */
|
|
801
|
-
async captureOrder(
|
|
802
|
-
const { result } = await this.orders.captureOrder(
|
|
1497
|
+
async captureOrder(input2) {
|
|
1498
|
+
const { result } = await this.orders.captureOrder(input2);
|
|
803
1499
|
return observePayPalOrder(result);
|
|
804
1500
|
}
|
|
805
|
-
async getOrder(
|
|
806
|
-
const { result } = await this.orders.getOrder(
|
|
1501
|
+
async getOrder(input2) {
|
|
1502
|
+
const { result } = await this.orders.getOrder(input2);
|
|
807
1503
|
return observePayPalOrder(result);
|
|
808
1504
|
}
|
|
809
1505
|
async getAccessToken() {
|
|
@@ -856,18 +1552,21 @@ var PayPalPaymentTransport = class {
|
|
|
856
1552
|
return false;
|
|
857
1553
|
}
|
|
858
1554
|
}
|
|
859
|
-
async refundCapture(captureId, request) {
|
|
1555
|
+
async refundCapture(captureId, request, requestId) {
|
|
860
1556
|
const accessToken = await this.getAccessToken();
|
|
861
1557
|
const response = await this.fetcher(`${this.baseUrl}/v2/payments/captures/${encodeURIComponent(captureId)}/refund`, {
|
|
862
1558
|
method: "POST",
|
|
863
1559
|
headers: {
|
|
864
1560
|
"Content-Type": "application/json",
|
|
865
|
-
Authorization: `Bearer ${accessToken}
|
|
1561
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1562
|
+
...requestId ? {
|
|
1563
|
+
"PayPal-Request-Id": requestId
|
|
1564
|
+
} : {}
|
|
866
1565
|
},
|
|
867
1566
|
body: JSON.stringify(request)
|
|
868
1567
|
});
|
|
869
1568
|
const result = await response.json();
|
|
870
|
-
if (!response.ok
|
|
1569
|
+
if (!response.ok) throw new Error(`PayPal refund request failed (${response.status})`);
|
|
871
1570
|
return result;
|
|
872
1571
|
}
|
|
873
1572
|
};
|
|
@@ -878,24 +1577,40 @@ var PayPalPaymentTransport = class {
|
|
|
878
1577
|
OrderNotFoundError,
|
|
879
1578
|
OrderService,
|
|
880
1579
|
PAYMENT_PROVIDERS,
|
|
1580
|
+
PAYPAL_SUPPORTED_CURRENCIES,
|
|
881
1581
|
PayPalPaymentTransport,
|
|
882
1582
|
PaymentEvents,
|
|
1583
|
+
PaymentProviderRegistry,
|
|
883
1584
|
PaymentRecoveryService,
|
|
884
|
-
PaymentService,
|
|
885
1585
|
RECOVERY_ATTEMPT_STORE,
|
|
886
1586
|
RECOVERY_MAIL_SENDER,
|
|
887
1587
|
RECOVERY_URL_GENERATOR,
|
|
888
1588
|
RECOVERY_USER_LOOKUP,
|
|
1589
|
+
SETTLEMENT_STATES,
|
|
889
1590
|
ZuckerPaymentModule,
|
|
1591
|
+
adapterSupportsCurrency,
|
|
1592
|
+
checkPaymentAdapterContract,
|
|
1593
|
+
createFakeAdapter,
|
|
1594
|
+
createPayPalAdapter,
|
|
1595
|
+
createStripeAdapter,
|
|
890
1596
|
createStripeCheckoutSession,
|
|
891
1597
|
createStripeClient,
|
|
1598
|
+
currencyExponent,
|
|
1599
|
+
decodePayPalMoneyReturnedEvent,
|
|
892
1600
|
decodePayPalWebhookEvent,
|
|
893
1601
|
friendlyErrorReason,
|
|
1602
|
+
fromMinorUnits,
|
|
1603
|
+
isMoneyMoved,
|
|
1604
|
+
isTerminal,
|
|
894
1605
|
observePayPalOrder,
|
|
1606
|
+
observePayPalState,
|
|
895
1607
|
observeStripeCheckoutSession,
|
|
896
1608
|
observeStripePaymentIntent,
|
|
1609
|
+
observeStripeState,
|
|
1610
|
+
reconcile,
|
|
897
1611
|
retrieveStripeCheckoutSession,
|
|
898
1612
|
retrieveStripePaymentIntent,
|
|
899
1613
|
stripeResourceId,
|
|
1614
|
+
toMinorUnits,
|
|
900
1615
|
verifyStripeWebhookEvent
|
|
901
1616
|
});
|