@proxy-checkout/stripe-server-js 0.0.1 → 0.1.0-prx-124.139.1
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/README.md +18 -14
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/metadata.d.cts +2 -0
- package/dist/cjs/metadata.d.ts +2 -0
- package/dist/cjs/metadata.js +15 -7
- package/dist/cjs/open-checkout.d.cts +22 -1
- package/dist/cjs/open-checkout.d.ts +22 -1
- package/dist/cjs/open-checkout.js +146 -19
- package/dist/cjs/types.d.cts +5 -0
- package/dist/cjs/types.d.ts +5 -0
- package/dist/cjs/version.d.cts +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/metadata.d.ts +2 -0
- package/dist/esm/metadata.js +14 -6
- package/dist/esm/open-checkout.d.ts +22 -1
- package/dist/esm/open-checkout.js +146 -19
- package/dist/esm/types.d.ts +5 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Server-side Stripe adapter for Proxy Checkout. It owns the Stripe-specific integ
|
|
|
4
4
|
|
|
5
5
|
- **`requiredMetadata(proxySession)`** — the `proxy_session_id` metadata Proxy needs on the Checkout Session, Subscription, and PaymentIntent.
|
|
6
6
|
- **`assertCheckoutSessionBelongsToProxy` / `assertSubscriptionBelongsToProxy` / `assertPaymentIntentBelongsToProxy`** — provider-object ownership checks.
|
|
7
|
-
- **`openCheckout(...)`** — records payer-opened, validates/reconciles the typed cart with rollback, injects required metadata, creates the Stripe Checkout Session with deterministic idempotency, records the Proxy provider binding
|
|
7
|
+
- **`openCheckout(...)`** — records payer-opened, validates/reconciles the typed cart with rollback, injects required metadata, creates or joins the Stripe Checkout Session with deterministic idempotency, and records the Proxy provider binding. It returns provider-create material only for a `ready` outcome; terminal outcomes retain the shared cart without starting Stripe.
|
|
8
8
|
- **`syncCheckoutCart(...)`** — validates the stored Proxy provider binding, then updates the Proxy cart and the Stripe Checkout `line_items` in the safest order with deterministic rollback/reconciliation (`ProxyStripeCartSyncError`).
|
|
9
9
|
|
|
10
10
|
PSP-agnostic primitives (sessions, subscriptions, webhooks, lifecycle) live in [`@proxy-checkout/server-js`](https://www.npmjs.com/package/@proxy-checkout/server-js). This package is an additive adapter on top and is dependency-free: you inject your own `Stripe` instance and your `@proxy-checkout/server-js` client.
|
|
@@ -38,19 +38,23 @@ const checkout = await openCheckout({
|
|
|
38
38
|
ui_mode: "custom",
|
|
39
39
|
}),
|
|
40
40
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
if (checkout.outcome === "ready") {
|
|
42
|
+
// checkout.clientSecret -> render Stripe Elements
|
|
43
|
+
|
|
44
|
+
// Cart edits go through the binding-validated sync helper.
|
|
45
|
+
await syncCheckoutCart({
|
|
46
|
+
proxy,
|
|
47
|
+
stripe,
|
|
48
|
+
proxySessionId,
|
|
49
|
+
checkoutSessionId: checkout.checkoutSession.id,
|
|
50
|
+
cartSchema,
|
|
51
|
+
input: { interval: "year" },
|
|
52
|
+
buildNextCart: ({ currentCart, input }) => buildMerchantCart(currentCart, input),
|
|
53
|
+
buildStripeLineItems: (cart) => cart.lineItems,
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
// Render checkout.cart plus checkout.outcome/sessionStatus. Do not start Stripe.
|
|
57
|
+
}
|
|
54
58
|
```
|
|
55
59
|
|
|
56
60
|
Use the delegated-checkout guide from your Proxy onboarding materials for the
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { type ProxyStripeCartSyncCode, ProxyStripeCartSyncError, type ProxyStripeOpenCheckoutCode, ProxyStripeOpenCheckoutError, } from "./errors.js";
|
|
2
|
-
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
|
|
3
|
-
export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResult, openCheckout, } from "./open-checkout.js";
|
|
2
|
+
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_INTEGRATION_PATH_METADATA_KEY, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
|
|
3
|
+
export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResponse, type OpenCheckoutResult, type OpenCheckoutTerminalResult, openCheckout, } from "./open-checkout.js";
|
|
4
4
|
export { normalizePsp } from "./psp.js";
|
|
5
5
|
export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
|
|
6
6
|
export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { type ProxyStripeCartSyncCode, ProxyStripeCartSyncError, type ProxyStripeOpenCheckoutCode, ProxyStripeOpenCheckoutError, } from "./errors.js";
|
|
2
|
-
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
|
|
3
|
-
export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResult, openCheckout, } from "./open-checkout.js";
|
|
2
|
+
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_INTEGRATION_PATH_METADATA_KEY, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
|
|
3
|
+
export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResponse, type OpenCheckoutResult, type OpenCheckoutTerminalResult, openCheckout, } from "./open-checkout.js";
|
|
4
4
|
export { normalizePsp } from "./psp.js";
|
|
5
5
|
export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
|
|
6
6
|
export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.proxyCheckoutStripeServerSdkVersion = exports.proxyCheckoutStripeServerSdkName = exports.syncCheckoutCart = exports.normalizePsp = exports.openCheckout = exports.requiredMetadata = exports.PROXY_SESSION_METADATA_KEY = exports.injectRequiredCheckoutSessionMetadata = exports.assertSubscriptionBelongsToProxy = exports.assertPaymentIntentBelongsToProxy = exports.assertCheckoutSessionBelongsToProxy = exports.ProxyStripeOpenCheckoutError = exports.ProxyStripeCartSyncError = void 0;
|
|
3
|
+
exports.proxyCheckoutStripeServerSdkVersion = exports.proxyCheckoutStripeServerSdkName = exports.syncCheckoutCart = exports.normalizePsp = exports.openCheckout = exports.requiredMetadata = exports.PROXY_SESSION_METADATA_KEY = exports.PROXY_INTEGRATION_PATH_METADATA_KEY = exports.injectRequiredCheckoutSessionMetadata = exports.assertSubscriptionBelongsToProxy = exports.assertPaymentIntentBelongsToProxy = exports.assertCheckoutSessionBelongsToProxy = exports.ProxyStripeOpenCheckoutError = exports.ProxyStripeCartSyncError = void 0;
|
|
4
4
|
var errors_js_1 = require("./errors.js");
|
|
5
5
|
Object.defineProperty(exports, "ProxyStripeCartSyncError", { enumerable: true, get: function () { return errors_js_1.ProxyStripeCartSyncError; } });
|
|
6
6
|
Object.defineProperty(exports, "ProxyStripeOpenCheckoutError", { enumerable: true, get: function () { return errors_js_1.ProxyStripeOpenCheckoutError; } });
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "assertCheckoutSessionBelongsToProxy", { enumerab
|
|
|
9
9
|
Object.defineProperty(exports, "assertPaymentIntentBelongsToProxy", { enumerable: true, get: function () { return metadata_js_1.assertPaymentIntentBelongsToProxy; } });
|
|
10
10
|
Object.defineProperty(exports, "assertSubscriptionBelongsToProxy", { enumerable: true, get: function () { return metadata_js_1.assertSubscriptionBelongsToProxy; } });
|
|
11
11
|
Object.defineProperty(exports, "injectRequiredCheckoutSessionMetadata", { enumerable: true, get: function () { return metadata_js_1.injectRequiredCheckoutSessionMetadata; } });
|
|
12
|
+
Object.defineProperty(exports, "PROXY_INTEGRATION_PATH_METADATA_KEY", { enumerable: true, get: function () { return metadata_js_1.PROXY_INTEGRATION_PATH_METADATA_KEY; } });
|
|
12
13
|
Object.defineProperty(exports, "PROXY_SESSION_METADATA_KEY", { enumerable: true, get: function () { return metadata_js_1.PROXY_SESSION_METADATA_KEY; } });
|
|
13
14
|
Object.defineProperty(exports, "requiredMetadata", { enumerable: true, get: function () { return metadata_js_1.requiredMetadata; } });
|
|
14
15
|
var open_checkout_js_1 = require("./open-checkout.js");
|
package/dist/cjs/metadata.d.cts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripePaymentIntentLike, StripeSubscriptionLike } from "./types.js";
|
|
10
10
|
/** The single metadata key Proxy reads to correlate a Stripe object to a session. */
|
|
11
11
|
export declare const PROXY_SESSION_METADATA_KEY = "proxy_session_id";
|
|
12
|
+
/** Server-owned marker that distinguishes Checkout-created child objects from direct objects. */
|
|
13
|
+
export declare const PROXY_INTEGRATION_PATH_METADATA_KEY = "proxy_integration_path";
|
|
12
14
|
export type ProxySessionMetadata = Readonly<Record<"proxy_session_id", string>>;
|
|
13
15
|
/**
|
|
14
16
|
* The metadata each Stripe object must carry, ready to spread into Stripe params.
|
package/dist/cjs/metadata.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripePaymentIntentLike, StripeSubscriptionLike } from "./types.js";
|
|
10
10
|
/** The single metadata key Proxy reads to correlate a Stripe object to a session. */
|
|
11
11
|
export declare const PROXY_SESSION_METADATA_KEY = "proxy_session_id";
|
|
12
|
+
/** Server-owned marker that distinguishes Checkout-created child objects from direct objects. */
|
|
13
|
+
export declare const PROXY_INTEGRATION_PATH_METADATA_KEY = "proxy_integration_path";
|
|
12
14
|
export type ProxySessionMetadata = Readonly<Record<"proxy_session_id", string>>;
|
|
13
15
|
/**
|
|
14
16
|
* The metadata each Stripe object must carry, ready to spread into Stripe params.
|
package/dist/cjs/metadata.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* provides assertions that a Stripe object actually belongs to a Proxy session.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.PROXY_SESSION_METADATA_KEY = void 0;
|
|
11
|
+
exports.PROXY_INTEGRATION_PATH_METADATA_KEY = exports.PROXY_SESSION_METADATA_KEY = void 0;
|
|
12
12
|
exports.requiredMetadata = requiredMetadata;
|
|
13
13
|
exports.injectRequiredCheckoutSessionMetadata = injectRequiredCheckoutSessionMetadata;
|
|
14
14
|
exports.assertCheckoutSessionBelongsToProxy = assertCheckoutSessionBelongsToProxy;
|
|
@@ -17,6 +17,8 @@ exports.assertPaymentIntentBelongsToProxy = assertPaymentIntentBelongsToProxy;
|
|
|
17
17
|
const server_js_1 = require("@proxy-checkout/server-js");
|
|
18
18
|
/** The single metadata key Proxy reads to correlate a Stripe object to a session. */
|
|
19
19
|
exports.PROXY_SESSION_METADATA_KEY = "proxy_session_id";
|
|
20
|
+
/** Server-owned marker that distinguishes Checkout-created child objects from direct objects. */
|
|
21
|
+
exports.PROXY_INTEGRATION_PATH_METADATA_KEY = "proxy_integration_path";
|
|
20
22
|
/** Build the required Stripe metadata placements for a Proxy session. */
|
|
21
23
|
function requiredMetadata(proxySession) {
|
|
22
24
|
const metadata = { proxy_session_id: proxySession.id };
|
|
@@ -35,14 +37,18 @@ function requiredMetadata(proxySession) {
|
|
|
35
37
|
*/
|
|
36
38
|
function injectRequiredCheckoutSessionMetadata(params, proxySessionId) {
|
|
37
39
|
const metadata = { proxy_session_id: proxySessionId };
|
|
40
|
+
const checkoutMetadata = {
|
|
41
|
+
...metadata,
|
|
42
|
+
[exports.PROXY_INTEGRATION_PATH_METADATA_KEY]: "checkout_session",
|
|
43
|
+
};
|
|
38
44
|
return {
|
|
39
45
|
...params,
|
|
40
|
-
metadata: mergeProxyMetadata(params.metadata,
|
|
46
|
+
metadata: mergeProxyMetadata(params.metadata, checkoutMetadata, "metadata"),
|
|
41
47
|
...(params.mode === "subscription" || params.subscription_data !== undefined
|
|
42
48
|
? {
|
|
43
49
|
subscription_data: {
|
|
44
50
|
...objectRecord(params.subscription_data),
|
|
45
|
-
metadata: mergeProxyMetadata(metadataFromNestedParams(params.subscription_data),
|
|
51
|
+
metadata: mergeProxyMetadata(metadataFromNestedParams(params.subscription_data), checkoutMetadata, "subscription_data.metadata"),
|
|
46
52
|
},
|
|
47
53
|
}
|
|
48
54
|
: {}),
|
|
@@ -50,7 +56,7 @@ function injectRequiredCheckoutSessionMetadata(params, proxySessionId) {
|
|
|
50
56
|
? {
|
|
51
57
|
payment_intent_data: {
|
|
52
58
|
...objectRecord(params.payment_intent_data),
|
|
53
|
-
metadata: mergeProxyMetadata(metadataFromNestedParams(params.payment_intent_data),
|
|
59
|
+
metadata: mergeProxyMetadata(metadataFromNestedParams(params.payment_intent_data), checkoutMetadata, "payment_intent_data.metadata"),
|
|
54
60
|
},
|
|
55
61
|
}
|
|
56
62
|
: {}),
|
|
@@ -75,9 +81,11 @@ function assertProxyMetadata(metadata, proxySessionId, objectLabel) {
|
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
function mergeProxyMetadata(existing, required, field) {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
for (const [key, requiredValue] of Object.entries(required)) {
|
|
85
|
+
const existingValue = existing?.[key];
|
|
86
|
+
if (existingValue !== undefined && existingValue !== requiredValue) {
|
|
87
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Stripe ${field}.${key} (${existingValue}) conflicts with Proxy-required value ${requiredValue}.`, { code: "provider_binding_mismatch", field: `${field}.${key}` });
|
|
88
|
+
}
|
|
81
89
|
}
|
|
82
90
|
return {
|
|
83
91
|
...existing,
|
|
@@ -54,7 +54,28 @@ export interface OpenCheckoutResult<TCart, TOffer, TSession> {
|
|
|
54
54
|
readonly checkoutSession: TSession;
|
|
55
55
|
readonly clientSecret: string | null;
|
|
56
56
|
readonly offer: TOffer;
|
|
57
|
+
readonly outcome: "ready";
|
|
57
58
|
readonly proxySession: PayerOpenedResult;
|
|
58
59
|
readonly proxySessionId: string;
|
|
59
60
|
}
|
|
60
|
-
export
|
|
61
|
+
export type OpenCheckoutTerminalResult<TCart> = {
|
|
62
|
+
readonly cart: TCart;
|
|
63
|
+
readonly outcome: "already_paid";
|
|
64
|
+
readonly proxySession: PayerOpenedResult;
|
|
65
|
+
readonly proxySessionId: string;
|
|
66
|
+
readonly sessionStatus: "paid" | "provisionable" | "provisioned" | "provisioning_failed";
|
|
67
|
+
} | {
|
|
68
|
+
readonly cart: TCart;
|
|
69
|
+
readonly outcome: "action_required";
|
|
70
|
+
readonly proxySession: PayerOpenedResult;
|
|
71
|
+
readonly proxySessionId: string;
|
|
72
|
+
readonly sessionStatus: "merchant_action_required";
|
|
73
|
+
} | {
|
|
74
|
+
readonly cart: TCart;
|
|
75
|
+
readonly outcome: "unavailable";
|
|
76
|
+
readonly proxySession: PayerOpenedResult;
|
|
77
|
+
readonly proxySessionId: string;
|
|
78
|
+
readonly sessionStatus: "cancelled" | "created" | "expired" | "failed";
|
|
79
|
+
};
|
|
80
|
+
export type OpenCheckoutResponse<TCart, TOffer, TSession> = OpenCheckoutResult<TCart, TOffer, TSession> | OpenCheckoutTerminalResult<TCart>;
|
|
81
|
+
export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResponse<TCart, TOffer, TSession>>;
|
|
@@ -54,7 +54,28 @@ export interface OpenCheckoutResult<TCart, TOffer, TSession> {
|
|
|
54
54
|
readonly checkoutSession: TSession;
|
|
55
55
|
readonly clientSecret: string | null;
|
|
56
56
|
readonly offer: TOffer;
|
|
57
|
+
readonly outcome: "ready";
|
|
57
58
|
readonly proxySession: PayerOpenedResult;
|
|
58
59
|
readonly proxySessionId: string;
|
|
59
60
|
}
|
|
60
|
-
export
|
|
61
|
+
export type OpenCheckoutTerminalResult<TCart> = {
|
|
62
|
+
readonly cart: TCart;
|
|
63
|
+
readonly outcome: "already_paid";
|
|
64
|
+
readonly proxySession: PayerOpenedResult;
|
|
65
|
+
readonly proxySessionId: string;
|
|
66
|
+
readonly sessionStatus: "paid" | "provisionable" | "provisioned" | "provisioning_failed";
|
|
67
|
+
} | {
|
|
68
|
+
readonly cart: TCart;
|
|
69
|
+
readonly outcome: "action_required";
|
|
70
|
+
readonly proxySession: PayerOpenedResult;
|
|
71
|
+
readonly proxySessionId: string;
|
|
72
|
+
readonly sessionStatus: "merchant_action_required";
|
|
73
|
+
} | {
|
|
74
|
+
readonly cart: TCart;
|
|
75
|
+
readonly outcome: "unavailable";
|
|
76
|
+
readonly proxySession: PayerOpenedResult;
|
|
77
|
+
readonly proxySessionId: string;
|
|
78
|
+
readonly sessionStatus: "cancelled" | "created" | "expired" | "failed";
|
|
79
|
+
};
|
|
80
|
+
export type OpenCheckoutResponse<TCart, TOffer, TSession> = OpenCheckoutResult<TCart, TOffer, TSession> | OpenCheckoutTerminalResult<TCart>;
|
|
81
|
+
export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResponse<TCart, TOffer, TSession>>;
|
|
@@ -27,6 +27,13 @@ async function openCheckout(options) {
|
|
|
27
27
|
let cart = options.cartSchema || options.getCartBuyerReference
|
|
28
28
|
? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
|
|
29
29
|
: proxySession.cartSnapshot;
|
|
30
|
+
if (proxySession.presentationState !== "checkout_available") {
|
|
31
|
+
return terminalResultFromSession(proxySession, cart);
|
|
32
|
+
}
|
|
33
|
+
if (proxySession.status === "payment_pending" &&
|
|
34
|
+
proxySession.providerCheckoutSessionId === null) {
|
|
35
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has payment preparation in progress without a bound Stripe Checkout Session.`, { code: "provider_binding_missing", field: "provider_checkout_session_id" });
|
|
36
|
+
}
|
|
30
37
|
let offer = options.validateCart
|
|
31
38
|
? await options.validateCart({ cart, session: proxySession })
|
|
32
39
|
: undefined;
|
|
@@ -40,10 +47,14 @@ async function openCheckout(options) {
|
|
|
40
47
|
checkoutSession,
|
|
41
48
|
clientSecret: checkoutSession.client_secret ?? null,
|
|
42
49
|
offer,
|
|
50
|
+
outcome: "ready",
|
|
43
51
|
proxySession,
|
|
44
52
|
proxySessionId: proxySession.id,
|
|
45
53
|
};
|
|
46
54
|
}
|
|
55
|
+
if (proxySession.currentAcquisitionAttempt !== null) {
|
|
56
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has acquisition ${proxySession.currentAcquisitionAttempt.id} in progress; openCheckout will not create an unrelated Stripe Checkout Session.`, { code: "provider_acquisition_in_progress", field: "current_acquisition_attempt" });
|
|
57
|
+
}
|
|
47
58
|
let rollbackCart;
|
|
48
59
|
const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
|
|
49
60
|
if (reconciledCart) {
|
|
@@ -56,42 +67,91 @@ async function openCheckout(options) {
|
|
|
56
67
|
cartVersion: proxySession.cartVersion,
|
|
57
68
|
currency: proxySession.currency,
|
|
58
69
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
try {
|
|
71
|
+
const updatedCart = await options.proxy.sessions.cart.set(proxySession.id, {
|
|
72
|
+
amountMinor: reconciledCart.amountMinor,
|
|
73
|
+
cartSnapshot: reconciledCart.cartSnapshot,
|
|
74
|
+
currency: reconciledCart.currency,
|
|
75
|
+
expectedCartVersion: proxySession.cartVersion,
|
|
76
|
+
requestId: options.requestId,
|
|
77
|
+
});
|
|
78
|
+
rollbackCart = {
|
|
79
|
+
...priorCart,
|
|
80
|
+
expectedCartVersion: updatedCart.cartVersion,
|
|
81
|
+
};
|
|
82
|
+
proxySession = {
|
|
83
|
+
...proxySession,
|
|
84
|
+
amountMinor: updatedCart.amountMinor,
|
|
85
|
+
cartSnapshot: updatedCart.cartSnapshot,
|
|
86
|
+
cartVersion: updatedCart.cartVersion,
|
|
87
|
+
currency: updatedCart.currency,
|
|
88
|
+
updatedAt: proxySession.updatedAt,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
if (!isProxyConflict(cause)) {
|
|
93
|
+
throw cause;
|
|
94
|
+
}
|
|
95
|
+
const joined = await options.proxy.sessions.payerOpened(proxySession.id, {
|
|
96
|
+
requestId: options.requestId,
|
|
97
|
+
});
|
|
98
|
+
if (!sameCheckoutCart(joined, reconciledCart)) {
|
|
99
|
+
throw cause;
|
|
100
|
+
}
|
|
101
|
+
proxySession = joined;
|
|
102
|
+
}
|
|
78
103
|
cart = reconciledCart.cart;
|
|
79
104
|
}
|
|
80
105
|
try {
|
|
81
106
|
if (reconciledCart && options.validateCart) {
|
|
82
107
|
offer = await options.validateCart({ cart, session: proxySession });
|
|
83
108
|
}
|
|
109
|
+
const current = await options.proxy.sessions.payerOpened(proxySession.id, {
|
|
110
|
+
requestId: options.requestId,
|
|
111
|
+
});
|
|
112
|
+
if (current.presentationState !== "checkout_available") {
|
|
113
|
+
return terminalResultFromSession(current, cart);
|
|
114
|
+
}
|
|
115
|
+
if (!sameCheckoutCart(current, proxySession)) {
|
|
116
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} cart changed while Checkout was being prepared.`, { code: "cart_version_conflict", field: "cart_version" });
|
|
117
|
+
}
|
|
118
|
+
proxySession = current;
|
|
119
|
+
if (proxySession.providerCheckoutSessionId !== null) {
|
|
120
|
+
assertExistingBindingMatches(proxySession, psp);
|
|
121
|
+
const checkoutSession = (await options.stripe.checkout.sessions.retrieve(proxySession.providerCheckoutSessionId, undefined, options.stripeRequestOptions));
|
|
122
|
+
(0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, proxySession.id);
|
|
123
|
+
return {
|
|
124
|
+
binding: bindingFromSession(proxySession),
|
|
125
|
+
cart,
|
|
126
|
+
checkoutSession,
|
|
127
|
+
clientSecret: checkoutSession.client_secret ?? null,
|
|
128
|
+
offer,
|
|
129
|
+
outcome: "ready",
|
|
130
|
+
proxySession,
|
|
131
|
+
proxySessionId: proxySession.id,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (proxySession.currentAcquisitionAttempt !== null) {
|
|
135
|
+
// A competing acquisition now owns the reconciled cart. Do not roll it
|
|
136
|
+
// back underneath that owner.
|
|
137
|
+
rollbackCart = undefined;
|
|
138
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has acquisition ${proxySession.currentAcquisitionAttempt.id} in progress; openCheckout will not create an unrelated Stripe Checkout Session.`, { code: "provider_acquisition_in_progress", field: "current_acquisition_attempt" });
|
|
139
|
+
}
|
|
84
140
|
const params = await options.buildCheckoutSessionParams({
|
|
85
141
|
cart,
|
|
86
142
|
offer,
|
|
87
143
|
proxySession,
|
|
88
144
|
stripe: options.stripe,
|
|
89
145
|
});
|
|
146
|
+
assertCheckoutAutomaticRecoveryDisabled(params, proxySession.id);
|
|
147
|
+
const commercialMode = checkoutCommercialMode(params, proxySession.id);
|
|
90
148
|
const checkoutSession = (await options.stripe.checkout.sessions.create((0, metadata_js_1.injectRequiredCheckoutSessionMetadata)(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
|
|
91
149
|
// Fail closed if the merchant forgot to place proxy_session_id metadata.
|
|
92
150
|
(0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, proxySession.id);
|
|
93
151
|
const binding = await recordOrReconcileProviderBinding({
|
|
94
152
|
checkoutSessionId: checkoutSession.id,
|
|
153
|
+
expectedCartVersion: proxySession.cartVersion,
|
|
154
|
+
commercialMode,
|
|
95
155
|
proxy: options.proxy,
|
|
96
156
|
proxySessionId: proxySession.id,
|
|
97
157
|
psp,
|
|
@@ -105,6 +165,7 @@ async function openCheckout(options) {
|
|
|
105
165
|
checkoutSession,
|
|
106
166
|
clientSecret: checkoutSession.client_secret ?? null,
|
|
107
167
|
offer,
|
|
168
|
+
outcome: "ready",
|
|
108
169
|
proxySession,
|
|
109
170
|
proxySessionId: proxySession.id,
|
|
110
171
|
};
|
|
@@ -122,6 +183,39 @@ async function openCheckout(options) {
|
|
|
122
183
|
throw cause;
|
|
123
184
|
}
|
|
124
185
|
}
|
|
186
|
+
function terminalResultFromSession(proxySession, cart) {
|
|
187
|
+
const common = {
|
|
188
|
+
cart,
|
|
189
|
+
proxySession,
|
|
190
|
+
proxySessionId: proxySession.id,
|
|
191
|
+
};
|
|
192
|
+
switch (proxySession.presentationState) {
|
|
193
|
+
case "already_paid":
|
|
194
|
+
if (proxySession.status !== "paid" &&
|
|
195
|
+
proxySession.status !== "provisionable" &&
|
|
196
|
+
proxySession.status !== "provisioned" &&
|
|
197
|
+
proxySession.status !== "provisioning_failed") {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
return { ...common, outcome: "already_paid", sessionStatus: proxySession.status };
|
|
201
|
+
case "action_required":
|
|
202
|
+
if (proxySession.status !== "merchant_action_required") {
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
return { ...common, outcome: "action_required", sessionStatus: proxySession.status };
|
|
206
|
+
case "unavailable":
|
|
207
|
+
if (proxySession.status !== "cancelled" &&
|
|
208
|
+
proxySession.status !== "created" &&
|
|
209
|
+
proxySession.status !== "expired" &&
|
|
210
|
+
proxySession.status !== "failed") {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
return { ...common, outcome: "unavailable", sessionStatus: proxySession.status };
|
|
214
|
+
case "checkout_available":
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} returned an inconsistent presentation and lifecycle status.`, { code: "invalid_session_presentation", field: "presentation_state" });
|
|
218
|
+
}
|
|
125
219
|
function buildStripeRequestOptions(options, proxySessionId, psp) {
|
|
126
220
|
return {
|
|
127
221
|
...options.stripeRequestOptions,
|
|
@@ -170,6 +264,8 @@ async function rollbackReconciledCart(input) {
|
|
|
170
264
|
async function recordOrReconcileProviderBinding(input) {
|
|
171
265
|
try {
|
|
172
266
|
return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
|
|
267
|
+
commercialMode: input.commercialMode,
|
|
268
|
+
expectedCartVersion: input.expectedCartVersion,
|
|
173
269
|
providerCheckoutSessionId: input.checkoutSessionId,
|
|
174
270
|
psp: input.psp,
|
|
175
271
|
requestId: input.requestId,
|
|
@@ -190,6 +286,37 @@ async function recordOrReconcileProviderBinding(input) {
|
|
|
190
286
|
});
|
|
191
287
|
}
|
|
192
288
|
}
|
|
289
|
+
function sameCheckoutCart(actual, expected) {
|
|
290
|
+
return (actual.amountMinor === expected.amountMinor &&
|
|
291
|
+
actual.currency === expected.currency &&
|
|
292
|
+
canonicalJson(actual.cartSnapshot) === canonicalJson(expected.cartSnapshot));
|
|
293
|
+
}
|
|
294
|
+
function canonicalJson(value) {
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
297
|
+
}
|
|
298
|
+
if (typeof value === "object" && value !== null) {
|
|
299
|
+
return `{${Object.entries(value)
|
|
300
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
301
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`)
|
|
302
|
+
.join(",")}}`;
|
|
303
|
+
}
|
|
304
|
+
return JSON.stringify(value);
|
|
305
|
+
}
|
|
306
|
+
function checkoutCommercialMode(params, proxySessionId) {
|
|
307
|
+
if (params.mode === "payment") {
|
|
308
|
+
return "one_time";
|
|
309
|
+
}
|
|
310
|
+
if (params.mode === "subscription") {
|
|
311
|
+
return "subscription";
|
|
312
|
+
}
|
|
313
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySessionId} requires Stripe Checkout mode payment or subscription.`, { code: "unsupported_provider_option", field: "mode" });
|
|
314
|
+
}
|
|
315
|
+
function assertCheckoutAutomaticRecoveryDisabled(params, proxySessionId) {
|
|
316
|
+
if (params.after_expiration?.recovery?.enabled === true) {
|
|
317
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySessionId} does not allow Stripe Checkout automatic recovery because replacement roots require explicit Proxy supersession.`, { code: "unsupported_provider_option", field: "after_expiration.recovery.enabled" });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
193
320
|
async function readReconciledBinding(input) {
|
|
194
321
|
const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
|
|
195
322
|
requestId: input.requestId,
|
package/dist/cjs/types.d.cts
CHANGED
|
@@ -27,6 +27,11 @@ export interface StripeCheckoutSessionUpdateParams {
|
|
|
27
27
|
readonly metadata?: StripeMetadataParam;
|
|
28
28
|
}
|
|
29
29
|
export type StripeCheckoutSessionCreateParams = object & {
|
|
30
|
+
readonly after_expiration?: {
|
|
31
|
+
readonly recovery?: {
|
|
32
|
+
readonly enabled?: boolean;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
30
35
|
readonly metadata?: StripeMetadataParam;
|
|
31
36
|
readonly mode?: "payment" | "setup" | "subscription";
|
|
32
37
|
readonly payment_intent_data?: unknown;
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface StripeCheckoutSessionUpdateParams {
|
|
|
27
27
|
readonly metadata?: StripeMetadataParam;
|
|
28
28
|
}
|
|
29
29
|
export type StripeCheckoutSessionCreateParams = object & {
|
|
30
|
+
readonly after_expiration?: {
|
|
31
|
+
readonly recovery?: {
|
|
32
|
+
readonly enabled?: boolean;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
30
35
|
readonly metadata?: StripeMetadataParam;
|
|
31
36
|
readonly mode?: "payment" | "setup" | "subscription";
|
|
32
37
|
readonly payment_intent_data?: unknown;
|
package/dist/cjs/version.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
2
|
-
export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1";
|
|
2
|
+
export declare const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.139.1";
|
package/dist/cjs/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
2
|
-
export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1";
|
|
2
|
+
export declare const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.139.1";
|
package/dist/cjs/version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.proxyCheckoutStripeServerSdkVersion = exports.proxyCheckoutStripeServerSdkName = void 0;
|
|
4
4
|
exports.proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
5
|
-
exports.proxyCheckoutStripeServerSdkVersion = "0.0.1";
|
|
5
|
+
exports.proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.139.1";
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { type ProxyStripeCartSyncCode, ProxyStripeCartSyncError, type ProxyStripeOpenCheckoutCode, ProxyStripeOpenCheckoutError, } from "./errors.js";
|
|
2
|
-
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
|
|
3
|
-
export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResult, openCheckout, } from "./open-checkout.js";
|
|
2
|
+
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_INTEGRATION_PATH_METADATA_KEY, PROXY_SESSION_METADATA_KEY, type ProxySessionMetadata, type ProxyStripeRequiredMetadata, requiredMetadata, } from "./metadata.js";
|
|
3
|
+
export { type OpenCheckoutOptions, type OpenCheckoutReconciledCart, type OpenCheckoutResponse, type OpenCheckoutResult, type OpenCheckoutTerminalResult, openCheckout, } from "./open-checkout.js";
|
|
4
4
|
export { normalizePsp } from "./psp.js";
|
|
5
5
|
export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
|
|
6
6
|
export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ProxyStripeCartSyncError, ProxyStripeOpenCheckoutError, } from "./errors.js";
|
|
2
|
-
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, requiredMetadata, } from "./metadata.js";
|
|
2
|
+
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_INTEGRATION_PATH_METADATA_KEY, PROXY_SESSION_METADATA_KEY, requiredMetadata, } from "./metadata.js";
|
|
3
3
|
export { openCheckout, } from "./open-checkout.js";
|
|
4
4
|
export { normalizePsp } from "./psp.js";
|
|
5
5
|
export { syncCheckoutCart, } from "./sync-checkout-cart.js";
|
package/dist/esm/metadata.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripePaymentIntentLike, StripeSubscriptionLike } from "./types.js";
|
|
10
10
|
/** The single metadata key Proxy reads to correlate a Stripe object to a session. */
|
|
11
11
|
export declare const PROXY_SESSION_METADATA_KEY = "proxy_session_id";
|
|
12
|
+
/** Server-owned marker that distinguishes Checkout-created child objects from direct objects. */
|
|
13
|
+
export declare const PROXY_INTEGRATION_PATH_METADATA_KEY = "proxy_integration_path";
|
|
12
14
|
export type ProxySessionMetadata = Readonly<Record<"proxy_session_id", string>>;
|
|
13
15
|
/**
|
|
14
16
|
* The metadata each Stripe object must carry, ready to spread into Stripe params.
|
package/dist/esm/metadata.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { ProxyCheckoutValidationError } from "@proxy-checkout/server-js";
|
|
10
10
|
/** The single metadata key Proxy reads to correlate a Stripe object to a session. */
|
|
11
11
|
export const PROXY_SESSION_METADATA_KEY = "proxy_session_id";
|
|
12
|
+
/** Server-owned marker that distinguishes Checkout-created child objects from direct objects. */
|
|
13
|
+
export const PROXY_INTEGRATION_PATH_METADATA_KEY = "proxy_integration_path";
|
|
12
14
|
/** Build the required Stripe metadata placements for a Proxy session. */
|
|
13
15
|
export function requiredMetadata(proxySession) {
|
|
14
16
|
const metadata = { proxy_session_id: proxySession.id };
|
|
@@ -27,14 +29,18 @@ export function requiredMetadata(proxySession) {
|
|
|
27
29
|
*/
|
|
28
30
|
export function injectRequiredCheckoutSessionMetadata(params, proxySessionId) {
|
|
29
31
|
const metadata = { proxy_session_id: proxySessionId };
|
|
32
|
+
const checkoutMetadata = {
|
|
33
|
+
...metadata,
|
|
34
|
+
[PROXY_INTEGRATION_PATH_METADATA_KEY]: "checkout_session",
|
|
35
|
+
};
|
|
30
36
|
return {
|
|
31
37
|
...params,
|
|
32
|
-
metadata: mergeProxyMetadata(params.metadata,
|
|
38
|
+
metadata: mergeProxyMetadata(params.metadata, checkoutMetadata, "metadata"),
|
|
33
39
|
...(params.mode === "subscription" || params.subscription_data !== undefined
|
|
34
40
|
? {
|
|
35
41
|
subscription_data: {
|
|
36
42
|
...objectRecord(params.subscription_data),
|
|
37
|
-
metadata: mergeProxyMetadata(metadataFromNestedParams(params.subscription_data),
|
|
43
|
+
metadata: mergeProxyMetadata(metadataFromNestedParams(params.subscription_data), checkoutMetadata, "subscription_data.metadata"),
|
|
38
44
|
},
|
|
39
45
|
}
|
|
40
46
|
: {}),
|
|
@@ -42,7 +48,7 @@ export function injectRequiredCheckoutSessionMetadata(params, proxySessionId) {
|
|
|
42
48
|
? {
|
|
43
49
|
payment_intent_data: {
|
|
44
50
|
...objectRecord(params.payment_intent_data),
|
|
45
|
-
metadata: mergeProxyMetadata(metadataFromNestedParams(params.payment_intent_data),
|
|
51
|
+
metadata: mergeProxyMetadata(metadataFromNestedParams(params.payment_intent_data), checkoutMetadata, "payment_intent_data.metadata"),
|
|
46
52
|
},
|
|
47
53
|
}
|
|
48
54
|
: {}),
|
|
@@ -67,9 +73,11 @@ function assertProxyMetadata(metadata, proxySessionId, objectLabel) {
|
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
function mergeProxyMetadata(existing, required, field) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
for (const [key, requiredValue] of Object.entries(required)) {
|
|
77
|
+
const existingValue = existing?.[key];
|
|
78
|
+
if (existingValue !== undefined && existingValue !== requiredValue) {
|
|
79
|
+
throw new ProxyCheckoutValidationError(`Stripe ${field}.${key} (${existingValue}) conflicts with Proxy-required value ${requiredValue}.`, { code: "provider_binding_mismatch", field: `${field}.${key}` });
|
|
80
|
+
}
|
|
73
81
|
}
|
|
74
82
|
return {
|
|
75
83
|
...existing,
|
|
@@ -54,7 +54,28 @@ export interface OpenCheckoutResult<TCart, TOffer, TSession> {
|
|
|
54
54
|
readonly checkoutSession: TSession;
|
|
55
55
|
readonly clientSecret: string | null;
|
|
56
56
|
readonly offer: TOffer;
|
|
57
|
+
readonly outcome: "ready";
|
|
57
58
|
readonly proxySession: PayerOpenedResult;
|
|
58
59
|
readonly proxySessionId: string;
|
|
59
60
|
}
|
|
60
|
-
export
|
|
61
|
+
export type OpenCheckoutTerminalResult<TCart> = {
|
|
62
|
+
readonly cart: TCart;
|
|
63
|
+
readonly outcome: "already_paid";
|
|
64
|
+
readonly proxySession: PayerOpenedResult;
|
|
65
|
+
readonly proxySessionId: string;
|
|
66
|
+
readonly sessionStatus: "paid" | "provisionable" | "provisioned" | "provisioning_failed";
|
|
67
|
+
} | {
|
|
68
|
+
readonly cart: TCart;
|
|
69
|
+
readonly outcome: "action_required";
|
|
70
|
+
readonly proxySession: PayerOpenedResult;
|
|
71
|
+
readonly proxySessionId: string;
|
|
72
|
+
readonly sessionStatus: "merchant_action_required";
|
|
73
|
+
} | {
|
|
74
|
+
readonly cart: TCart;
|
|
75
|
+
readonly outcome: "unavailable";
|
|
76
|
+
readonly proxySession: PayerOpenedResult;
|
|
77
|
+
readonly proxySessionId: string;
|
|
78
|
+
readonly sessionStatus: "cancelled" | "created" | "expired" | "failed";
|
|
79
|
+
};
|
|
80
|
+
export type OpenCheckoutResponse<TCart, TOffer, TSession> = OpenCheckoutResult<TCart, TOffer, TSession> | OpenCheckoutTerminalResult<TCart>;
|
|
81
|
+
export declare function openCheckout<TStripe extends StripeClientLike, TCart = unknown, TOffer = undefined, TSession extends StripeCheckoutSessionLike = StripeCheckoutSessionLike>(options: OpenCheckoutOptions<TStripe, TCart, TOffer>): Promise<OpenCheckoutResponse<TCart, TOffer, TSession>>;
|
|
@@ -24,6 +24,13 @@ export async function openCheckout(options) {
|
|
|
24
24
|
let cart = options.cartSchema || options.getCartBuyerReference
|
|
25
25
|
? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
|
|
26
26
|
: proxySession.cartSnapshot;
|
|
27
|
+
if (proxySession.presentationState !== "checkout_available") {
|
|
28
|
+
return terminalResultFromSession(proxySession, cart);
|
|
29
|
+
}
|
|
30
|
+
if (proxySession.status === "payment_pending" &&
|
|
31
|
+
proxySession.providerCheckoutSessionId === null) {
|
|
32
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has payment preparation in progress without a bound Stripe Checkout Session.`, { code: "provider_binding_missing", field: "provider_checkout_session_id" });
|
|
33
|
+
}
|
|
27
34
|
let offer = options.validateCart
|
|
28
35
|
? await options.validateCart({ cart, session: proxySession })
|
|
29
36
|
: undefined;
|
|
@@ -37,10 +44,14 @@ export async function openCheckout(options) {
|
|
|
37
44
|
checkoutSession,
|
|
38
45
|
clientSecret: checkoutSession.client_secret ?? null,
|
|
39
46
|
offer,
|
|
47
|
+
outcome: "ready",
|
|
40
48
|
proxySession,
|
|
41
49
|
proxySessionId: proxySession.id,
|
|
42
50
|
};
|
|
43
51
|
}
|
|
52
|
+
if (proxySession.currentAcquisitionAttempt !== null) {
|
|
53
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has acquisition ${proxySession.currentAcquisitionAttempt.id} in progress; openCheckout will not create an unrelated Stripe Checkout Session.`, { code: "provider_acquisition_in_progress", field: "current_acquisition_attempt" });
|
|
54
|
+
}
|
|
44
55
|
let rollbackCart;
|
|
45
56
|
const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
|
|
46
57
|
if (reconciledCart) {
|
|
@@ -53,42 +64,91 @@ export async function openCheckout(options) {
|
|
|
53
64
|
cartVersion: proxySession.cartVersion,
|
|
54
65
|
currency: proxySession.currency,
|
|
55
66
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
try {
|
|
68
|
+
const updatedCart = await options.proxy.sessions.cart.set(proxySession.id, {
|
|
69
|
+
amountMinor: reconciledCart.amountMinor,
|
|
70
|
+
cartSnapshot: reconciledCart.cartSnapshot,
|
|
71
|
+
currency: reconciledCart.currency,
|
|
72
|
+
expectedCartVersion: proxySession.cartVersion,
|
|
73
|
+
requestId: options.requestId,
|
|
74
|
+
});
|
|
75
|
+
rollbackCart = {
|
|
76
|
+
...priorCart,
|
|
77
|
+
expectedCartVersion: updatedCart.cartVersion,
|
|
78
|
+
};
|
|
79
|
+
proxySession = {
|
|
80
|
+
...proxySession,
|
|
81
|
+
amountMinor: updatedCart.amountMinor,
|
|
82
|
+
cartSnapshot: updatedCart.cartSnapshot,
|
|
83
|
+
cartVersion: updatedCart.cartVersion,
|
|
84
|
+
currency: updatedCart.currency,
|
|
85
|
+
updatedAt: proxySession.updatedAt,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (cause) {
|
|
89
|
+
if (!isProxyConflict(cause)) {
|
|
90
|
+
throw cause;
|
|
91
|
+
}
|
|
92
|
+
const joined = await options.proxy.sessions.payerOpened(proxySession.id, {
|
|
93
|
+
requestId: options.requestId,
|
|
94
|
+
});
|
|
95
|
+
if (!sameCheckoutCart(joined, reconciledCart)) {
|
|
96
|
+
throw cause;
|
|
97
|
+
}
|
|
98
|
+
proxySession = joined;
|
|
99
|
+
}
|
|
75
100
|
cart = reconciledCart.cart;
|
|
76
101
|
}
|
|
77
102
|
try {
|
|
78
103
|
if (reconciledCart && options.validateCart) {
|
|
79
104
|
offer = await options.validateCart({ cart, session: proxySession });
|
|
80
105
|
}
|
|
106
|
+
const current = await options.proxy.sessions.payerOpened(proxySession.id, {
|
|
107
|
+
requestId: options.requestId,
|
|
108
|
+
});
|
|
109
|
+
if (current.presentationState !== "checkout_available") {
|
|
110
|
+
return terminalResultFromSession(current, cart);
|
|
111
|
+
}
|
|
112
|
+
if (!sameCheckoutCart(current, proxySession)) {
|
|
113
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} cart changed while Checkout was being prepared.`, { code: "cart_version_conflict", field: "cart_version" });
|
|
114
|
+
}
|
|
115
|
+
proxySession = current;
|
|
116
|
+
if (proxySession.providerCheckoutSessionId !== null) {
|
|
117
|
+
assertExistingBindingMatches(proxySession, psp);
|
|
118
|
+
const checkoutSession = (await options.stripe.checkout.sessions.retrieve(proxySession.providerCheckoutSessionId, undefined, options.stripeRequestOptions));
|
|
119
|
+
assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
|
|
120
|
+
return {
|
|
121
|
+
binding: bindingFromSession(proxySession),
|
|
122
|
+
cart,
|
|
123
|
+
checkoutSession,
|
|
124
|
+
clientSecret: checkoutSession.client_secret ?? null,
|
|
125
|
+
offer,
|
|
126
|
+
outcome: "ready",
|
|
127
|
+
proxySession,
|
|
128
|
+
proxySessionId: proxySession.id,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (proxySession.currentAcquisitionAttempt !== null) {
|
|
132
|
+
// A competing acquisition now owns the reconciled cart. Do not roll it
|
|
133
|
+
// back underneath that owner.
|
|
134
|
+
rollbackCart = undefined;
|
|
135
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} already has acquisition ${proxySession.currentAcquisitionAttempt.id} in progress; openCheckout will not create an unrelated Stripe Checkout Session.`, { code: "provider_acquisition_in_progress", field: "current_acquisition_attempt" });
|
|
136
|
+
}
|
|
81
137
|
const params = await options.buildCheckoutSessionParams({
|
|
82
138
|
cart,
|
|
83
139
|
offer,
|
|
84
140
|
proxySession,
|
|
85
141
|
stripe: options.stripe,
|
|
86
142
|
});
|
|
143
|
+
assertCheckoutAutomaticRecoveryDisabled(params, proxySession.id);
|
|
144
|
+
const commercialMode = checkoutCommercialMode(params, proxySession.id);
|
|
87
145
|
const checkoutSession = (await options.stripe.checkout.sessions.create(injectRequiredCheckoutSessionMetadata(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
|
|
88
146
|
// Fail closed if the merchant forgot to place proxy_session_id metadata.
|
|
89
147
|
assertCheckoutSessionBelongsToProxy(checkoutSession, proxySession.id);
|
|
90
148
|
const binding = await recordOrReconcileProviderBinding({
|
|
91
149
|
checkoutSessionId: checkoutSession.id,
|
|
150
|
+
expectedCartVersion: proxySession.cartVersion,
|
|
151
|
+
commercialMode,
|
|
92
152
|
proxy: options.proxy,
|
|
93
153
|
proxySessionId: proxySession.id,
|
|
94
154
|
psp,
|
|
@@ -102,6 +162,7 @@ export async function openCheckout(options) {
|
|
|
102
162
|
checkoutSession,
|
|
103
163
|
clientSecret: checkoutSession.client_secret ?? null,
|
|
104
164
|
offer,
|
|
165
|
+
outcome: "ready",
|
|
105
166
|
proxySession,
|
|
106
167
|
proxySessionId: proxySession.id,
|
|
107
168
|
};
|
|
@@ -119,6 +180,39 @@ export async function openCheckout(options) {
|
|
|
119
180
|
throw cause;
|
|
120
181
|
}
|
|
121
182
|
}
|
|
183
|
+
function terminalResultFromSession(proxySession, cart) {
|
|
184
|
+
const common = {
|
|
185
|
+
cart,
|
|
186
|
+
proxySession,
|
|
187
|
+
proxySessionId: proxySession.id,
|
|
188
|
+
};
|
|
189
|
+
switch (proxySession.presentationState) {
|
|
190
|
+
case "already_paid":
|
|
191
|
+
if (proxySession.status !== "paid" &&
|
|
192
|
+
proxySession.status !== "provisionable" &&
|
|
193
|
+
proxySession.status !== "provisioned" &&
|
|
194
|
+
proxySession.status !== "provisioning_failed") {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
return { ...common, outcome: "already_paid", sessionStatus: proxySession.status };
|
|
198
|
+
case "action_required":
|
|
199
|
+
if (proxySession.status !== "merchant_action_required") {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
return { ...common, outcome: "action_required", sessionStatus: proxySession.status };
|
|
203
|
+
case "unavailable":
|
|
204
|
+
if (proxySession.status !== "cancelled" &&
|
|
205
|
+
proxySession.status !== "created" &&
|
|
206
|
+
proxySession.status !== "expired" &&
|
|
207
|
+
proxySession.status !== "failed") {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
return { ...common, outcome: "unavailable", sessionStatus: proxySession.status };
|
|
211
|
+
case "checkout_available":
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySession.id} returned an inconsistent presentation and lifecycle status.`, { code: "invalid_session_presentation", field: "presentation_state" });
|
|
215
|
+
}
|
|
122
216
|
function buildStripeRequestOptions(options, proxySessionId, psp) {
|
|
123
217
|
return {
|
|
124
218
|
...options.stripeRequestOptions,
|
|
@@ -167,6 +261,8 @@ async function rollbackReconciledCart(input) {
|
|
|
167
261
|
async function recordOrReconcileProviderBinding(input) {
|
|
168
262
|
try {
|
|
169
263
|
return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
|
|
264
|
+
commercialMode: input.commercialMode,
|
|
265
|
+
expectedCartVersion: input.expectedCartVersion,
|
|
170
266
|
providerCheckoutSessionId: input.checkoutSessionId,
|
|
171
267
|
psp: input.psp,
|
|
172
268
|
requestId: input.requestId,
|
|
@@ -187,6 +283,37 @@ async function recordOrReconcileProviderBinding(input) {
|
|
|
187
283
|
});
|
|
188
284
|
}
|
|
189
285
|
}
|
|
286
|
+
function sameCheckoutCart(actual, expected) {
|
|
287
|
+
return (actual.amountMinor === expected.amountMinor &&
|
|
288
|
+
actual.currency === expected.currency &&
|
|
289
|
+
canonicalJson(actual.cartSnapshot) === canonicalJson(expected.cartSnapshot));
|
|
290
|
+
}
|
|
291
|
+
function canonicalJson(value) {
|
|
292
|
+
if (Array.isArray(value)) {
|
|
293
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
294
|
+
}
|
|
295
|
+
if (typeof value === "object" && value !== null) {
|
|
296
|
+
return `{${Object.entries(value)
|
|
297
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
298
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`)
|
|
299
|
+
.join(",")}}`;
|
|
300
|
+
}
|
|
301
|
+
return JSON.stringify(value);
|
|
302
|
+
}
|
|
303
|
+
function checkoutCommercialMode(params, proxySessionId) {
|
|
304
|
+
if (params.mode === "payment") {
|
|
305
|
+
return "one_time";
|
|
306
|
+
}
|
|
307
|
+
if (params.mode === "subscription") {
|
|
308
|
+
return "subscription";
|
|
309
|
+
}
|
|
310
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySessionId} requires Stripe Checkout mode payment or subscription.`, { code: "unsupported_provider_option", field: "mode" });
|
|
311
|
+
}
|
|
312
|
+
function assertCheckoutAutomaticRecoveryDisabled(params, proxySessionId) {
|
|
313
|
+
if (params.after_expiration?.recovery?.enabled === true) {
|
|
314
|
+
throw new ProxyCheckoutValidationError(`Proxy session ${proxySessionId} does not allow Stripe Checkout automatic recovery because replacement roots require explicit Proxy supersession.`, { code: "unsupported_provider_option", field: "after_expiration.recovery.enabled" });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
190
317
|
async function readReconciledBinding(input) {
|
|
191
318
|
const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
|
|
192
319
|
requestId: input.requestId,
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface StripeCheckoutSessionUpdateParams {
|
|
|
27
27
|
readonly metadata?: StripeMetadataParam;
|
|
28
28
|
}
|
|
29
29
|
export type StripeCheckoutSessionCreateParams = object & {
|
|
30
|
+
readonly after_expiration?: {
|
|
31
|
+
readonly recovery?: {
|
|
32
|
+
readonly enabled?: boolean;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
30
35
|
readonly metadata?: StripeMetadataParam;
|
|
31
36
|
readonly mode?: "payment" | "setup" | "subscription";
|
|
32
37
|
readonly payment_intent_data?: unknown;
|
package/dist/esm/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
2
|
-
export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1";
|
|
2
|
+
export declare const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.139.1";
|
package/dist/esm/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
2
|
-
export const proxyCheckoutStripeServerSdkVersion = "0.0.1";
|
|
2
|
+
export const proxyCheckoutStripeServerSdkVersion = "0.1.0-prx-124.139.1";
|
package/package.json
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"type": "module",
|
|
9
9
|
"types": "./dist/esm/index.d.ts",
|
|
10
|
-
"version": "0.0.1",
|
|
10
|
+
"version": "0.1.0-prx-124.139.1",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^24.12.4",
|
|
13
13
|
"@vitest/coverage-v8": "4.1.7",
|
|
14
14
|
"stripe": "^21.0.1",
|
|
15
15
|
"typescript": "^6.0.3",
|
|
16
16
|
"vitest": "4.1.7",
|
|
17
|
-
"@proxy-checkout/server-js": "0.0.
|
|
17
|
+
"@proxy-checkout/server-js": "0.1.0-prx-124.139.1"
|
|
18
18
|
},
|
|
19
19
|
"exports": {
|
|
20
20
|
".": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"sdk"
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@proxy-checkout/server-js": ">=0.0
|
|
44
|
+
"@proxy-checkout/server-js": ">=0.1.0-0 <0.2.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "node ../../scripts/sdk/build-dual-package.mjs",
|
|
56
56
|
"test:all": "vitest run",
|
|
57
|
+
"test:ci": "pnpm run test:coverage",
|
|
57
58
|
"test:coverage": "vitest run --coverage.enabled --coverage.reportsDirectory=coverage/all",
|
|
58
59
|
"test:coverage:changed": "vitest run --coverage.enabled --coverage.reportsDirectory=coverage/changed",
|
|
59
60
|
"test:coverage:integration": "vitest run --project integration --coverage.enabled --coverage.reportsDirectory=coverage/integration",
|