vona-module-pay-stripe 5.0.4 → 5.0.6

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.
@@ -1,23 +1,36 @@
1
1
  import type { IDecoratorPayProviderOptions, IPayProviderClientOptions, IPayProviderClientRecord, IPayProviderExecute, IPayProviderPaymentInput, IPayProviderPaymentSnapshot, IPayProviderRefundInput, IPayProviderRefundSnapshot, IPayProviderVerifiedWebhook, IPayProviderWebhookInput } from 'vona-module-a-pay';
2
2
  import { BeanBase } from 'vona';
3
+ import type { IStripeGateway } from '../lib/stripeGateway.ts';
3
4
  export interface IPayProviderStripeClientRecord extends IPayProviderClientRecord {
4
5
  }
5
6
  export interface IPayProviderStripeClientOptions extends IPayProviderClientOptions {
6
7
  secretCredential: string | undefined;
7
8
  secretWebhook: string | undefined;
9
+ gateway?: IStripeGateway;
8
10
  }
9
11
  export interface IPayProviderOptionsStripe extends IDecoratorPayProviderOptions<IPayProviderStripeClientRecord, IPayProviderStripeClientOptions> {
10
12
  }
11
13
  export declare class PayProviderStripe extends BeanBase implements IPayProviderExecute<IPayProviderStripeClientOptions> {
12
- startPayment(_input: IPayProviderPaymentInput, _clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderPaymentSnapshot>;
13
- confirmPayment(_input: IPayProviderPaymentInput, _clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderPaymentSnapshot>;
14
- queryPayment(_input: IPayProviderPaymentInput, _clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderPaymentSnapshot>;
15
- createRefund(_input: IPayProviderRefundInput, _clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderRefundSnapshot>;
16
- verifyWebhook(_input: IPayProviderWebhookInput, _clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderVerifiedWebhook>;
17
- }
18
- declare module 'vona-module-a-pay' {
19
- interface IPayProviderRecord {
20
- 'pay-stripe:stripe': IPayProviderOptionsStripe;
21
- }
14
+ startPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderPaymentSnapshot>;
15
+ confirmPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderPaymentSnapshot>;
16
+ queryPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderPaymentSnapshot>;
17
+ createRefund(input: IPayProviderRefundInput, clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderRefundSnapshot>;
18
+ queryRefund(input: IPayProviderRefundInput, clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderRefundSnapshot>;
19
+ verifyWebhook(input: IPayProviderWebhookInput, clientOptions: IPayProviderStripeClientOptions): Promise<IPayProviderVerifiedWebhook>;
20
+ private _mapCheckoutWebhook;
21
+ private _mapPaymentIntentWebhook;
22
+ private _mapRefundWebhook;
23
+ private _inputFromCheckoutSession;
24
+ private _mapCheckoutSession;
25
+ private _mapPaymentIntent;
26
+ private _mapRefund;
27
+ private _paymentMetadata;
28
+ private _refundMetadata;
29
+ private _gateway;
30
+ private _gatewayOptions;
31
+ private _matchesEnvironment;
32
+ private _isDefinitiveRefundRejection;
33
+ private _throwTerminalPaymentFailure;
34
+ private _throwGatewayError;
22
35
  }
23
36
  //# sourceMappingURL=payProvider.stripe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"payProvider.stripe.d.ts","sourceRoot":"","sources":["../../src/bean/payProvider.stripe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,QAAQ,EAAU,MAAM,MAAM,CAAC;AAGxC,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;CAAG;AAEnF,MAAM,WAAW,+BAAgC,SAAQ,yBAAyB;IAChF,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAID,MAAM,WAAW,yBAA0B,SAAQ,4BAA4B,CAC7E,8BAA8B,EAC9B,+BAA+B,CAChC;CAAG;AAEJ,qBAoBa,iBACX,SAAQ,QACR,YAAW,mBAAmB,CAAC,+BAA+B,CAAC;IAEzD,YAAY,CAChB,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;IAIjC,cAAc,CAClB,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;IAIjC,YAAY,CAChB,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;IAIjC,YAAY,CAChB,MAAM,EAAE,uBAAuB,EAC/B,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,0BAA0B,CAAC;IAIhC,aAAa,CACjB,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;CAGxC;AAED,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAiB,kBAAkB;QACjC,mBAAmB,EAAE,yBAAyB,CAAC;KAChD;CACF"}
1
+ {"version":3,"file":"payProvider.stripe.d.ts","sourceRoot":"","sources":["../../src/bean/payProvider.stripe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAU,MAAM,MAAM,CAAC;AAGxC,OAAO,KAAK,EAAE,cAAc,EAAyB,MAAM,yBAAyB,CAAC;AAIrF,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;CAAG;AAEnF,MAAM,WAAW,+BAAgC,SAAQ,yBAAyB;IAChF,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAID,MAAM,WAAW,yBAA0B,SAAQ,4BAA4B,CAC7E,8BAA8B,EAC9B,+BAA+B,CAChC;CAAG;AAEJ,qBAqBa,iBACX,SAAQ,QACR,YAAW,mBAAmB,CAAC,+BAA+B,CAAC;IAEzD,YAAY,CAChB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IAsCjC,cAAc,CAClB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IAIjC,YAAY,CAChB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IAUjC,YAAY,CAChB,KAAK,EAAE,uBAAuB,EAC9B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,0BAA0B,CAAC;IAmBhC,WAAW,CACf,KAAK,EAAE,uBAAuB,EAC9B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,0BAA0B,CAAC;IAUhC,aAAa,CACjB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;YA4BzB,mBAAmB;YAkCnB,wBAAwB;YAgDxB,iBAAiB;YAsCjB,yBAAyB;IA4BvC,OAAO,CAAC,mBAAmB;IAmD3B,OAAO,CAAC,iBAAiB;IAmCzB,OAAO,CAAC,UAAU;IAyBlB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IAIpC,OAAO,CAAC,4BAA4B;IAUpC,OAAO,CAAC,kBAAkB;CAI3B"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export type { IPayProviderStripeClientOptions } from './bean/payProvider.stripe.ts';
1
2
  export * from './types/index.ts';
2
3
  export * from './.metadata/index.ts';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC;AACpF,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -1,31 +1,498 @@
1
1
  import { useApp, BeanInfo, BeanBase, BeanScopeBase } from 'vona';
2
2
  import { __decorate } from 'tslib';
3
- import { PayProvider } from 'vona-module-a-pay';
3
+ import { PayProvider, ProviderOperationFailure } from 'vona-module-a-pay';
4
+ import Stripe from 'stripe';
4
5
  import { Scope } from 'vona-module-a-bean';
5
6
 
7
+ const StripeRequestTimeoutMilliseconds = 15_000;
8
+ class StripeGatewayError extends Error {
9
+ constructor(status, message) {
10
+ super(message);
11
+ this.status = void 0;
12
+ this.status = status;
13
+ }
14
+ }
15
+ function createStripeGateway() {
16
+ return {
17
+ async createCheckoutSession(options, input) {
18
+ const request = input;
19
+ return await createClient(options).checkout.sessions.create(request.body, {
20
+ idempotencyKey: request.idempotencyKey
21
+ });
22
+ },
23
+ async retrieveCheckoutSession(options, input) {
24
+ const request = input;
25
+ return await createClient(options).checkout.sessions.retrieve(request.id, {
26
+ expand: ['payment_intent']
27
+ });
28
+ },
29
+ async createRefund(options, input) {
30
+ const request = input;
31
+ return await createClient(options).refunds.create(request.body, {
32
+ idempotencyKey: request.idempotencyKey
33
+ });
34
+ },
35
+ async retrieveRefund(options, input) {
36
+ const request = input;
37
+ return await createClient(options).refunds.retrieve(request.id);
38
+ },
39
+ async constructWebhookEvent(options, input) {
40
+ const signature = getHeader(input.headers, 'stripe-signature');
41
+ if (!input.rawBody || !signature) {
42
+ throw new StripeGatewayError(401, 'Stripe webhook signature is invalid');
43
+ }
44
+ try {
45
+ return createClient(options).webhooks.constructEvent(input.rawBody, signature, options.webhookSecret);
46
+ } catch {
47
+ throw new StripeGatewayError(401, 'Stripe webhook signature is invalid');
48
+ }
49
+ }
50
+ };
51
+ }
52
+ function createClient(options) {
53
+ return new Stripe(options.secretKey, {
54
+ maxNetworkRetries: 0,
55
+ timeout: StripeRequestTimeoutMilliseconds
56
+ });
57
+ }
58
+ function getHeader(headers, key) {
59
+ const value = headers[key];
60
+ return Array.isArray(value) ? value[0] : value;
61
+ }
62
+
6
63
  const app = useApp();
7
64
  let PayProviderStripe = class PayProviderStripe extends BeanBase {
8
- async startPayment(_input, _clientOptions) {
9
- this.app.throw(501, 'Stripe provider is not implemented');
65
+ async startPayment(input, clientOptions) {
66
+ if (input.providerOrderId) return await this.queryPayment(input, clientOptions);
67
+ if (!input.returnUrl || !input.cancelUrl) {
68
+ this.app.throw(500, 'Stripe payment requires return and cancel URLs');
69
+ }
70
+ const gatewayOptions = this._gatewayOptions(clientOptions);
71
+ assertStripeMoney(input.amountMinor, input.currency);
72
+ const body = {
73
+ mode: 'payment',
74
+ success_url: input.returnUrl,
75
+ cancel_url: input.cancelUrl,
76
+ client_reference_id: input.providerCorrelationReference,
77
+ metadata: this._paymentMetadata(input),
78
+ payment_intent_data: {
79
+ metadata: this._paymentMetadata(input)
80
+ },
81
+ managed_payments: {
82
+ enabled: false
83
+ },
84
+ line_items: [{
85
+ quantity: 1,
86
+ price_data: {
87
+ currency: input.currency.toLowerCase(),
88
+ unit_amount: input.amountMinor,
89
+ product_data: {
90
+ name: `Payment ${input.providerInvoiceReference}`
91
+ }
92
+ }
93
+ }]
94
+ };
95
+ let checkoutSession;
96
+ try {
97
+ checkoutSession = await this._gateway(clientOptions).createCheckoutSession(gatewayOptions, {
98
+ idempotencyKey: input.idempotencyKey,
99
+ body
100
+ });
101
+ } catch (error) {
102
+ this._throwTerminalPaymentFailure(error);
103
+ }
104
+ return this._mapCheckoutSession(checkoutSession, input, clientOptions, true);
105
+ }
106
+ async confirmPayment(input, clientOptions) {
107
+ return await this.queryPayment(input, clientOptions);
108
+ }
109
+ async queryPayment(input, clientOptions) {
110
+ const gatewayOptions = this._gatewayOptions(clientOptions);
111
+ if (!input.providerOrderId) this.app.throw(409, 'Stripe payment has no Checkout Session');
112
+ const checkoutSession = await this._gateway(clientOptions).retrieveCheckoutSession(gatewayOptions, {
113
+ id: input.providerOrderId
114
+ });
115
+ return this._mapCheckoutSession(checkoutSession, input, clientOptions);
116
+ }
117
+ async createRefund(input, clientOptions) {
118
+ const gatewayOptions = this._gatewayOptions(clientOptions);
119
+ assertStripeMoney(input.amountMinor, input.currency);
120
+ try {
121
+ const refund = await this._gateway(clientOptions).createRefund(gatewayOptions, {
122
+ idempotencyKey: input.idempotencyKey,
123
+ body: {
124
+ payment_intent: input.providerCaptureId,
125
+ amount: input.amountMinor,
126
+ metadata: this._refundMetadata(input)
127
+ }
128
+ });
129
+ return this._mapRefund(refund, input);
130
+ } catch (error) {
131
+ if (this._isDefinitiveRefundRejection(error)) return {
132
+ state: 'failed'
133
+ };
134
+ throw error;
135
+ }
136
+ }
137
+ async queryRefund(input, clientOptions) {
138
+ const gatewayOptions = this._gatewayOptions(clientOptions);
139
+ if (!input.providerRefundId) this.app.throw(409, 'Stripe refund has no provider refund identifier');
140
+ const refund = await this._gateway(clientOptions).retrieveRefund(gatewayOptions, {
141
+ id: input.providerRefundId
142
+ });
143
+ return this._mapRefund(refund, input);
144
+ }
145
+ async verifyWebhook(input, clientOptions) {
146
+ const gatewayOptions = this._gatewayOptions(clientOptions);
147
+ let event;
148
+ try {
149
+ event = asRecord(await this._gateway(clientOptions).constructWebhookEvent(gatewayOptions, input));
150
+ } catch (error) {
151
+ this._throwGatewayError(error);
152
+ }
153
+ const eventId = readString(event.id);
154
+ const eventType = readString(event.type);
155
+ const resource = asRecord(asRecord(event.data).object);
156
+ if (!eventId || !eventType || !Object.keys(resource).length) {
157
+ this.app.throw(400, 'Stripe webhook is invalid');
158
+ }
159
+ if (eventType.startsWith('checkout.session.')) {
160
+ return await this._mapCheckoutWebhook(eventId, eventType, resource, clientOptions);
161
+ }
162
+ if (eventType.startsWith('payment_intent.')) {
163
+ return await this._mapPaymentIntentWebhook(eventId, eventType, resource);
164
+ }
165
+ if (eventType.startsWith('refund.')) {
166
+ return await this._mapRefundWebhook(eventId, eventType, resource);
167
+ }
168
+ return {
169
+ eventId,
170
+ eventType,
171
+ ignored: true,
172
+ summary: {
173
+ amountMinor: 0,
174
+ currency: 'USD'
175
+ }
176
+ };
177
+ }
178
+ async _mapCheckoutWebhook(eventId, eventType, checkoutSession, clientOptions) {
179
+ const input = await this._inputFromCheckoutSession(checkoutSession);
180
+ const payment = await this._mapCheckoutSession(checkoutSession, input, clientOptions);
181
+ if (eventType === 'checkout.session.async_payment_failed') {
182
+ return {
183
+ eventId,
184
+ eventType,
185
+ paymentSessionId: input.paymentSessionId,
186
+ payment: {
187
+ ...payment,
188
+ state: 'failed'
189
+ },
190
+ summary: {
191
+ amountMinor: input.amountMinor,
192
+ currency: input.currency
193
+ }
194
+ };
195
+ }
196
+ if (!['succeeded', 'failed', 'cancelled'].includes(payment.state)) {
197
+ return {
198
+ eventId,
199
+ eventType,
200
+ ignored: true,
201
+ summary: {
202
+ amountMinor: input.amountMinor,
203
+ currency: input.currency
204
+ }
205
+ };
206
+ }
207
+ return {
208
+ eventId,
209
+ eventType,
210
+ paymentSessionId: input.paymentSessionId,
211
+ payment,
212
+ summary: {
213
+ amountMinor: input.amountMinor,
214
+ currency: input.currency
215
+ }
216
+ };
217
+ }
218
+ async _mapPaymentIntentWebhook(eventId, eventType, paymentIntent) {
219
+ const metadata = asRecord(paymentIntent.metadata);
220
+ const providerCorrelationReference = readString(metadata.providerCorrelationReference);
221
+ const providerInvoiceReference = readString(metadata.providerInvoiceReference);
222
+ if (!providerCorrelationReference || !providerInvoiceReference) {
223
+ this.app.throw(400, 'Stripe PaymentIntent webhook is not correlated');
224
+ }
225
+ const session = await this.$scope.pay.model.paymentSession.get({
226
+ providerCorrelationReference
227
+ });
228
+ if (!session || session.providerInvoiceReference !== providerInvoiceReference) {
229
+ this.app.throw(400, 'Stripe PaymentIntent webhook is not correlated');
230
+ }
231
+ const payment = this._mapPaymentIntent(paymentIntent, {
232
+ paymentSessionId: session.id,
233
+ businessReference: session.businessReference,
234
+ providerInvoiceReference: session.providerInvoiceReference,
235
+ providerCorrelationReference: session.providerCorrelationReference,
236
+ idempotencyKey: '',
237
+ amountMinor: session.amountMinor,
238
+ currency: session.currency,
239
+ providerOrderId: session.providerOrderId
240
+ });
241
+ const terminalPayment = eventType === 'payment_intent.payment_failed' ? {
242
+ ...payment,
243
+ state: 'failed'
244
+ } : payment;
245
+ if (!['succeeded', 'failed', 'cancelled'].includes(terminalPayment.state)) {
246
+ return {
247
+ eventId,
248
+ eventType,
249
+ ignored: true,
250
+ summary: {
251
+ amountMinor: session.amountMinor,
252
+ currency: session.currency
253
+ }
254
+ };
255
+ }
256
+ return {
257
+ eventId,
258
+ eventType,
259
+ paymentSessionId: session.id,
260
+ payment: terminalPayment,
261
+ summary: {
262
+ amountMinor: session.amountMinor,
263
+ currency: session.currency
264
+ }
265
+ };
266
+ }
267
+ async _mapRefundWebhook(eventId, eventType, refundResource) {
268
+ const refundId = readString(refundResource.id);
269
+ const metadata = asRecord(refundResource.metadata);
270
+ const providerCorrelationReference = readString(metadata.providerCorrelationReference);
271
+ if (!refundId || !providerCorrelationReference) {
272
+ this.app.throw(400, 'Stripe refund webhook is not correlated');
273
+ }
274
+ const refund = await this.$scope.pay.model.refundOperation.get({
275
+ providerCorrelationReference
276
+ });
277
+ if (!refund) this.app.throw(400, 'Stripe refund webhook is not correlated');
278
+ const session = await this.$scope.pay.model.paymentSession.getById(refund.paymentSessionId);
279
+ if (!session?.providerCaptureId) this.app.throw(409, 'Stripe refund webhook payment conflicts');
280
+ const snapshot = this._mapRefund(refundResource, {
281
+ paymentSessionId: session.id,
282
+ refundOperationId: refund.id,
283
+ businessReference: refund.businessReference,
284
+ providerInvoiceReference: refund.providerInvoiceReference,
285
+ providerCorrelationReference: refund.providerCorrelationReference,
286
+ idempotencyKey: '',
287
+ amountMinor: refund.amountMinor,
288
+ currency: refund.currency,
289
+ providerCaptureId: session.providerCaptureId,
290
+ providerRefundId: refundId
291
+ });
292
+ return {
293
+ eventId,
294
+ eventType,
295
+ refundOperationId: refund.id,
296
+ refund: snapshot,
297
+ summary: {
298
+ amountMinor: refund.amountMinor,
299
+ currency: refund.currency
300
+ }
301
+ };
302
+ }
303
+ async _inputFromCheckoutSession(checkoutSession) {
304
+ const metadata = asRecord(checkoutSession.metadata);
305
+ const providerCorrelationReference = readString(metadata.providerCorrelationReference);
306
+ const providerInvoiceReference = readString(metadata.providerInvoiceReference);
307
+ const providerOrderId = readString(checkoutSession.id);
308
+ if (!providerCorrelationReference || !providerInvoiceReference || !providerOrderId) {
309
+ this.app.throw(400, 'Stripe Checkout Session is not correlated');
310
+ }
311
+ const session = await this.$scope.pay.model.paymentSession.get({
312
+ providerCorrelationReference
313
+ });
314
+ if (!session || session.providerInvoiceReference !== providerInvoiceReference) {
315
+ this.app.throw(400, 'Stripe Checkout Session is not correlated');
316
+ }
317
+ return {
318
+ paymentSessionId: session.id,
319
+ businessReference: session.businessReference,
320
+ providerInvoiceReference: session.providerInvoiceReference,
321
+ providerCorrelationReference: session.providerCorrelationReference,
322
+ idempotencyKey: '',
323
+ amountMinor: session.amountMinor,
324
+ currency: session.currency,
325
+ providerOrderId
326
+ };
327
+ }
328
+ _mapCheckoutSession(checkoutSession, input, clientOptions, requireRedirect = false) {
329
+ const record = asRecord(checkoutSession);
330
+ const providerOrderId = readString(record.id);
331
+ const amountMinor = readInteger(record.amount_total);
332
+ const currency = readString(record.currency)?.toUpperCase();
333
+ const metadata = asRecord(record.metadata);
334
+ if (!providerOrderId || input.providerOrderId && input.providerOrderId !== providerOrderId || amountMinor !== input.amountMinor || currency !== input.currency || readString(record.client_reference_id) !== input.providerCorrelationReference || readString(metadata.providerCorrelationReference) !== input.providerCorrelationReference || readString(metadata.providerInvoiceReference) !== input.providerInvoiceReference || !this._matchesEnvironment(record, clientOptions)) {
335
+ this.app.throw(409, 'Stripe Checkout Session facts conflict with the payment session');
336
+ }
337
+ const paymentIntent = asRecord(record.payment_intent);
338
+ const paymentIntentId = readString(record.payment_intent) ?? readString(paymentIntent.id) ?? undefined;
339
+ if (paymentIntentId && Object.keys(paymentIntent).length) {
340
+ return this._mapPaymentIntent(paymentIntent, {
341
+ ...input,
342
+ providerOrderId
343
+ }, providerOrderId);
344
+ }
345
+ if (record.status === 'expired') {
346
+ return {
347
+ state: 'cancelled',
348
+ providerPaymentId: providerOrderId,
349
+ providerOrderId
350
+ };
351
+ }
352
+ if (record.payment_status === 'paid' && paymentIntentId) {
353
+ return {
354
+ state: 'succeeded',
355
+ providerPaymentId: paymentIntentId,
356
+ providerOrderId,
357
+ providerCaptureId: paymentIntentId,
358
+ nextAction: {
359
+ kind: 'completed'
360
+ }
361
+ };
362
+ }
363
+ const url = readString(record.url);
364
+ if (requireRedirect && !url) this.app.throw(502, 'Stripe did not return a Checkout URL');
365
+ return {
366
+ state: 'requires_action',
367
+ providerPaymentId: providerOrderId,
368
+ providerOrderId,
369
+ ...(url && {
370
+ nextAction: {
371
+ kind: 'redirect',
372
+ url
373
+ }
374
+ })
375
+ };
10
376
  }
11
- async confirmPayment(_input, _clientOptions) {
12
- this.app.throw(501, 'Stripe provider is not implemented');
377
+ _mapPaymentIntent(paymentIntent, input, providerOrderId = input.providerOrderId) {
378
+ const providerPaymentId = readString(paymentIntent.id);
379
+ const amountMinor = readInteger(paymentIntent.amount);
380
+ const currency = readString(paymentIntent.currency)?.toUpperCase();
381
+ const metadata = asRecord(paymentIntent.metadata);
382
+ if (!providerPaymentId || amountMinor !== input.amountMinor || currency !== input.currency || readString(metadata.providerCorrelationReference) !== input.providerCorrelationReference || readString(metadata.providerInvoiceReference) !== input.providerInvoiceReference) {
383
+ this.app.throw(409, 'Stripe PaymentIntent facts conflict with the payment session');
384
+ }
385
+ const status = readString(paymentIntent.status);
386
+ if (status === 'succeeded') {
387
+ return {
388
+ state: 'succeeded',
389
+ providerPaymentId,
390
+ providerOrderId,
391
+ providerCaptureId: providerPaymentId,
392
+ nextAction: {
393
+ kind: 'completed'
394
+ }
395
+ };
396
+ }
397
+ if (status === 'processing') return {
398
+ state: 'processing',
399
+ providerPaymentId,
400
+ providerOrderId
401
+ };
402
+ if (status === 'canceled') return {
403
+ state: 'cancelled',
404
+ providerPaymentId,
405
+ providerOrderId
406
+ };
407
+ if (status === 'requires_payment_method') return {
408
+ state: 'failed',
409
+ providerPaymentId,
410
+ providerOrderId
411
+ };
412
+ return {
413
+ state: 'requires_action',
414
+ providerPaymentId,
415
+ providerOrderId
416
+ };
417
+ }
418
+ _mapRefund(refund, input) {
419
+ const record = asRecord(refund);
420
+ const providerRefundId = readString(record.id);
421
+ const amountMinor = readInteger(record.amount);
422
+ const currency = readString(record.currency)?.toUpperCase();
423
+ const metadata = asRecord(record.metadata);
424
+ const paymentIntent = readString(record.payment_intent);
425
+ if (!providerRefundId || input.providerRefundId && input.providerRefundId !== providerRefundId || amountMinor !== input.amountMinor || currency !== input.currency || paymentIntent !== input.providerCaptureId || readString(metadata.providerCorrelationReference) !== input.providerCorrelationReference || readString(metadata.providerInvoiceReference) !== input.providerInvoiceReference) {
426
+ this.app.throw(409, 'Stripe refund facts conflict with the refund operation');
427
+ }
428
+ const status = readString(record.status);
429
+ if (status === 'succeeded') return {
430
+ state: 'succeeded',
431
+ providerRefundId
432
+ };
433
+ if (status === 'failed') return {
434
+ state: 'failed',
435
+ providerRefundId
436
+ };
437
+ if (status === 'canceled') return {
438
+ state: 'cancelled',
439
+ providerRefundId
440
+ };
441
+ return {
442
+ state: 'pending',
443
+ providerRefundId
444
+ };
445
+ }
446
+ _paymentMetadata(input) {
447
+ return {
448
+ paymentSessionId: String(input.paymentSessionId),
449
+ providerCorrelationReference: input.providerCorrelationReference,
450
+ providerInvoiceReference: input.providerInvoiceReference
451
+ };
452
+ }
453
+ _refundMetadata(input) {
454
+ return {
455
+ paymentSessionId: String(input.paymentSessionId),
456
+ refundOperationId: String(input.refundOperationId),
457
+ providerCorrelationReference: input.providerCorrelationReference,
458
+ providerInvoiceReference: input.providerInvoiceReference
459
+ };
13
460
  }
14
- async queryPayment(_input, _clientOptions) {
15
- this.app.throw(501, 'Stripe provider is not implemented');
461
+ _gateway(clientOptions) {
462
+ return clientOptions.gateway ?? createStripeGateway();
16
463
  }
17
- async createRefund(_input, _clientOptions) {
18
- this.app.throw(501, 'Stripe provider is not implemented');
464
+ _gatewayOptions(clientOptions) {
465
+ const secretKey = clientOptions.secretCredential;
466
+ const webhookSecret = clientOptions.secretWebhook;
467
+ if (!secretKey || !webhookSecret) this.app.throw(503, 'Stripe is not fully configured');
468
+ return {
469
+ secretKey,
470
+ webhookSecret
471
+ };
19
472
  }
20
- async verifyWebhook(_input, _clientOptions) {
21
- this.app.throw(501, 'Stripe provider is not implemented');
473
+ _matchesEnvironment(value, clientOptions) {
474
+ const livemode = value.livemode;
475
+ return typeof livemode !== 'boolean' || livemode === (clientOptions.environment === 'live');
476
+ }
477
+ _isDefinitiveRefundRejection(error) {
478
+ return isStripeDefinitiveRejection(error);
479
+ }
480
+ _throwTerminalPaymentFailure(error) {
481
+ if (isStripeDefinitiveRejection(error)) {
482
+ throw new ProviderOperationFailure('stripe_request_rejected', 'Stripe rejected the payment request');
483
+ }
484
+ throw error;
485
+ }
486
+ _throwGatewayError(error) {
487
+ if (error instanceof StripeGatewayError) this.app.throw(error.status, error.message);
488
+ throw error;
22
489
  }
23
490
  };
24
491
  PayProviderStripe = __decorate([PayProvider({
25
492
  base: {
26
493
  capabilities: {
27
- redirectCheckout: false,
28
- embeddedCheckout: true,
494
+ redirectCheckout: true,
495
+ embeddedCheckout: false,
29
496
  automaticCapture: true,
30
497
  manualCapture: false,
31
498
  refunds: true,
@@ -35,14 +502,34 @@ PayProviderStripe = __decorate([PayProvider({
35
502
  },
36
503
  clients: {
37
504
  default: {
38
- environment: 'sandbox',
505
+ environment: app.meta.env.STRIPE_ENVIRONMENT,
39
506
  secretCredential: app.meta.env.STRIPE_SECRET_KEY,
40
- secretWebhook: app.meta.env.STRIPE_WEBHOOK_SECRET
507
+ secretWebhook: app.meta.env.STRIPE_WEBHOOK_SECRET,
508
+ merchantReference: app.meta.env.STRIPE_MERCHANT_REFERENCE
41
509
  }
42
510
  }
43
511
  }), BeanInfo({
44
512
  module: "pay-stripe"
45
513
  })], PayProviderStripe);
514
+ function isStripeDefinitiveRejection(error) {
515
+ if (error instanceof StripeGatewayError) return error.status >= 400 && error.status < 500;
516
+ const type = error?.type;
517
+ return ['StripeInvalidRequestError', 'StripeIdempotencyError', 'StripeAuthenticationError', 'StripePermissionError'].includes(type);
518
+ }
519
+ function assertStripeMoney(amountMinor, currency) {
520
+ if (!Number.isSafeInteger(amountMinor) || amountMinor <= 0 || currency !== 'USD') {
521
+ throw new Error('Stripe supports only positive USD minor-unit amounts');
522
+ }
523
+ }
524
+ function asRecord(value) {
525
+ return value && typeof value === 'object' ? value : {};
526
+ }
527
+ function readString(value) {
528
+ return typeof value === 'string' && value ? value : undefined;
529
+ }
530
+ function readInteger(value) {
531
+ return typeof value === 'number' && Number.isSafeInteger(value) ? value : undefined;
532
+ }
46
533
 
47
534
  let ScopeModulePayStripe = class ScopeModulePayStripe extends BeanScopeBase {};
48
535
  ScopeModulePayStripe = __decorate([Scope(), BeanInfo({