better-ship 0.21.0 → 0.22.0
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/stripe/client.d.ts +13 -0
- package/dist/stripe/client.d.ts.map +1 -0
- package/dist/stripe/client.js +20 -0
- package/dist/stripe/client.js.map +1 -0
- package/dist/stripe/event-map.d.ts +14 -0
- package/dist/stripe/event-map.d.ts.map +1 -0
- package/dist/stripe/event-map.js +19 -0
- package/dist/stripe/event-map.js.map +1 -0
- package/dist/stripe/payment-service.d.ts +4 -5
- package/dist/stripe/payment-service.d.ts.map +1 -1
- package/dist/stripe/payment-service.error.js +1 -1
- package/dist/stripe/payment-service.error.js.map +1 -1
- package/dist/stripe/payment-service.js +11 -35
- package/dist/stripe/payment-service.js.map +1 -1
- package/dist/stripe/request.d.ts +9 -0
- package/dist/stripe/request.d.ts.map +1 -0
- package/dist/stripe/request.js +21 -0
- package/dist/stripe/request.js.map +1 -0
- package/dist/testing/payment-service.d.ts.map +1 -1
- package/dist/testing/payment-service.js +12 -12
- package/dist/testing/payment-service.js.map +1 -1
- package/dist/testing/stripe.d.ts +21 -0
- package/dist/testing/stripe.d.ts.map +1 -0
- package/dist/testing/stripe.js +33 -0
- package/dist/testing/stripe.js.map +1 -0
- package/package.json +5 -1
- package/src/infrastructure/stripe/client.ts +16 -0
- package/src/infrastructure/stripe/event-map.ts +25 -0
- package/src/infrastructure/stripe/payment-service.error.ts +2 -1
- package/src/infrastructure/stripe/payment-service.ts +15 -39
- package/src/infrastructure/stripe/request.ts +17 -0
- package/src/infrastructure/testing/payment-service-contract.ts +11 -14
- package/src/infrastructure/testing/stripe-fixture.ts +42 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
//#region src/infrastructure/stripe/client.d.ts
|
|
3
|
+
/** Default SDK retries; client configuration and individual requests can override this value. */
|
|
4
|
+
export declare const STRIPE_REQUEST_OPTIONS: Readonly<{
|
|
5
|
+
maxNetworkRetries: 2;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Construct a native Stripe client with fetch transport and two SDK retries by default.
|
|
9
|
+
* Native configuration overrides these defaults. Inject the client into application services.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createStripeClient(secretKey: string, options?: Stripe.StripeConfig): Stripe;
|
|
12
|
+
//#endregion
|
|
13
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/infrastructure/stripe/client.ts"],"mappings":";;;qBAGa,wBAAsB;EAAA;;;;;;wBAMnB,mBAAmB,mBAAmB,UAAS,OAAO,eAAoB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
|
|
3
|
+
//#region src/infrastructure/stripe/client.ts
|
|
4
|
+
/** Default SDK retries; client configuration and individual requests can override this value. */
|
|
5
|
+
const STRIPE_REQUEST_OPTIONS = Object.freeze({ maxNetworkRetries: 2 });
|
|
6
|
+
/**
|
|
7
|
+
* Construct a native Stripe client with fetch transport and two SDK retries by default.
|
|
8
|
+
* Native configuration overrides these defaults. Inject the client into application services.
|
|
9
|
+
*/
|
|
10
|
+
function createStripeClient(secretKey, options = {}) {
|
|
11
|
+
return new Stripe(secretKey, {
|
|
12
|
+
httpClient: Stripe.createFetchHttpClient(),
|
|
13
|
+
...STRIPE_REQUEST_OPTIONS,
|
|
14
|
+
...options
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { STRIPE_REQUEST_OPTIONS, createStripeClient };
|
|
20
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../src/infrastructure/stripe/client.ts"],"sourcesContent":["import Stripe from 'stripe'\n\n/** Default SDK retries; client configuration and individual requests can override this value. */\nexport const STRIPE_REQUEST_OPTIONS = Object.freeze({ maxNetworkRetries: 2 })\n\n/**\n * Construct a native Stripe client with fetch transport and two SDK retries by default.\n * Native configuration overrides these defaults. Inject the client into application services.\n */\nexport function createStripeClient(secretKey: string, options: Stripe.StripeConfig = {}): Stripe {\n return new Stripe(secretKey, {\n httpClient: Stripe.createFetchHttpClient(),\n ...STRIPE_REQUEST_OPTIONS,\n ...options,\n })\n}\n"],"mappings":";;;;AAGA,MAAa,yBAAyB,OAAO,OAAO,EAAE,mBAAmB,EAAE,CAAC;;;;;AAM5E,SAAgB,mBAAmB,WAAmB,UAA+B,CAAC,GAAW;CAC/F,OAAO,IAAI,OAAO,WAAW;EAC3B,YAAY,OAAO,sBAAsB;EACzC,GAAG;EACH,GAAG;CACL,CAAC;AACH"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
//#region src/infrastructure/stripe/event-map.d.ts
|
|
3
|
+
type EventObject<K extends Stripe.Event['type']> = Extract<Stripe.Event, {
|
|
4
|
+
type: K;
|
|
5
|
+
}>['data']['object'];
|
|
6
|
+
/**
|
|
7
|
+
* Convert verified Stripe events with callbacks typed to each event's object.
|
|
8
|
+
* Unmapped events return undefined. Applications own validation and payment policy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stripeEventMap<T>(map: { [K in Stripe.Event['type']]?: (object: EventObject<K>) => T; }): (event: Stripe.Event) => T | undefined;
|
|
11
|
+
/** Identify one Stripe event delivery; different events for one payment retain different IDs. */
|
|
12
|
+
export declare function stripeEventId(event: Stripe.Event): string;
|
|
13
|
+
//#endregion
|
|
14
|
+
//# sourceMappingURL=event-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-map.d.ts","names":[],"sources":["../../src/infrastructure/stripe/event-map.ts"],"mappings":";;KAEK,YAAY,UAAU,OAAO,iBAAiB,QACjD,OAAO;EACL,MAAM;;;;;;wBAOM,eAAe,GAAG,QAC/B,KAAK,OAAO,kBAAkB,QAAQ,YAAY,OAAO,QACvD,OAAO,OAAO,UAAU;;wBASb,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/infrastructure/stripe/event-map.ts
|
|
2
|
+
/**
|
|
3
|
+
* Convert verified Stripe events with callbacks typed to each event's object.
|
|
4
|
+
* Unmapped events return undefined. Applications own validation and payment policy.
|
|
5
|
+
*/
|
|
6
|
+
function stripeEventMap(map) {
|
|
7
|
+
const handlers = map;
|
|
8
|
+
return (event) => {
|
|
9
|
+
return handlers[event.type]?.(event.data.object);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** Identify one Stripe event delivery; different events for one payment retain different IDs. */
|
|
13
|
+
function stripeEventId(event) {
|
|
14
|
+
return `stripe:event:${event.id}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { stripeEventId, stripeEventMap };
|
|
19
|
+
//# sourceMappingURL=event-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-map.js","names":[],"sources":["../../src/infrastructure/stripe/event-map.ts"],"sourcesContent":["import type Stripe from 'stripe'\n\ntype EventObject<K extends Stripe.Event['type']> = Extract<\n Stripe.Event,\n { type: K }\n>['data']['object']\n\n/**\n * Convert verified Stripe events with callbacks typed to each event's object.\n * Unmapped events return undefined. Applications own validation and payment policy.\n */\nexport function stripeEventMap<T>(map: {\n [K in Stripe.Event['type']]?: (object: EventObject<K>) => T\n}): (event: Stripe.Event) => T | undefined {\n const handlers: Partial<Record<Stripe.Event['type'], (object: never) => T>> = map\n return (event) => {\n // SAFETY: The map binds each event type to its object; indexed lookup loses that correlation.\n return handlers[event.type]?.(event.data.object as never)\n }\n}\n\n/** Identify one Stripe event delivery; different events for one payment retain different IDs. */\nexport function stripeEventId(event: Stripe.Event): string {\n return `stripe:event:${event.id}`\n}\n"],"mappings":";;;;;AAWA,SAAgB,eAAkB,KAES;CACzC,MAAM,WAAwE;CAC9E,QAAQ,UAAU;EAEhB,OAAO,SAAS,MAAM,KAAK,GAAG,MAAM,KAAK,MAAe;CAC1D;AACF;;AAGA,SAAgB,cAAc,OAA6B;CACzD,OAAO,gBAAgB,MAAM;AAC/B"}
|
|
@@ -14,7 +14,7 @@ export type CheckoutResult = Awaited<ReturnType<Stripe['checkout']['sessions']['
|
|
|
14
14
|
export type PaymentResult = Awaited<ReturnType<Stripe['paymentIntents']['create']>>;
|
|
15
15
|
/**
|
|
16
16
|
* Stripe payment operations, with native types and stable keys for mutations.
|
|
17
|
-
* Rejects with PaymentServiceError
|
|
17
|
+
* Rejects with PaymentServiceError for Stripe failures; non-Stripe failures propagate unchanged.
|
|
18
18
|
*/
|
|
19
19
|
export interface IPaymentService {
|
|
20
20
|
/** Create a customer; the application owns persistence and reuse of the customer link. */
|
|
@@ -23,14 +23,14 @@ export interface IPaymentService {
|
|
|
23
23
|
createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
24
24
|
/** Create checkout in setup mode. The application records consent and verifies the saved method's owner. */
|
|
25
25
|
setupPaymentMethod(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
26
|
-
/** Confirm a saved method off-session.
|
|
26
|
+
/** Confirm a saved method off-session. PaymentServiceError retains card errors in cause. */
|
|
27
27
|
chargeSavedMethod(input: SavedMethodCharge, options: PaymentRequestOptions): Promise<PaymentResult>;
|
|
28
28
|
/** Retrieve an existing payment without starting another payment attempt. */
|
|
29
29
|
getPayment(paymentId: string, params?: Stripe.PaymentIntentRetrieveParams, options?: Stripe.RequestOptions): Promise<PaymentResult>;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Ported from Typist's StripeClient request boundary; the native SDK now owns retries.
|
|
33
|
-
*
|
|
33
|
+
* The injected client owns retry and timeout defaults. Native request options can override them.
|
|
34
34
|
*/
|
|
35
35
|
export declare class StripePaymentService implements IPaymentService {
|
|
36
36
|
private readonly stripe;
|
|
@@ -42,11 +42,10 @@ export declare class StripePaymentService implements IPaymentService {
|
|
|
42
42
|
createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
43
43
|
/** Force setup mode after caller parameters; this operation does not charge the customer. */
|
|
44
44
|
setupPaymentMethod(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
45
|
-
/** Force confirmation and off-session use;
|
|
45
|
+
/** Force confirmation and off-session use; retain native card failures in PaymentServiceError.cause. */
|
|
46
46
|
chargeSavedMethod(input: SavedMethodCharge, options: PaymentRequestOptions): Promise<PaymentResult>;
|
|
47
47
|
/** Preserve every native status and response field when reconciling an existing payment. */
|
|
48
48
|
getPayment(paymentId: string, params?: Stripe.PaymentIntentRetrieveParams, options?: Stripe.RequestOptions): Promise<PaymentResult>;
|
|
49
|
-
private request;
|
|
50
49
|
}
|
|
51
50
|
//#endregion
|
|
52
51
|
//# sourceMappingURL=payment-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-service.d.ts","names":[],"sources":["../../src/infrastructure/stripe/payment-service.ts"],"mappings":";;;YAKY,wBAAwB,OAAO,iBACzC,SAAS,KAAK,OAAO;;YAGX,gBAAgB,KAAK,OAAO,SAAS;;YAGrC,oBAAoB,KAAK,OAAO,wDAC1C,SAAS,KAAK,OAAO;;YAGX,iBAAiB,QAAQ,WAAW;;YAGpC,iBAAiB,QAAQ,WAAW;;YAGpC,gBAAgB,QAAQ,WAAW;;;;;iBAM9B;;EAEf,eACE,OAAO,OAAO,sBACd,SAAS,wBACR,QAAQ;;EAEX,eAAe,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAE9E,mBAAmB,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAElF,kBACE,OAAO,mBACP,SAAS,wBACR,QAAQ;;EAEX,WACE,mBACA,SAAS,OAAO,6BAChB,UAAU,OAAO,iBAChB,QAAQ;;;;;;qBAOA,gCAAgC;mBAEd;;EAA7B,YAA6B,QAAQ;;EAGrC,eACE,OAAO,OAAO,sBACd,SAAS,wBACR,QAAQ;;
|
|
1
|
+
{"version":3,"file":"payment-service.d.ts","names":[],"sources":["../../src/infrastructure/stripe/payment-service.ts"],"mappings":";;;YAKY,wBAAwB,OAAO,iBACzC,SAAS,KAAK,OAAO;;YAGX,gBAAgB,KAAK,OAAO,SAAS;;YAGrC,oBAAoB,KAAK,OAAO,wDAC1C,SAAS,KAAK,OAAO;;YAGX,iBAAiB,QAAQ,WAAW;;YAGpC,iBAAiB,QAAQ,WAAW;;YAGpC,gBAAgB,QAAQ,WAAW;;;;;iBAM9B;;EAEf,eACE,OAAO,OAAO,sBACd,SAAS,wBACR,QAAQ;;EAEX,eAAe,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAE9E,mBAAmB,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAElF,kBACE,OAAO,mBACP,SAAS,wBACR,QAAQ;;EAEX,WACE,mBACA,SAAS,OAAO,6BAChB,UAAU,OAAO,iBAChB,QAAQ;;;;;;qBAOA,gCAAgC;mBAEd;;EAA7B,YAA6B,QAAQ;;EAGrC,eACE,OAAO,OAAO,sBACd,SAAS,wBACR,QAAQ;;EAKX,eAAe,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAO9E,mBACE,OAAO,eACP,SAAS,wBACR,QAAQ;;EAOX,kBACE,OAAO,mBACP,SAAS,wBACR,QAAQ;;EAOX,WACE,mBACA,SAAS,OAAO,6BAChB,UAAU,OAAO,iBAChB,QAAQ"}
|
|
@@ -18,7 +18,7 @@ function classifyStripeError(error) {
|
|
|
18
18
|
if (error.headers?.["stripe-should-retry"] === "false") return "terminal";
|
|
19
19
|
const status = error.statusCode;
|
|
20
20
|
if (status !== void 0 && status >= 400 && status < 500 && status !== 409 && status !== 429) return "terminal";
|
|
21
|
-
if (error instanceof Stripe.errors.StripeAuthenticationError || error instanceof Stripe.errors.StripePermissionError || error instanceof Stripe.errors.StripeInvalidRequestError || error instanceof Stripe.errors.StripeIdempotencyError) return "terminal";
|
|
21
|
+
if (error instanceof Stripe.errors.StripeAuthenticationError || error instanceof Stripe.errors.StripePermissionError || error instanceof Stripe.errors.StripeInvalidRequestError || error instanceof Stripe.errors.StripeIdempotencyError || error instanceof Stripe.errors.StripeCardError) return "terminal";
|
|
22
22
|
if (error.headers?.["stripe-should-retry"] === "true") return "transient";
|
|
23
23
|
if (status === 409 || status === 429 || status !== void 0 && status >= 500) return "transient";
|
|
24
24
|
if (error instanceof Stripe.errors.StripeConnectionError || error instanceof Stripe.errors.StripeAPIError || error instanceof Stripe.errors.StripeRateLimitError) return "transient";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-service.error.js","names":[],"sources":["../../src/infrastructure/stripe/payment-service.error.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { AppError, type FailureClassification } from '@/core'\n\n/** A final Stripe request failure. The caller logs once; native details remain in cause. */\nexport class PaymentServiceError extends AppError {\n readonly _tag = 'PaymentServiceError'\n readonly classification: FailureClassification\n\n /** Classify the final SDK error without scheduling another payment attempt. */\n constructor(\n readonly operation: string,\n cause: Stripe.errors.StripeError,\n ) {\n super(`Stripe request failed: ${operation}`, { cause })\n this.classification = classifyStripeError(cause)\n }\n}\n\nfunction classifyStripeError(error: Stripe.errors.StripeError): FailureClassification {\n if (error.headers?.['stripe-should-retry'] === 'false') return 'terminal'\n const status = error.statusCode\n if (status !== undefined && status >= 400 && status < 500 && status !== 409 && status !== 429)\n return 'terminal'\n if (\n error instanceof Stripe.errors.StripeAuthenticationError ||\n error instanceof Stripe.errors.StripePermissionError ||\n error instanceof Stripe.errors.StripeInvalidRequestError ||\n error instanceof Stripe.errors.StripeIdempotencyError\n )\n return 'terminal'\n if (error.headers?.['stripe-should-retry'] === 'true') return 'transient'\n if (status === 409 || status === 429 || (status !== undefined && status >= 500))\n return 'transient'\n if (\n error instanceof Stripe.errors.StripeConnectionError ||\n error instanceof Stripe.errors.StripeAPIError ||\n error instanceof Stripe.errors.StripeRateLimitError\n )\n return 'transient'\n return 'unknown'\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cAAyC,SAAS;CAMrC;CALX,AAAS,OAAO;CAChB,AAAS;;CAGT,YACE,AAAS,WACT,OACA;EACA,MAAM,0BAA0B,aAAa,EAAE,MAAM,CAAC;EAH7C;EAIT,KAAK,iBAAiB,oBAAoB,KAAK;CACjD;AACF;AAEA,SAAS,oBAAoB,OAAyD;CACpF,IAAI,MAAM,UAAU,2BAA2B,SAAS,OAAO;CAC/D,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,UAAa,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW,KACxF,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,
|
|
1
|
+
{"version":3,"file":"payment-service.error.js","names":[],"sources":["../../src/infrastructure/stripe/payment-service.error.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { AppError, type FailureClassification } from '@/core'\n\n/** A final Stripe request failure. The caller logs once; native details remain in cause. */\nexport class PaymentServiceError extends AppError {\n readonly _tag = 'PaymentServiceError'\n readonly classification: FailureClassification\n\n /** Classify the final SDK error without scheduling another payment attempt. */\n constructor(\n readonly operation: string,\n cause: Stripe.errors.StripeError,\n ) {\n super(`Stripe request failed: ${operation}`, { cause })\n this.classification = classifyStripeError(cause)\n }\n}\n\nfunction classifyStripeError(error: Stripe.errors.StripeError): FailureClassification {\n if (error.headers?.['stripe-should-retry'] === 'false') return 'terminal'\n const status = error.statusCode\n if (status !== undefined && status >= 400 && status < 500 && status !== 409 && status !== 429)\n return 'terminal'\n if (\n error instanceof Stripe.errors.StripeAuthenticationError ||\n error instanceof Stripe.errors.StripePermissionError ||\n error instanceof Stripe.errors.StripeInvalidRequestError ||\n error instanceof Stripe.errors.StripeIdempotencyError ||\n error instanceof Stripe.errors.StripeCardError\n )\n return 'terminal'\n if (error.headers?.['stripe-should-retry'] === 'true') return 'transient'\n if (status === 409 || status === 429 || (status !== undefined && status >= 500))\n return 'transient'\n if (\n error instanceof Stripe.errors.StripeConnectionError ||\n error instanceof Stripe.errors.StripeAPIError ||\n error instanceof Stripe.errors.StripeRateLimitError\n )\n return 'transient'\n return 'unknown'\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cAAyC,SAAS;CAMrC;CALX,AAAS,OAAO;CAChB,AAAS;;CAGT,YACE,AAAS,WACT,OACA;EACA,MAAM,0BAA0B,aAAa,EAAE,MAAM,CAAC;EAH7C;EAIT,KAAK,iBAAiB,oBAAoB,KAAK;CACjD;AACF;AAEA,SAAS,oBAAoB,OAAyD;CACpF,IAAI,MAAM,UAAU,2BAA2B,SAAS,OAAO;CAC/D,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,UAAa,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW,KACxF,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,0BAC/B,iBAAiB,OAAO,OAAO,iBAE/B,OAAO;CACT,IAAI,MAAM,UAAU,2BAA2B,QAAQ,OAAO;CAC9D,IAAI,WAAW,OAAO,WAAW,OAAQ,WAAW,UAAa,UAAU,KACzE,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,kBAC/B,iBAAiB,OAAO,OAAO,sBAE/B,OAAO;CACT,OAAO;AACT"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Stripe from "stripe";
|
|
1
|
+
import { stripeRequest } from "./request.js";
|
|
3
2
|
|
|
4
3
|
//#region src/infrastructure/stripe/payment-service.ts
|
|
5
4
|
/**
|
|
6
5
|
* Ported from Typist's StripeClient request boundary; the native SDK now owns retries.
|
|
7
|
-
*
|
|
6
|
+
* The injected client owns retry and timeout defaults. Native request options can override them.
|
|
8
7
|
*/
|
|
9
8
|
var StripePaymentService = class {
|
|
10
9
|
stripe;
|
|
@@ -14,56 +13,33 @@ var StripePaymentService = class {
|
|
|
14
13
|
}
|
|
15
14
|
/** Create a customer with a stable application key and preserve the native response. */
|
|
16
15
|
createCustomer(input, options) {
|
|
17
|
-
return
|
|
18
|
-
maxNetworkRetries: 2,
|
|
19
|
-
...options
|
|
20
|
-
}));
|
|
16
|
+
return stripeRequest("customers.create", () => this.stripe.customers.create(input, options));
|
|
21
17
|
}
|
|
22
18
|
/** Force payment mode after caller parameters; return the native Checkout Session. */
|
|
23
19
|
createCheckout(input, options) {
|
|
24
|
-
return
|
|
20
|
+
return stripeRequest("checkout.sessions.create", () => this.stripe.checkout.sessions.create({
|
|
25
21
|
...input,
|
|
26
22
|
mode: "payment"
|
|
27
|
-
},
|
|
28
|
-
maxNetworkRetries: 2,
|
|
29
|
-
...options
|
|
30
|
-
}));
|
|
23
|
+
}, options));
|
|
31
24
|
}
|
|
32
25
|
/** Force setup mode after caller parameters; this operation does not charge the customer. */
|
|
33
26
|
setupPaymentMethod(input, options) {
|
|
34
|
-
return
|
|
27
|
+
return stripeRequest("checkout.sessions.create", () => this.stripe.checkout.sessions.create({
|
|
35
28
|
...input,
|
|
36
29
|
mode: "setup"
|
|
37
|
-
},
|
|
38
|
-
maxNetworkRetries: 2,
|
|
39
|
-
...options
|
|
40
|
-
}));
|
|
30
|
+
}, options));
|
|
41
31
|
}
|
|
42
|
-
/** Force confirmation and off-session use;
|
|
32
|
+
/** Force confirmation and off-session use; retain native card failures in PaymentServiceError.cause. */
|
|
43
33
|
chargeSavedMethod(input, options) {
|
|
44
|
-
return
|
|
34
|
+
return stripeRequest("paymentIntents.create", () => this.stripe.paymentIntents.create({
|
|
45
35
|
...input,
|
|
46
36
|
confirm: true,
|
|
47
37
|
off_session: true
|
|
48
|
-
},
|
|
49
|
-
maxNetworkRetries: 2,
|
|
50
|
-
...options
|
|
51
|
-
}));
|
|
38
|
+
}, options));
|
|
52
39
|
}
|
|
53
40
|
/** Preserve every native status and response field when reconciling an existing payment. */
|
|
54
41
|
getPayment(paymentId, params, options) {
|
|
55
|
-
return
|
|
56
|
-
maxNetworkRetries: 2,
|
|
57
|
-
...options
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
async request(operation, work) {
|
|
61
|
-
try {
|
|
62
|
-
return await work();
|
|
63
|
-
} catch (cause) {
|
|
64
|
-
if (!(cause instanceof Stripe.errors.StripeError) || cause instanceof Stripe.errors.StripeCardError) throw cause;
|
|
65
|
-
throw new PaymentServiceError(operation, cause);
|
|
66
|
-
}
|
|
42
|
+
return stripeRequest("paymentIntents.retrieve", () => this.stripe.paymentIntents.retrieve(paymentId, params, options));
|
|
67
43
|
}
|
|
68
44
|
};
|
|
69
45
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-service.js","names":[],"sources":["../../src/infrastructure/stripe/payment-service.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport {
|
|
1
|
+
{"version":3,"file":"payment-service.js","names":[],"sources":["../../src/infrastructure/stripe/payment-service.ts"],"sourcesContent":["import type Stripe from 'stripe'\n\nimport { stripeRequest } from './request.ts'\n\n/** Supply the same key and parameters when repeating one durable payment attempt. */\nexport type PaymentRequestOptions = Stripe.RequestOptions &\n Required<Pick<Stripe.RequestOptions, 'idempotencyKey'>>\n\n/** Native Checkout parameters; the service selects payment or setup mode. */\nexport type CheckoutInput = Omit<Stripe.Checkout.SessionCreateParams, 'mode'>\n\n/** A saved customer method; the service confirms the payment off-session. */\nexport type SavedMethodCharge = Omit<Stripe.PaymentIntentCreateParams, 'confirm' | 'off_session'> &\n Required<Pick<Stripe.PaymentIntentCreateParams, 'customer' | 'payment_method'>>\n\n/** Native customer response, including Stripe request metadata. */\nexport type CustomerResult = Awaited<ReturnType<Stripe['customers']['create']>>\n\n/** Native Checkout response. Its URL can be null; this does not confirm payment. */\nexport type CheckoutResult = Awaited<ReturnType<Stripe['checkout']['sessions']['create']>>\n\n/** Native PaymentIntent response. Applications decide what each status permits. */\nexport type PaymentResult = Awaited<ReturnType<Stripe['paymentIntents']['create']>>\n\n/**\n * Stripe payment operations, with native types and stable keys for mutations.\n * Rejects with PaymentServiceError for Stripe failures; non-Stripe failures propagate unchanged.\n */\nexport interface IPaymentService {\n /** Create a customer; the application owns persistence and reuse of the customer link. */\n createCustomer(\n input: Stripe.CustomerCreateParams,\n options: PaymentRequestOptions,\n ): Promise<CustomerResult>\n /** Create one-time payment checkout; application fulfillment requires verified payment confirmation. */\n createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>\n /** Create checkout in setup mode. The application records consent and verifies the saved method's owner. */\n setupPaymentMethod(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>\n /** Confirm a saved method off-session. PaymentServiceError retains card errors in cause. */\n chargeSavedMethod(\n input: SavedMethodCharge,\n options: PaymentRequestOptions,\n ): Promise<PaymentResult>\n /** Retrieve an existing payment without starting another payment attempt. */\n getPayment(\n paymentId: string,\n params?: Stripe.PaymentIntentRetrieveParams,\n options?: Stripe.RequestOptions,\n ): Promise<PaymentResult>\n}\n\n/**\n * Ported from Typist's StripeClient request boundary; the native SDK now owns retries.\n * The injected client owns retry and timeout defaults. Native request options can override them.\n */\nexport class StripePaymentService implements IPaymentService {\n /** Inject a native SDK instance; its transport, credentials, and API version remain caller-owned. */\n constructor(private readonly stripe: Stripe) {}\n\n /** Create a customer with a stable application key and preserve the native response. */\n createCustomer(\n input: Stripe.CustomerCreateParams,\n options: PaymentRequestOptions,\n ): Promise<CustomerResult> {\n return stripeRequest('customers.create', () => this.stripe.customers.create(input, options))\n }\n\n /** Force payment mode after caller parameters; return the native Checkout Session. */\n createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult> {\n return stripeRequest('checkout.sessions.create', () =>\n this.stripe.checkout.sessions.create({ ...input, mode: 'payment' }, options),\n )\n }\n\n /** Force setup mode after caller parameters; this operation does not charge the customer. */\n setupPaymentMethod(\n input: CheckoutInput,\n options: PaymentRequestOptions,\n ): Promise<CheckoutResult> {\n return stripeRequest('checkout.sessions.create', () =>\n this.stripe.checkout.sessions.create({ ...input, mode: 'setup' }, options),\n )\n }\n\n /** Force confirmation and off-session use; retain native card failures in PaymentServiceError.cause. */\n chargeSavedMethod(\n input: SavedMethodCharge,\n options: PaymentRequestOptions,\n ): Promise<PaymentResult> {\n return stripeRequest('paymentIntents.create', () =>\n this.stripe.paymentIntents.create({ ...input, confirm: true, off_session: true }, options),\n )\n }\n\n /** Preserve every native status and response field when reconciling an existing payment. */\n getPayment(\n paymentId: string,\n params?: Stripe.PaymentIntentRetrieveParams,\n options?: Stripe.RequestOptions,\n ): Promise<PaymentResult> {\n return stripeRequest('paymentIntents.retrieve', () =>\n this.stripe.paymentIntents.retrieve(paymentId, params, options),\n )\n }\n}\n"],"mappings":";;;;;;;AAuDA,IAAa,uBAAb,MAA6D;CAE9B;;CAA7B,YAAY,AAAiB,QAAgB;EAAhB;CAAiB;;CAG9C,eACE,OACA,SACyB;EACzB,OAAO,cAAc,0BAA0B,KAAK,OAAO,UAAU,OAAO,OAAO,OAAO,CAAC;CAC7F;;CAGA,eAAe,OAAsB,SAAyD;EAC5F,OAAO,cAAc,kCACnB,KAAK,OAAO,SAAS,SAAS,OAAO;GAAE,GAAG;GAAO,MAAM;EAAU,GAAG,OAAO,CAC7E;CACF;;CAGA,mBACE,OACA,SACyB;EACzB,OAAO,cAAc,kCACnB,KAAK,OAAO,SAAS,SAAS,OAAO;GAAE,GAAG;GAAO,MAAM;EAAQ,GAAG,OAAO,CAC3E;CACF;;CAGA,kBACE,OACA,SACwB;EACxB,OAAO,cAAc,+BACnB,KAAK,OAAO,eAAe,OAAO;GAAE,GAAG;GAAO,SAAS;GAAM,aAAa;EAAK,GAAG,OAAO,CAC3F;CACF;;CAGA,WACE,WACA,QACA,SACwB;EACxB,OAAO,cAAc,iCACnB,KAAK,OAAO,eAAe,SAAS,WAAW,QAAQ,OAAO,CAChE;CACF;AACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/infrastructure/stripe/request.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Invoke one SDK operation; the SDK owns retries configured on its client or request.
|
|
4
|
+
* Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.
|
|
5
|
+
* Non-Stripe failures propagate unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export declare function stripeRequest<T>(operation: string, work: () => Promise<T>): Promise<T>;
|
|
8
|
+
//#endregion
|
|
9
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","names":[],"sources":["../../src/infrastructure/stripe/request.ts"],"mappings":";;;;;;wBASsB,cAAc,GAAG,mBAAmB,YAAY,QAAQ,KAAK,QAAQ"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PaymentServiceError } from "./payment-service.error.js";
|
|
2
|
+
import Stripe from "stripe";
|
|
3
|
+
|
|
4
|
+
//#region src/infrastructure/stripe/request.ts
|
|
5
|
+
/**
|
|
6
|
+
* Invoke one SDK operation; the SDK owns retries configured on its client or request.
|
|
7
|
+
* Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.
|
|
8
|
+
* Non-Stripe failures propagate unchanged.
|
|
9
|
+
*/
|
|
10
|
+
async function stripeRequest(operation, work) {
|
|
11
|
+
try {
|
|
12
|
+
return await work();
|
|
13
|
+
} catch (cause) {
|
|
14
|
+
if (!(cause instanceof Stripe.errors.StripeError)) throw cause;
|
|
15
|
+
throw new PaymentServiceError(operation, cause);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { stripeRequest };
|
|
21
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","names":[],"sources":["../../src/infrastructure/stripe/request.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { PaymentServiceError } from './payment-service.error.ts'\n\n/**\n * Invoke one SDK operation; the SDK owns retries configured on its client or request.\n * Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.\n * Non-Stripe failures propagate unchanged.\n */\nexport async function stripeRequest<T>(operation: string, work: () => Promise<T>): Promise<T> {\n try {\n return await work()\n } catch (cause) {\n if (!(cause instanceof Stripe.errors.StripeError)) throw cause\n throw new PaymentServiceError(operation, cause)\n }\n}\n"],"mappings":";;;;;;;;;AASA,eAAsB,cAAiB,WAAmB,MAAoC;CAC5F,IAAI;EACF,OAAO,MAAM,KAAK;CACpB,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,OAAO,OAAO,cAAc,MAAM;EACzD,MAAM,IAAI,oBAAoB,WAAW,KAAK;CAChD;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-service.d.ts","names":[],"sources":["../../src/infrastructure/testing/payment-service-contract.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"payment-service.d.ts","names":[],"sources":["../../src/infrastructure/testing/payment-service-contract.ts"],"mappings":";;;;;;;wBAYgB,uBACd,OAAO,cAAc,WAAW,wBAChC,SAAS,QAAQ,WAAW"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { PaymentServiceError } from "../stripe/payment-service.error.js";
|
|
2
|
+
import { stripeFixture } from "./stripe.js";
|
|
1
3
|
import Stripe from "stripe";
|
|
2
|
-
import { deepStrictEqual, equal, rejects } from "node:assert/strict";
|
|
4
|
+
import { deepStrictEqual, equal, ok, rejects } from "node:assert/strict";
|
|
3
5
|
|
|
4
6
|
//#region src/infrastructure/testing/payment-service-contract.ts
|
|
5
7
|
/**
|
|
@@ -39,7 +41,7 @@ function paymentServiceContract(test, create) {
|
|
|
39
41
|
status: "requires_capture"
|
|
40
42
|
});
|
|
41
43
|
});
|
|
42
|
-
test("card failures retain the native error
|
|
44
|
+
test("card failures retain the native error in cause", async () => {
|
|
43
45
|
const fixture = paymentFixture(create, Response.json({ error: {
|
|
44
46
|
type: "card_error",
|
|
45
47
|
code: "authentication_required",
|
|
@@ -50,23 +52,21 @@ function paymentServiceContract(test, create) {
|
|
|
50
52
|
currency: "usd",
|
|
51
53
|
customer: "cus_contract",
|
|
52
54
|
payment_method: "pm_contract"
|
|
53
|
-
}, { idempotencyKey: "charge:contract" }),
|
|
55
|
+
}, { idempotencyKey: "charge:contract" }), (error) => {
|
|
56
|
+
ok(error instanceof PaymentServiceError);
|
|
57
|
+
ok(error.cause instanceof Stripe.errors.StripeCardError);
|
|
58
|
+
equal(error.cause.code, "authentication_required");
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
54
61
|
});
|
|
55
62
|
}
|
|
56
63
|
function paymentFixture(create, response = Response.json({
|
|
57
64
|
id: "pi_contract",
|
|
58
65
|
status: "requires_capture"
|
|
59
66
|
})) {
|
|
60
|
-
const requests =
|
|
67
|
+
const { stripe, requests } = stripeFixture(() => response.clone());
|
|
61
68
|
return {
|
|
62
|
-
service: create(
|
|
63
|
-
const request = new Request(input, init);
|
|
64
|
-
requests.push({
|
|
65
|
-
headers: request.headers,
|
|
66
|
-
body: new URLSearchParams(new TextDecoder().decode(await request.arrayBuffer()))
|
|
67
|
-
});
|
|
68
|
-
return response.clone();
|
|
69
|
-
}) })),
|
|
69
|
+
service: create(stripe),
|
|
70
70
|
requests
|
|
71
71
|
};
|
|
72
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-service.js","names":[],"sources":["../../src/infrastructure/testing/payment-service-contract.ts"],"sourcesContent":["import { deepStrictEqual, equal, rejects } from 'node:assert/strict'\n\nimport Stripe from 'stripe'\n\nimport type { IPaymentService } from '../stripe/payment-service.ts'\n\n/**\n * Test a Stripe payment adapter through native fetch injection without live payments.\n * Each case constructs a fresh SDK instance and adapter; no Vitest dependency is required.\n */\nexport function paymentServiceContract(\n test: (name: string, run: () => Promise<void>) => void,\n create: (stripe: Stripe) => IPaymentService,\n): void {\n test('payment checkout selects payment mode and retains the supplied key', async () => {\n const fixture = paymentFixture(create)\n await fixture.service.createCheckout(\n { customer: 'cus_contract' },\n { idempotencyKey: 'checkout:contract' },\n )\n equal(fixture.requests[0]?.body.get('mode'), 'payment')\n equal(fixture.requests[0]?.headers.get('idempotency-key'), 'checkout:contract')\n })\n\n test('payment method setup selects setup mode', async () => {\n const fixture = paymentFixture(create)\n await fixture.service.setupPaymentMethod(\n { customer: 'cus_contract' },\n { idempotencyKey: 'setup:contract' },\n )\n equal(fixture.requests[0]?.body.get('mode'), 'setup')\n })\n\n test('saved method payments confirm off-session', async () => {\n const fixture = paymentFixture(create)\n await fixture.service.chargeSavedMethod(\n { amount: 1000, currency: 'usd', customer: 'cus_contract', payment_method: 'pm_contract' },\n { idempotencyKey: 'charge:contract' },\n )\n equal(fixture.requests[0]?.body.get('confirm'), 'true')\n equal(fixture.requests[0]?.body.get('off_session'), 'true')\n })\n\n test('payment reads preserve native statuses and response fields', async () => {\n const fixture = paymentFixture(create)\n const payment = await fixture.service.getPayment('pi_contract')\n deepStrictEqual(\n { id: payment.id, status: payment.status },\n { id: 'pi_contract', status: 'requires_capture' },\n )\n })\n\n test('card failures retain the native error
|
|
1
|
+
{"version":3,"file":"payment-service.js","names":[],"sources":["../../src/infrastructure/testing/payment-service-contract.ts"],"sourcesContent":["import { deepStrictEqual, equal, ok, rejects } from 'node:assert/strict'\n\nimport Stripe from 'stripe'\n\nimport { PaymentServiceError } from '../stripe/payment-service.error.ts'\nimport type { IPaymentService } from '../stripe/payment-service.ts'\nimport { stripeFixture } from './stripe-fixture.ts'\n\n/**\n * Test a Stripe payment adapter through native fetch injection without live payments.\n * Each case constructs a fresh SDK instance and adapter; no Vitest dependency is required.\n */\nexport function paymentServiceContract(\n test: (name: string, run: () => Promise<void>) => void,\n create: (stripe: Stripe) => IPaymentService,\n): void {\n test('payment checkout selects payment mode and retains the supplied key', async () => {\n const fixture = paymentFixture(create)\n await fixture.service.createCheckout(\n { customer: 'cus_contract' },\n { idempotencyKey: 'checkout:contract' },\n )\n equal(fixture.requests[0]?.body.get('mode'), 'payment')\n equal(fixture.requests[0]?.headers.get('idempotency-key'), 'checkout:contract')\n })\n\n test('payment method setup selects setup mode', async () => {\n const fixture = paymentFixture(create)\n await fixture.service.setupPaymentMethod(\n { customer: 'cus_contract' },\n { idempotencyKey: 'setup:contract' },\n )\n equal(fixture.requests[0]?.body.get('mode'), 'setup')\n })\n\n test('saved method payments confirm off-session', async () => {\n const fixture = paymentFixture(create)\n await fixture.service.chargeSavedMethod(\n { amount: 1000, currency: 'usd', customer: 'cus_contract', payment_method: 'pm_contract' },\n { idempotencyKey: 'charge:contract' },\n )\n equal(fixture.requests[0]?.body.get('confirm'), 'true')\n equal(fixture.requests[0]?.body.get('off_session'), 'true')\n })\n\n test('payment reads preserve native statuses and response fields', async () => {\n const fixture = paymentFixture(create)\n const payment = await fixture.service.getPayment('pi_contract')\n deepStrictEqual(\n { id: payment.id, status: payment.status },\n { id: 'pi_contract', status: 'requires_capture' },\n )\n })\n\n test('card failures retain the native error in cause', async () => {\n const response = Response.json(\n {\n error: {\n type: 'card_error',\n code: 'authentication_required',\n message: 'Authentication required',\n },\n },\n { status: 402 },\n )\n const fixture = paymentFixture(create, response)\n await rejects(\n fixture.service.chargeSavedMethod(\n { amount: 1000, currency: 'usd', customer: 'cus_contract', payment_method: 'pm_contract' },\n { idempotencyKey: 'charge:contract' },\n ),\n (error) => {\n ok(error instanceof PaymentServiceError)\n ok(error.cause instanceof Stripe.errors.StripeCardError)\n equal(error.cause.code, 'authentication_required')\n return true\n },\n )\n })\n}\n\nfunction paymentFixture(\n create: (stripe: Stripe) => IPaymentService,\n response = Response.json({ id: 'pi_contract', status: 'requires_capture' }),\n) {\n const { stripe, requests } = stripeFixture(() => response.clone())\n return { service: create(stripe), requests }\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,uBACd,MACA,QACM;CACN,KAAK,sEAAsE,YAAY;EACrF,MAAM,UAAU,eAAe,MAAM;EACrC,MAAM,QAAQ,QAAQ,eACpB,EAAE,UAAU,eAAe,GAC3B,EAAE,gBAAgB,oBAAoB,CACxC;EACA,MAAM,QAAQ,SAAS,EAAE,EAAE,KAAK,IAAI,MAAM,GAAG,SAAS;EACtD,MAAM,QAAQ,SAAS,EAAE,EAAE,QAAQ,IAAI,iBAAiB,GAAG,mBAAmB;CAChF,CAAC;CAED,KAAK,2CAA2C,YAAY;EAC1D,MAAM,UAAU,eAAe,MAAM;EACrC,MAAM,QAAQ,QAAQ,mBACpB,EAAE,UAAU,eAAe,GAC3B,EAAE,gBAAgB,iBAAiB,CACrC;EACA,MAAM,QAAQ,SAAS,EAAE,EAAE,KAAK,IAAI,MAAM,GAAG,OAAO;CACtD,CAAC;CAED,KAAK,6CAA6C,YAAY;EAC5D,MAAM,UAAU,eAAe,MAAM;EACrC,MAAM,QAAQ,QAAQ,kBACpB;GAAE,QAAQ;GAAM,UAAU;GAAO,UAAU;GAAgB,gBAAgB;EAAc,GACzF,EAAE,gBAAgB,kBAAkB,CACtC;EACA,MAAM,QAAQ,SAAS,EAAE,EAAE,KAAK,IAAI,SAAS,GAAG,MAAM;EACtD,MAAM,QAAQ,SAAS,EAAE,EAAE,KAAK,IAAI,aAAa,GAAG,MAAM;CAC5D,CAAC;CAED,KAAK,8DAA8D,YAAY;EAE7E,MAAM,UAAU,MADA,eAAe,MACH,CAAC,CAAC,QAAQ,WAAW,aAAa;EAC9D,gBACE;GAAE,IAAI,QAAQ;GAAI,QAAQ,QAAQ;EAAO,GACzC;GAAE,IAAI;GAAe,QAAQ;EAAmB,CAClD;CACF,CAAC;CAED,KAAK,kDAAkD,YAAY;EAWjE,MAAM,UAAU,eAAe,QAVd,SAAS,KACxB,EACE,OAAO;GACL,MAAM;GACN,MAAM;GACN,SAAS;EACX,EACF,GACA,EAAE,QAAQ,IAAI,CAE8B,CAAC;EAC/C,MAAM,QACJ,QAAQ,QAAQ,kBACd;GAAE,QAAQ;GAAM,UAAU;GAAO,UAAU;GAAgB,gBAAgB;EAAc,GACzF,EAAE,gBAAgB,kBAAkB,CACtC,IACC,UAAU;GACT,GAAG,iBAAiB,mBAAmB;GACvC,GAAG,MAAM,iBAAiB,OAAO,OAAO,eAAe;GACvD,MAAM,MAAM,MAAM,MAAM,yBAAyB;GACjD,OAAO;EACT,CACF;CACF,CAAC;AACH;AAEA,SAAS,eACP,QACA,WAAW,SAAS,KAAK;CAAE,IAAI;CAAe,QAAQ;AAAmB,CAAC,GAC1E;CACA,MAAM,EAAE,QAAQ,aAAa,oBAAoB,SAAS,MAAM,CAAC;CACjE,OAAO;EAAE,SAAS,OAAO,MAAM;EAAG;CAAS;AAC7C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
//#region src/infrastructure/testing/stripe-fixture.d.ts
|
|
3
|
+
/** One SDK HTTP attempt; query and body expose Stripe's URL-encoded parameters. */
|
|
4
|
+
export type StripeCall = {
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly query: URLSearchParams;
|
|
8
|
+
readonly body: URLSearchParams;
|
|
9
|
+
readonly headers: Headers;
|
|
10
|
+
};
|
|
11
|
+
type StripeFixture = {
|
|
12
|
+
stripe: Stripe;
|
|
13
|
+
requests: readonly StripeCall[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Construct the real SDK with a fake fetch transport and an ordered record of HTTP attempts.
|
|
17
|
+
* The responder receives an unread request. The shared client factory supplies retry defaults.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stripeFixture(respond: (request: Request) => Response | Promise<Response>): StripeFixture;
|
|
20
|
+
//#endregion
|
|
21
|
+
//# sourceMappingURL=stripe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripe.d.ts","names":[],"sources":["../../src/infrastructure/testing/stripe-fixture.ts"],"mappings":";;;YAKY;WACD;WACA;WACA,OAAO;WACP,MAAM;WACN,SAAS;;KAGf;EAAkB,QAAQ;EAAQ,mBAAmB;;;;;;wBAM1C,cACd,UAAU,SAAS,YAAY,WAAW,QAAQ,YACjD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createStripeClient } from "../stripe/client.js";
|
|
2
|
+
import Stripe from "stripe";
|
|
3
|
+
|
|
4
|
+
//#region src/infrastructure/testing/stripe-fixture.ts
|
|
5
|
+
/**
|
|
6
|
+
* Construct the real SDK with a fake fetch transport and an ordered record of HTTP attempts.
|
|
7
|
+
* The responder receives an unread request. The shared client factory supplies retry defaults.
|
|
8
|
+
*/
|
|
9
|
+
function stripeFixture(respond) {
|
|
10
|
+
const requests = [];
|
|
11
|
+
return {
|
|
12
|
+
stripe: createStripeClient("sk_test_fixture", { httpClient: Stripe.createFetchHttpClient(async (input, init) => {
|
|
13
|
+
const request = new Request(input, init);
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const call = {
|
|
16
|
+
method: request.method,
|
|
17
|
+
path: url.pathname,
|
|
18
|
+
query: url.searchParams,
|
|
19
|
+
body: new URLSearchParams(),
|
|
20
|
+
headers: new Headers(request.headers)
|
|
21
|
+
};
|
|
22
|
+
requests.push(call);
|
|
23
|
+
const body = new TextDecoder().decode(await request.clone().arrayBuffer());
|
|
24
|
+
for (const [key, value] of new URLSearchParams(body)) call.body.append(key, value);
|
|
25
|
+
return respond(request);
|
|
26
|
+
}) }),
|
|
27
|
+
requests
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { stripeFixture };
|
|
33
|
+
//# sourceMappingURL=stripe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripe.js","names":[],"sources":["../../src/infrastructure/testing/stripe-fixture.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { createStripeClient } from '../stripe/client.ts'\n\n/** One SDK HTTP attempt; query and body expose Stripe's URL-encoded parameters. */\nexport type StripeCall = {\n readonly method: string\n readonly path: string\n readonly query: URLSearchParams\n readonly body: URLSearchParams\n readonly headers: Headers\n}\n\ntype StripeFixture = { stripe: Stripe; requests: readonly StripeCall[] }\n\n/**\n * Construct the real SDK with a fake fetch transport and an ordered record of HTTP attempts.\n * The responder receives an unread request. The shared client factory supplies retry defaults.\n */\nexport function stripeFixture(\n respond: (request: Request) => Response | Promise<Response>,\n): StripeFixture {\n const requests: StripeCall[] = []\n const stripe = createStripeClient('sk_test_fixture', {\n httpClient: Stripe.createFetchHttpClient(async (input, init) => {\n const request = new Request(input, init)\n const url = new URL(request.url)\n const call: StripeCall = {\n method: request.method,\n path: url.pathname,\n query: url.searchParams,\n body: new URLSearchParams(),\n headers: new Headers(request.headers),\n }\n requests.push(call)\n const body = new TextDecoder().decode(await request.clone().arrayBuffer())\n for (const [key, value] of new URLSearchParams(body)) call.body.append(key, value)\n return respond(request)\n }),\n })\n return { stripe, requests }\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,cACd,SACe;CACf,MAAM,WAAyB,CAAC;CAkBhC,OAAO;EAAE,QAjBM,mBAAmB,mBAAmB,EACnD,YAAY,OAAO,sBAAsB,OAAO,OAAO,SAAS;GAC9D,MAAM,UAAU,IAAI,QAAQ,OAAO,IAAI;GACvC,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;GAC/B,MAAM,OAAmB;IACvB,QAAQ,QAAQ;IAChB,MAAM,IAAI;IACV,OAAO,IAAI;IACX,MAAM,IAAI,gBAAgB;IAC1B,SAAS,IAAI,QAAQ,QAAQ,OAAO;GACtC;GACA,SAAS,KAAK,IAAI;GAClB,MAAM,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,MAAM,QAAQ,MAAM,CAAC,CAAC,YAAY,CAAC;GACzE,KAAK,MAAM,CAAC,KAAK,UAAU,IAAI,gBAAgB,IAAI,GAAG,KAAK,KAAK,OAAO,KAAK,KAAK;GACjF,OAAO,QAAQ,OAAO;EACxB,CAAC,EACH,CACc;EAAG;CAAS;AAC5B"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "better-ship",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.22.0",
|
|
5
5
|
"description": "Foundation for applications on Cloudflare Workers: logger, errors, message bus, infrastructure adapters, and UI primitives",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -44,13 +44,17 @@
|
|
|
44
44
|
"./sentry/client": "./dist/sentry/client.js",
|
|
45
45
|
"./sentry/server": "./dist/sentry/server.js",
|
|
46
46
|
"./sentry/vite": "./dist/sentry/vite.js",
|
|
47
|
+
"./stripe/client": "./dist/stripe/client.js",
|
|
48
|
+
"./stripe/event-map": "./dist/stripe/event-map.js",
|
|
47
49
|
"./stripe/payment-service": "./dist/stripe/payment-service.js",
|
|
48
50
|
"./stripe/payment-service.error": "./dist/stripe/payment-service.error.js",
|
|
49
51
|
"./stripe/payment-webhook": "./dist/stripe/payment-webhook.js",
|
|
52
|
+
"./stripe/request": "./dist/stripe/request.js",
|
|
50
53
|
"./tanstack": "./dist/tanstack.js",
|
|
51
54
|
"./tanstack/server": "./dist/tanstack/server.js",
|
|
52
55
|
"./testing/outbox-repository": "./dist/testing/outbox-repository.js",
|
|
53
56
|
"./testing/payment-service": "./dist/testing/payment-service.js",
|
|
57
|
+
"./testing/stripe": "./dist/testing/stripe.js",
|
|
54
58
|
"./ui": "./dist/ui.js",
|
|
55
59
|
"./package.json": "./package.json"
|
|
56
60
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Stripe from 'stripe'
|
|
2
|
+
|
|
3
|
+
/** Default SDK retries; client configuration and individual requests can override this value. */
|
|
4
|
+
export const STRIPE_REQUEST_OPTIONS = Object.freeze({ maxNetworkRetries: 2 })
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Construct a native Stripe client with fetch transport and two SDK retries by default.
|
|
8
|
+
* Native configuration overrides these defaults. Inject the client into application services.
|
|
9
|
+
*/
|
|
10
|
+
export function createStripeClient(secretKey: string, options: Stripe.StripeConfig = {}): Stripe {
|
|
11
|
+
return new Stripe(secretKey, {
|
|
12
|
+
httpClient: Stripe.createFetchHttpClient(),
|
|
13
|
+
...STRIPE_REQUEST_OPTIONS,
|
|
14
|
+
...options,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type Stripe from 'stripe'
|
|
2
|
+
|
|
3
|
+
type EventObject<K extends Stripe.Event['type']> = Extract<
|
|
4
|
+
Stripe.Event,
|
|
5
|
+
{ type: K }
|
|
6
|
+
>['data']['object']
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert verified Stripe events with callbacks typed to each event's object.
|
|
10
|
+
* Unmapped events return undefined. Applications own validation and payment policy.
|
|
11
|
+
*/
|
|
12
|
+
export function stripeEventMap<T>(map: {
|
|
13
|
+
[K in Stripe.Event['type']]?: (object: EventObject<K>) => T
|
|
14
|
+
}): (event: Stripe.Event) => T | undefined {
|
|
15
|
+
const handlers: Partial<Record<Stripe.Event['type'], (object: never) => T>> = map
|
|
16
|
+
return (event) => {
|
|
17
|
+
// SAFETY: The map binds each event type to its object; indexed lookup loses that correlation.
|
|
18
|
+
return handlers[event.type]?.(event.data.object as never)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Identify one Stripe event delivery; different events for one payment retain different IDs. */
|
|
23
|
+
export function stripeEventId(event: Stripe.Event): string {
|
|
24
|
+
return `stripe:event:${event.id}`
|
|
25
|
+
}
|
|
@@ -26,7 +26,8 @@ function classifyStripeError(error: Stripe.errors.StripeError): FailureClassific
|
|
|
26
26
|
error instanceof Stripe.errors.StripeAuthenticationError ||
|
|
27
27
|
error instanceof Stripe.errors.StripePermissionError ||
|
|
28
28
|
error instanceof Stripe.errors.StripeInvalidRequestError ||
|
|
29
|
-
error instanceof Stripe.errors.StripeIdempotencyError
|
|
29
|
+
error instanceof Stripe.errors.StripeIdempotencyError ||
|
|
30
|
+
error instanceof Stripe.errors.StripeCardError
|
|
30
31
|
)
|
|
31
32
|
return 'terminal'
|
|
32
33
|
if (error.headers?.['stripe-should-retry'] === 'true') return 'transient'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import Stripe from 'stripe'
|
|
1
|
+
import type Stripe from 'stripe'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { stripeRequest } from './request.ts'
|
|
4
4
|
|
|
5
5
|
/** Supply the same key and parameters when repeating one durable payment attempt. */
|
|
6
6
|
export type PaymentRequestOptions = Stripe.RequestOptions &
|
|
@@ -24,7 +24,7 @@ export type PaymentResult = Awaited<ReturnType<Stripe['paymentIntents']['create'
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Stripe payment operations, with native types and stable keys for mutations.
|
|
27
|
-
* Rejects with PaymentServiceError
|
|
27
|
+
* Rejects with PaymentServiceError for Stripe failures; non-Stripe failures propagate unchanged.
|
|
28
28
|
*/
|
|
29
29
|
export interface IPaymentService {
|
|
30
30
|
/** Create a customer; the application owns persistence and reuse of the customer link. */
|
|
@@ -36,7 +36,7 @@ export interface IPaymentService {
|
|
|
36
36
|
createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>
|
|
37
37
|
/** Create checkout in setup mode. The application records consent and verifies the saved method's owner. */
|
|
38
38
|
setupPaymentMethod(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>
|
|
39
|
-
/** Confirm a saved method off-session.
|
|
39
|
+
/** Confirm a saved method off-session. PaymentServiceError retains card errors in cause. */
|
|
40
40
|
chargeSavedMethod(
|
|
41
41
|
input: SavedMethodCharge,
|
|
42
42
|
options: PaymentRequestOptions,
|
|
@@ -51,7 +51,7 @@ export interface IPaymentService {
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Ported from Typist's StripeClient request boundary; the native SDK now owns retries.
|
|
54
|
-
*
|
|
54
|
+
* The injected client owns retry and timeout defaults. Native request options can override them.
|
|
55
55
|
*/
|
|
56
56
|
export class StripePaymentService implements IPaymentService {
|
|
57
57
|
/** Inject a native SDK instance; its transport, credentials, and API version remain caller-owned. */
|
|
@@ -62,18 +62,13 @@ export class StripePaymentService implements IPaymentService {
|
|
|
62
62
|
input: Stripe.CustomerCreateParams,
|
|
63
63
|
options: PaymentRequestOptions,
|
|
64
64
|
): Promise<CustomerResult> {
|
|
65
|
-
return
|
|
66
|
-
this.stripe.customers.create(input, { maxNetworkRetries: 2, ...options }),
|
|
67
|
-
)
|
|
65
|
+
return stripeRequest('customers.create', () => this.stripe.customers.create(input, options))
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/** Force payment mode after caller parameters; return the native Checkout Session. */
|
|
71
69
|
createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult> {
|
|
72
|
-
return
|
|
73
|
-
this.stripe.checkout.sessions.create(
|
|
74
|
-
{ ...input, mode: 'payment' },
|
|
75
|
-
{ maxNetworkRetries: 2, ...options },
|
|
76
|
-
),
|
|
70
|
+
return stripeRequest('checkout.sessions.create', () =>
|
|
71
|
+
this.stripe.checkout.sessions.create({ ...input, mode: 'payment' }, options),
|
|
77
72
|
)
|
|
78
73
|
}
|
|
79
74
|
|
|
@@ -82,24 +77,18 @@ export class StripePaymentService implements IPaymentService {
|
|
|
82
77
|
input: CheckoutInput,
|
|
83
78
|
options: PaymentRequestOptions,
|
|
84
79
|
): Promise<CheckoutResult> {
|
|
85
|
-
return
|
|
86
|
-
this.stripe.checkout.sessions.create(
|
|
87
|
-
{ ...input, mode: 'setup' },
|
|
88
|
-
{ maxNetworkRetries: 2, ...options },
|
|
89
|
-
),
|
|
80
|
+
return stripeRequest('checkout.sessions.create', () =>
|
|
81
|
+
this.stripe.checkout.sessions.create({ ...input, mode: 'setup' }, options),
|
|
90
82
|
)
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
/** Force confirmation and off-session use;
|
|
85
|
+
/** Force confirmation and off-session use; retain native card failures in PaymentServiceError.cause. */
|
|
94
86
|
chargeSavedMethod(
|
|
95
87
|
input: SavedMethodCharge,
|
|
96
88
|
options: PaymentRequestOptions,
|
|
97
89
|
): Promise<PaymentResult> {
|
|
98
|
-
return
|
|
99
|
-
this.stripe.paymentIntents.create(
|
|
100
|
-
{ ...input, confirm: true, off_session: true },
|
|
101
|
-
{ maxNetworkRetries: 2, ...options },
|
|
102
|
-
),
|
|
90
|
+
return stripeRequest('paymentIntents.create', () =>
|
|
91
|
+
this.stripe.paymentIntents.create({ ...input, confirm: true, off_session: true }, options),
|
|
103
92
|
)
|
|
104
93
|
}
|
|
105
94
|
|
|
@@ -109,21 +98,8 @@ export class StripePaymentService implements IPaymentService {
|
|
|
109
98
|
params?: Stripe.PaymentIntentRetrieveParams,
|
|
110
99
|
options?: Stripe.RequestOptions,
|
|
111
100
|
): Promise<PaymentResult> {
|
|
112
|
-
return
|
|
113
|
-
this.stripe.paymentIntents.retrieve(paymentId, params,
|
|
101
|
+
return stripeRequest('paymentIntents.retrieve', () =>
|
|
102
|
+
this.stripe.paymentIntents.retrieve(paymentId, params, options),
|
|
114
103
|
)
|
|
115
104
|
}
|
|
116
|
-
|
|
117
|
-
private async request<T>(operation: string, work: () => Promise<T>): Promise<T> {
|
|
118
|
-
try {
|
|
119
|
-
return await work()
|
|
120
|
-
} catch (cause) {
|
|
121
|
-
if (
|
|
122
|
-
!(cause instanceof Stripe.errors.StripeError) ||
|
|
123
|
-
cause instanceof Stripe.errors.StripeCardError
|
|
124
|
-
)
|
|
125
|
-
throw cause
|
|
126
|
-
throw new PaymentServiceError(operation, cause)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
105
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Stripe from 'stripe'
|
|
2
|
+
|
|
3
|
+
import { PaymentServiceError } from './payment-service.error.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Invoke one SDK operation; the SDK owns retries configured on its client or request.
|
|
7
|
+
* Convert every Stripe error to PaymentServiceError without logging; preserve the original error in cause.
|
|
8
|
+
* Non-Stripe failures propagate unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export async function stripeRequest<T>(operation: string, work: () => Promise<T>): Promise<T> {
|
|
11
|
+
try {
|
|
12
|
+
return await work()
|
|
13
|
+
} catch (cause) {
|
|
14
|
+
if (!(cause instanceof Stripe.errors.StripeError)) throw cause
|
|
15
|
+
throw new PaymentServiceError(operation, cause)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { deepStrictEqual, equal, rejects } from 'node:assert/strict'
|
|
1
|
+
import { deepStrictEqual, equal, ok, rejects } from 'node:assert/strict'
|
|
2
2
|
|
|
3
3
|
import Stripe from 'stripe'
|
|
4
4
|
|
|
5
|
+
import { PaymentServiceError } from '../stripe/payment-service.error.ts'
|
|
5
6
|
import type { IPaymentService } from '../stripe/payment-service.ts'
|
|
7
|
+
import { stripeFixture } from './stripe-fixture.ts'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Test a Stripe payment adapter through native fetch injection without live payments.
|
|
@@ -50,7 +52,7 @@ export function paymentServiceContract(
|
|
|
50
52
|
)
|
|
51
53
|
})
|
|
52
54
|
|
|
53
|
-
test('card failures retain the native error
|
|
55
|
+
test('card failures retain the native error in cause', async () => {
|
|
54
56
|
const response = Response.json(
|
|
55
57
|
{
|
|
56
58
|
error: {
|
|
@@ -67,7 +69,12 @@ export function paymentServiceContract(
|
|
|
67
69
|
{ amount: 1000, currency: 'usd', customer: 'cus_contract', payment_method: 'pm_contract' },
|
|
68
70
|
{ idempotencyKey: 'charge:contract' },
|
|
69
71
|
),
|
|
70
|
-
|
|
72
|
+
(error) => {
|
|
73
|
+
ok(error instanceof PaymentServiceError)
|
|
74
|
+
ok(error.cause instanceof Stripe.errors.StripeCardError)
|
|
75
|
+
equal(error.cause.code, 'authentication_required')
|
|
76
|
+
return true
|
|
77
|
+
},
|
|
71
78
|
)
|
|
72
79
|
})
|
|
73
80
|
}
|
|
@@ -76,16 +83,6 @@ function paymentFixture(
|
|
|
76
83
|
create: (stripe: Stripe) => IPaymentService,
|
|
77
84
|
response = Response.json({ id: 'pi_contract', status: 'requires_capture' }),
|
|
78
85
|
) {
|
|
79
|
-
const
|
|
80
|
-
const stripe = new Stripe('sk_test_contract', {
|
|
81
|
-
httpClient: Stripe.createFetchHttpClient(async (input, init) => {
|
|
82
|
-
const request = new Request(input, init)
|
|
83
|
-
requests.push({
|
|
84
|
-
headers: request.headers,
|
|
85
|
-
body: new URLSearchParams(new TextDecoder().decode(await request.arrayBuffer())),
|
|
86
|
-
})
|
|
87
|
-
return response.clone()
|
|
88
|
-
}),
|
|
89
|
-
})
|
|
86
|
+
const { stripe, requests } = stripeFixture(() => response.clone())
|
|
90
87
|
return { service: create(stripe), requests }
|
|
91
88
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Stripe from 'stripe'
|
|
2
|
+
|
|
3
|
+
import { createStripeClient } from '../stripe/client.ts'
|
|
4
|
+
|
|
5
|
+
/** One SDK HTTP attempt; query and body expose Stripe's URL-encoded parameters. */
|
|
6
|
+
export type StripeCall = {
|
|
7
|
+
readonly method: string
|
|
8
|
+
readonly path: string
|
|
9
|
+
readonly query: URLSearchParams
|
|
10
|
+
readonly body: URLSearchParams
|
|
11
|
+
readonly headers: Headers
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type StripeFixture = { stripe: Stripe; requests: readonly StripeCall[] }
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Construct the real SDK with a fake fetch transport and an ordered record of HTTP attempts.
|
|
18
|
+
* The responder receives an unread request. The shared client factory supplies retry defaults.
|
|
19
|
+
*/
|
|
20
|
+
export function stripeFixture(
|
|
21
|
+
respond: (request: Request) => Response | Promise<Response>,
|
|
22
|
+
): StripeFixture {
|
|
23
|
+
const requests: StripeCall[] = []
|
|
24
|
+
const stripe = createStripeClient('sk_test_fixture', {
|
|
25
|
+
httpClient: Stripe.createFetchHttpClient(async (input, init) => {
|
|
26
|
+
const request = new Request(input, init)
|
|
27
|
+
const url = new URL(request.url)
|
|
28
|
+
const call: StripeCall = {
|
|
29
|
+
method: request.method,
|
|
30
|
+
path: url.pathname,
|
|
31
|
+
query: url.searchParams,
|
|
32
|
+
body: new URLSearchParams(),
|
|
33
|
+
headers: new Headers(request.headers),
|
|
34
|
+
}
|
|
35
|
+
requests.push(call)
|
|
36
|
+
const body = new TextDecoder().decode(await request.clone().arrayBuffer())
|
|
37
|
+
for (const [key, value] of new URLSearchParams(body)) call.body.append(key, value)
|
|
38
|
+
return respond(request)
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
return { stripe, requests }
|
|
42
|
+
}
|