@proxy-checkout/stripe-server-js 0.0.0 → 0.0.1-prx-103.66.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 +8 -9
- package/dist/cjs/errors.d.cts +38 -0
- package/dist/cjs/errors.d.ts +38 -0
- package/dist/cjs/errors.js +38 -0
- package/dist/cjs/index.d.cts +7 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +22 -0
- package/dist/cjs/metadata.d.cts +46 -0
- package/dist/{metadata.d.ts → cjs/metadata.d.ts} +7 -1
- package/dist/cjs/metadata.js +92 -0
- package/dist/cjs/open-checkout.d.cts +60 -0
- package/dist/cjs/open-checkout.d.ts +60 -0
- package/dist/cjs/open-checkout.js +210 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/psp.d.cts +12 -0
- package/dist/cjs/psp.d.ts +12 -0
- package/dist/cjs/psp.js +25 -0
- package/dist/cjs/sync-checkout-cart.d.cts +47 -0
- package/dist/{sync-checkout-cart.d.ts → cjs/sync-checkout-cart.d.ts} +3 -1
- package/dist/cjs/sync-checkout-cart.js +97 -0
- package/dist/cjs/types.d.cts +52 -0
- package/dist/{types.d.ts → cjs/types.d.ts} +20 -3
- package/dist/cjs/types.js +10 -0
- package/dist/cjs/version.d.cts +2 -0
- package/dist/{version.d.ts → cjs/version.d.ts} +1 -1
- package/dist/cjs/version.js +5 -0
- package/dist/esm/errors.d.ts +38 -0
- package/dist/esm/errors.js +33 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/metadata.d.ts +46 -0
- package/dist/{metadata.js → esm/metadata.js} +44 -0
- package/dist/esm/open-checkout.d.ts +60 -0
- package/dist/esm/open-checkout.js +207 -0
- package/dist/esm/psp.d.ts +12 -0
- package/dist/esm/psp.js +22 -0
- package/dist/esm/sync-checkout-cart.d.ts +47 -0
- package/dist/{sync-checkout-cart.js → esm/sync-checkout-cart.js} +37 -17
- package/dist/esm/types.d.ts +52 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/{version.js → esm/version.js} +1 -1
- package/package.json +18 -9
- package/dist/errors.d.ts +0 -20
- package/dist/errors.js +0 -15
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -5
- package/dist/open-checkout.d.ts +0 -48
- package/dist/open-checkout.js +0 -48
- /package/dist/{types.js → esm/types.js} +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `openCheckout` — the payer-opened + provider-object creation workflow.
|
|
4
|
+
*
|
|
5
|
+
* It performs the Proxy protocol steps (record payer-opened, validate the cart,
|
|
6
|
+
* inject required metadata, create the Stripe Checkout Session, record the
|
|
7
|
+
* provider binding). Merchants provide only Stripe params, so the adapter owns
|
|
8
|
+
* metadata placement, idempotency, and binding reconciliation.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.openCheckout = openCheckout;
|
|
12
|
+
const server_js_1 = require("@proxy-checkout/server-js");
|
|
13
|
+
const errors_js_1 = require("./errors.js");
|
|
14
|
+
const metadata_js_1 = require("./metadata.js");
|
|
15
|
+
const psp_js_1 = require("./psp.js");
|
|
16
|
+
async function openCheckout(options) {
|
|
17
|
+
// Normalize the same way the API does on store, so the binding is recorded
|
|
18
|
+
// under the normalized psp and later syncCheckoutCart validations compare
|
|
19
|
+
// normalized-to-normalized.
|
|
20
|
+
const psp = (0, psp_js_1.normalizePsp)(options.psp ?? "stripe");
|
|
21
|
+
let proxySession = await options.proxy.sessions.payerOpened(options.proxySessionId, {
|
|
22
|
+
requestId: options.requestId,
|
|
23
|
+
});
|
|
24
|
+
// Parse the cart (and run the buyer-reference check) whenever a schema OR a
|
|
25
|
+
// buyer-reference extractor is provided, so the consistency check is never
|
|
26
|
+
// silently skipped just because no custom schema was passed.
|
|
27
|
+
let cart = options.cartSchema || options.getCartBuyerReference
|
|
28
|
+
? options.proxy.sessions.parseCart(proxySession, options.cartSchema ?? ((value) => value), { getBuyerReference: options.getCartBuyerReference })
|
|
29
|
+
: proxySession.cartSnapshot;
|
|
30
|
+
let offer = options.validateCart
|
|
31
|
+
? await options.validateCart({ cart, session: proxySession })
|
|
32
|
+
: undefined;
|
|
33
|
+
if (proxySession.providerCheckoutSessionId !== null) {
|
|
34
|
+
assertExistingBindingMatches(proxySession, psp);
|
|
35
|
+
const checkoutSession = (await options.stripe.checkout.sessions.retrieve(proxySession.providerCheckoutSessionId, undefined, options.stripeRequestOptions));
|
|
36
|
+
(0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, proxySession.id);
|
|
37
|
+
return {
|
|
38
|
+
binding: bindingFromSession(proxySession),
|
|
39
|
+
cart,
|
|
40
|
+
checkoutSession,
|
|
41
|
+
clientSecret: checkoutSession.client_secret ?? null,
|
|
42
|
+
offer,
|
|
43
|
+
proxySession,
|
|
44
|
+
proxySessionId: proxySession.id,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
let rollbackCart;
|
|
48
|
+
const reconciledCart = await options.reconcileCart?.({ cart, offer, session: proxySession });
|
|
49
|
+
if (reconciledCart) {
|
|
50
|
+
if (options.getCartBuyerReference !== undefined) {
|
|
51
|
+
(0, server_js_1.assertCartBuyerReference)(options.getCartBuyerReference(reconciledCart.cart), proxySession);
|
|
52
|
+
}
|
|
53
|
+
const priorCart = {
|
|
54
|
+
amountMinor: proxySession.amountMinor,
|
|
55
|
+
cartSnapshot: proxySession.cartSnapshot,
|
|
56
|
+
cartVersion: proxySession.cartVersion,
|
|
57
|
+
currency: proxySession.currency,
|
|
58
|
+
};
|
|
59
|
+
const updatedCart = await options.proxy.sessions.cart.set(proxySession.id, {
|
|
60
|
+
amountMinor: reconciledCart.amountMinor,
|
|
61
|
+
cartSnapshot: reconciledCart.cartSnapshot,
|
|
62
|
+
currency: reconciledCart.currency,
|
|
63
|
+
expectedCartVersion: proxySession.cartVersion,
|
|
64
|
+
requestId: options.requestId,
|
|
65
|
+
});
|
|
66
|
+
rollbackCart = {
|
|
67
|
+
...priorCart,
|
|
68
|
+
expectedCartVersion: updatedCart.cartVersion,
|
|
69
|
+
};
|
|
70
|
+
proxySession = {
|
|
71
|
+
...proxySession,
|
|
72
|
+
amountMinor: updatedCart.amountMinor,
|
|
73
|
+
cartSnapshot: updatedCart.cartSnapshot,
|
|
74
|
+
cartVersion: updatedCart.cartVersion,
|
|
75
|
+
currency: updatedCart.currency,
|
|
76
|
+
updatedAt: proxySession.updatedAt,
|
|
77
|
+
};
|
|
78
|
+
cart = reconciledCart.cart;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
if (reconciledCart && options.validateCart) {
|
|
82
|
+
offer = await options.validateCart({ cart, session: proxySession });
|
|
83
|
+
}
|
|
84
|
+
const params = await options.buildCheckoutSessionParams({
|
|
85
|
+
cart,
|
|
86
|
+
offer,
|
|
87
|
+
proxySession,
|
|
88
|
+
stripe: options.stripe,
|
|
89
|
+
});
|
|
90
|
+
const checkoutSession = (await options.stripe.checkout.sessions.create((0, metadata_js_1.injectRequiredCheckoutSessionMetadata)(params, proxySession.id), buildStripeRequestOptions(options, proxySession.id, psp)));
|
|
91
|
+
// Fail closed if the merchant forgot to place proxy_session_id metadata.
|
|
92
|
+
(0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, proxySession.id);
|
|
93
|
+
const binding = await recordOrReconcileProviderBinding({
|
|
94
|
+
checkoutSessionId: checkoutSession.id,
|
|
95
|
+
proxy: options.proxy,
|
|
96
|
+
proxySessionId: proxySession.id,
|
|
97
|
+
psp,
|
|
98
|
+
requestId: options.requestId,
|
|
99
|
+
stripe: options.stripe,
|
|
100
|
+
stripeRequestOptions: options.stripeRequestOptions,
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
binding,
|
|
104
|
+
cart,
|
|
105
|
+
checkoutSession,
|
|
106
|
+
clientSecret: checkoutSession.client_secret ?? null,
|
|
107
|
+
offer,
|
|
108
|
+
proxySession,
|
|
109
|
+
proxySessionId: proxySession.id,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (cause) {
|
|
113
|
+
if (rollbackCart) {
|
|
114
|
+
await rollbackReconciledCart({
|
|
115
|
+
cause,
|
|
116
|
+
proxy: options.proxy,
|
|
117
|
+
proxySessionId: proxySession.id,
|
|
118
|
+
requestId: options.requestId,
|
|
119
|
+
rollbackCart,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
throw cause;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function buildStripeRequestOptions(options, proxySessionId, psp) {
|
|
126
|
+
return {
|
|
127
|
+
...options.stripeRequestOptions,
|
|
128
|
+
idempotencyKey: `proxy-open-checkout:${proxySessionId}:${psp}`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function assertExistingBindingMatches(proxySession, psp) {
|
|
132
|
+
if (proxySession.providerCheckoutSessionId === null) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (proxySession.providerCheckoutSessionPsp !== psp) {
|
|
136
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${proxySession.id} is already bound under psp ${proxySession.providerCheckoutSessionPsp}.`, { code: "provider_binding_mismatch", field: "provider_checkout_session_psp" });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function bindingFromSession(proxySession) {
|
|
140
|
+
if (proxySession.providerCheckoutSessionId === null ||
|
|
141
|
+
proxySession.providerCheckoutSessionPsp === null) {
|
|
142
|
+
throw new Error("Proxy session is not bound to a provider checkout session.");
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
providerCheckoutSessionId: proxySession.providerCheckoutSessionId,
|
|
146
|
+
proxySessionId: proxySession.id,
|
|
147
|
+
psp: proxySession.providerCheckoutSessionPsp,
|
|
148
|
+
updatedAt: proxySession.updatedAt,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function rollbackReconciledCart(input) {
|
|
152
|
+
try {
|
|
153
|
+
await input.proxy.sessions.cart.set(input.proxySessionId, {
|
|
154
|
+
amountMinor: input.rollbackCart.amountMinor,
|
|
155
|
+
cartSnapshot: input.rollbackCart.cartSnapshot,
|
|
156
|
+
currency: input.rollbackCart.currency,
|
|
157
|
+
expectedCartVersion: input.rollbackCart.expectedCartVersion,
|
|
158
|
+
requestId: input.requestId,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (rollbackCause) {
|
|
162
|
+
throw new errors_js_1.ProxyStripeCartSyncError("openCheckout failed after reconciling the Proxy cart, and the rollback failed; carts may be out of sync.", {
|
|
163
|
+
cause: input.cause,
|
|
164
|
+
code: "reconciliation_failed",
|
|
165
|
+
reconciled: false,
|
|
166
|
+
rollbackCause,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function recordOrReconcileProviderBinding(input) {
|
|
171
|
+
try {
|
|
172
|
+
return await input.proxy.sessions.recordProviderBinding(input.proxySessionId, {
|
|
173
|
+
providerCheckoutSessionId: input.checkoutSessionId,
|
|
174
|
+
psp: input.psp,
|
|
175
|
+
requestId: input.requestId,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (cause) {
|
|
179
|
+
const reconciled = await readReconciledBinding(input).catch(() => undefined);
|
|
180
|
+
if (reconciled) {
|
|
181
|
+
return reconciled;
|
|
182
|
+
}
|
|
183
|
+
if (isProxyConflict(cause)) {
|
|
184
|
+
throw cause;
|
|
185
|
+
}
|
|
186
|
+
throw new errors_js_1.ProxyStripeOpenCheckoutError("Stripe Checkout Session was created but Proxy provider binding could not be recorded.", {
|
|
187
|
+
cause,
|
|
188
|
+
code: "provider_binding_failed",
|
|
189
|
+
providerCheckoutSessionId: input.checkoutSessionId,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function readReconciledBinding(input) {
|
|
194
|
+
const current = await input.proxy.sessions.retrieve(input.proxySessionId, {
|
|
195
|
+
requestId: input.requestId,
|
|
196
|
+
});
|
|
197
|
+
if (current.providerCheckoutSessionId !== input.checkoutSessionId ||
|
|
198
|
+
current.providerCheckoutSessionPsp !== input.psp) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
const checkoutSession = await input.stripe.checkout.sessions.retrieve(input.checkoutSessionId, undefined, input.stripeRequestOptions);
|
|
202
|
+
(0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(checkoutSession, input.proxySessionId);
|
|
203
|
+
return bindingFromSession(current);
|
|
204
|
+
}
|
|
205
|
+
function isProxyConflict(error) {
|
|
206
|
+
return (typeof error === "object" &&
|
|
207
|
+
error !== null &&
|
|
208
|
+
"statusCode" in error &&
|
|
209
|
+
error.statusCode === 409);
|
|
210
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider (psp) identifier normalization, kept in lockstep with the Proxy API.
|
|
3
|
+
*
|
|
4
|
+
* The API normalizes the psp before persisting a provider binding
|
|
5
|
+
* (`psp.trim().toLowerCase()`), then compares stored bindings against that
|
|
6
|
+
* normalized value. The SDK must normalize identically before recording or
|
|
7
|
+
* validating a binding so a caller passing the same psp with different
|
|
8
|
+
* casing/whitespace for `openCheckout` vs `syncCheckoutCart` does not get a
|
|
9
|
+
* false `provider_binding_mismatch`.
|
|
10
|
+
*/
|
|
11
|
+
/** Normalize and validate a psp identifier exactly as the Proxy API does before storing/comparing a binding. */
|
|
12
|
+
export declare function normalizePsp(psp: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider (psp) identifier normalization, kept in lockstep with the Proxy API.
|
|
3
|
+
*
|
|
4
|
+
* The API normalizes the psp before persisting a provider binding
|
|
5
|
+
* (`psp.trim().toLowerCase()`), then compares stored bindings against that
|
|
6
|
+
* normalized value. The SDK must normalize identically before recording or
|
|
7
|
+
* validating a binding so a caller passing the same psp with different
|
|
8
|
+
* casing/whitespace for `openCheckout` vs `syncCheckoutCart` does not get a
|
|
9
|
+
* false `provider_binding_mismatch`.
|
|
10
|
+
*/
|
|
11
|
+
/** Normalize and validate a psp identifier exactly as the Proxy API does before storing/comparing a binding. */
|
|
12
|
+
export declare function normalizePsp(psp: string): string;
|
package/dist/cjs/psp.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizePsp = normalizePsp;
|
|
4
|
+
const server_js_1 = require("@proxy-checkout/server-js");
|
|
5
|
+
/**
|
|
6
|
+
* Provider (psp) identifier normalization, kept in lockstep with the Proxy API.
|
|
7
|
+
*
|
|
8
|
+
* The API normalizes the psp before persisting a provider binding
|
|
9
|
+
* (`psp.trim().toLowerCase()`), then compares stored bindings against that
|
|
10
|
+
* normalized value. The SDK must normalize identically before recording or
|
|
11
|
+
* validating a binding so a caller passing the same psp with different
|
|
12
|
+
* casing/whitespace for `openCheckout` vs `syncCheckoutCart` does not get a
|
|
13
|
+
* false `provider_binding_mismatch`.
|
|
14
|
+
*/
|
|
15
|
+
/** Normalize and validate a psp identifier exactly as the Proxy API does before storing/comparing a binding. */
|
|
16
|
+
function normalizePsp(psp) {
|
|
17
|
+
const normalized = psp.trim().toLowerCase();
|
|
18
|
+
if (!/^[a-z0-9_]{1,32}$/.test(normalized)) {
|
|
19
|
+
throw new server_js_1.ProxyCheckoutValidationError("psp must be a short lowercase identifier", {
|
|
20
|
+
code: "provider_binding_mismatch",
|
|
21
|
+
field: "psp",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `syncCheckoutCart` — coordinated Proxy + Stripe cart update.
|
|
3
|
+
*
|
|
4
|
+
* Validates ownership against the first-class Proxy provider binding (not just
|
|
5
|
+
* Stripe metadata), then updates the Proxy cart and the Stripe checkout in the
|
|
6
|
+
* safest order with deterministic rollback/reconciliation on failure.
|
|
7
|
+
*/
|
|
8
|
+
import { type JsonObject, type MerchantProxySession, type ProxyCartValidator, type ProxyCheckoutServerClient } from "@proxy-checkout/server-js";
|
|
9
|
+
import type { StripeCheckoutSessionLike, StripeClientLike, StripeRequestOptionsWithoutIdempotencyLike } from "./types.js";
|
|
10
|
+
/** The next cart, expressed both as Proxy state and the typed merchant cart. */
|
|
11
|
+
export interface SyncedCart<TCart> {
|
|
12
|
+
readonly amountMinor: number;
|
|
13
|
+
readonly cart: TCart;
|
|
14
|
+
readonly cartSnapshot: JsonObject;
|
|
15
|
+
readonly currency: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SyncCheckoutCartOptions<TStripe extends StripeClientLike, TCart, TInput> {
|
|
18
|
+
/** Compute the next cart from current state and the merchant input. */
|
|
19
|
+
readonly buildNextCart: (input: {
|
|
20
|
+
currentCart: TCart;
|
|
21
|
+
currentSession: MerchantProxySession;
|
|
22
|
+
input: TInput;
|
|
23
|
+
}) => Promise<SyncedCart<TCart>> | SyncedCart<TCart>;
|
|
24
|
+
/** Derive the Stripe `line_items` update from the next typed cart. */
|
|
25
|
+
readonly buildStripeLineItems: (cart: TCart) => unknown;
|
|
26
|
+
/** Optional schema validating the current cart snapshot into a typed cart. */
|
|
27
|
+
readonly cartSchema?: ProxyCartValidator<TCart>;
|
|
28
|
+
readonly checkoutSessionId: string;
|
|
29
|
+
/** Merchant-supplied change request (e.g. new interval/quantity). */
|
|
30
|
+
readonly input: TInput;
|
|
31
|
+
readonly proxy: ProxyCheckoutServerClient;
|
|
32
|
+
readonly proxySessionId: string;
|
|
33
|
+
/** Provider identifier the session must be bound under. Defaults to "stripe". */
|
|
34
|
+
readonly psp?: string;
|
|
35
|
+
readonly requestId?: string;
|
|
36
|
+
readonly stripe: TStripe;
|
|
37
|
+
/** Optional Stripe request options, e.g. Stripe Connect account scoping. */
|
|
38
|
+
readonly stripeRequestOptions?: StripeRequestOptionsWithoutIdempotencyLike;
|
|
39
|
+
}
|
|
40
|
+
export interface SyncCheckoutCartResult<TCart> {
|
|
41
|
+
readonly amountMinor: number;
|
|
42
|
+
readonly cart: TCart;
|
|
43
|
+
readonly cartSnapshot: JsonObject;
|
|
44
|
+
readonly checkoutSession: StripeCheckoutSessionLike;
|
|
45
|
+
readonly currency: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function syncCheckoutCart<TStripe extends StripeClientLike, TCart, TInput>(options: SyncCheckoutCartOptions<TStripe, TCart, TInput>): Promise<SyncCheckoutCartResult<TCart>>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* safest order with deterministic rollback/reconciliation on failure.
|
|
7
7
|
*/
|
|
8
8
|
import { type JsonObject, type MerchantProxySession, type ProxyCartValidator, type ProxyCheckoutServerClient } from "@proxy-checkout/server-js";
|
|
9
|
-
import type { StripeCheckoutSessionLike, StripeClientLike } from "./types.js";
|
|
9
|
+
import type { StripeCheckoutSessionLike, StripeClientLike, StripeRequestOptionsWithoutIdempotencyLike } from "./types.js";
|
|
10
10
|
/** The next cart, expressed both as Proxy state and the typed merchant cart. */
|
|
11
11
|
export interface SyncedCart<TCart> {
|
|
12
12
|
readonly amountMinor: number;
|
|
@@ -34,6 +34,8 @@ export interface SyncCheckoutCartOptions<TStripe extends StripeClientLike, TCart
|
|
|
34
34
|
readonly psp?: string;
|
|
35
35
|
readonly requestId?: string;
|
|
36
36
|
readonly stripe: TStripe;
|
|
37
|
+
/** Optional Stripe request options, e.g. Stripe Connect account scoping. */
|
|
38
|
+
readonly stripeRequestOptions?: StripeRequestOptionsWithoutIdempotencyLike;
|
|
37
39
|
}
|
|
38
40
|
export interface SyncCheckoutCartResult<TCart> {
|
|
39
41
|
readonly amountMinor: number;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `syncCheckoutCart` — coordinated Proxy + Stripe cart update.
|
|
4
|
+
*
|
|
5
|
+
* Validates ownership against the first-class Proxy provider binding (not just
|
|
6
|
+
* Stripe metadata), then updates the Proxy cart and the Stripe checkout in the
|
|
7
|
+
* safest order with deterministic rollback/reconciliation on failure.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.syncCheckoutCart = syncCheckoutCart;
|
|
11
|
+
const server_js_1 = require("@proxy-checkout/server-js");
|
|
12
|
+
const errors_js_1 = require("./errors.js");
|
|
13
|
+
const metadata_js_1 = require("./metadata.js");
|
|
14
|
+
const psp_js_1 = require("./psp.js");
|
|
15
|
+
async function syncCheckoutCart(options) {
|
|
16
|
+
// Normalize the same way the API does on store, so the binding comparison is
|
|
17
|
+
// normalized-to-normalized and a custom psp with differing casing/whitespace
|
|
18
|
+
// does not produce a false provider_binding_mismatch.
|
|
19
|
+
const psp = (0, psp_js_1.normalizePsp)(options.psp ?? "stripe");
|
|
20
|
+
const session = await options.proxy.sessions.retrieve(options.proxySessionId, {
|
|
21
|
+
requestId: options.requestId,
|
|
22
|
+
});
|
|
23
|
+
// Ownership: validate against the stored binding first, then defensively
|
|
24
|
+
// against the Stripe object's own metadata.
|
|
25
|
+
assertSessionBoundTo(session, options.checkoutSessionId, psp);
|
|
26
|
+
const stripeSession = await options.stripe.checkout.sessions.retrieve(options.checkoutSessionId, undefined, options.stripeRequestOptions);
|
|
27
|
+
(0, metadata_js_1.assertCheckoutSessionBelongsToProxy)(stripeSession, options.proxySessionId);
|
|
28
|
+
const currentCart = options.cartSchema
|
|
29
|
+
? options.proxy.sessions.parseCart(session, options.cartSchema)
|
|
30
|
+
: session.cartSnapshot;
|
|
31
|
+
const next = await options.buildNextCart({
|
|
32
|
+
currentCart,
|
|
33
|
+
currentSession: session,
|
|
34
|
+
input: options.input,
|
|
35
|
+
});
|
|
36
|
+
const prior = {
|
|
37
|
+
amountMinor: session.amountMinor,
|
|
38
|
+
cartSnapshot: session.cartSnapshot,
|
|
39
|
+
cartVersion: session.cartVersion,
|
|
40
|
+
currency: session.currency,
|
|
41
|
+
};
|
|
42
|
+
const stripeUpdateParams = {
|
|
43
|
+
line_items: options.buildStripeLineItems(next.cart),
|
|
44
|
+
};
|
|
45
|
+
// Update Proxy first; the API rejects a non-editable session before Stripe is touched.
|
|
46
|
+
const proxyCartUpdate = await options.proxy.sessions.cart.set(options.proxySessionId, {
|
|
47
|
+
amountMinor: next.amountMinor,
|
|
48
|
+
cartSnapshot: next.cartSnapshot,
|
|
49
|
+
currency: next.currency,
|
|
50
|
+
expectedCartVersion: session.cartVersion,
|
|
51
|
+
requestId: options.requestId,
|
|
52
|
+
});
|
|
53
|
+
const stripeUpdateOptions = buildStripeUpdateRequestOptions(options, proxyCartUpdate.cartVersion);
|
|
54
|
+
let updatedCheckout;
|
|
55
|
+
try {
|
|
56
|
+
updatedCheckout = await options.stripe.checkout.sessions.update(options.checkoutSessionId, stripeUpdateParams, stripeUpdateOptions);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
try {
|
|
60
|
+
updatedCheckout = await options.stripe.checkout.sessions.update(options.checkoutSessionId, stripeUpdateParams, stripeUpdateOptions);
|
|
61
|
+
}
|
|
62
|
+
catch (retryCause) {
|
|
63
|
+
try {
|
|
64
|
+
await options.proxy.sessions.cart.set(options.proxySessionId, {
|
|
65
|
+
amountMinor: prior.amountMinor,
|
|
66
|
+
cartSnapshot: prior.cartSnapshot,
|
|
67
|
+
currency: prior.currency,
|
|
68
|
+
expectedCartVersion: proxyCartUpdate.cartVersion,
|
|
69
|
+
requestId: options.requestId,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (rollbackCause) {
|
|
73
|
+
throw new errors_js_1.ProxyStripeCartSyncError("Stripe checkout update failed after an idempotent retry and the Proxy cart rollback failed; carts may be out of sync.", { cause: retryCause, code: "reconciliation_failed", reconciled: false, rollbackCause });
|
|
74
|
+
}
|
|
75
|
+
throw new errors_js_1.ProxyStripeCartSyncError("Stripe checkout update failed after an idempotent retry; the Proxy cart rollback was attempted, but Stripe state may still be ambiguous.", { cause: retryCause, code: "stripe_update_failed", reconciled: false });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
amountMinor: next.amountMinor,
|
|
80
|
+
cart: next.cart,
|
|
81
|
+
cartSnapshot: next.cartSnapshot,
|
|
82
|
+
checkoutSession: updatedCheckout,
|
|
83
|
+
currency: proxyCartUpdate.currency,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildStripeUpdateRequestOptions(options, cartVersion) {
|
|
87
|
+
return {
|
|
88
|
+
...options.stripeRequestOptions,
|
|
89
|
+
idempotencyKey: `proxy-sync-checkout-cart:${options.proxySessionId}:${options.checkoutSessionId}:${cartVersion}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function assertSessionBoundTo(session, checkoutSessionId, psp) {
|
|
93
|
+
if (session.providerCheckoutSessionId !== checkoutSessionId ||
|
|
94
|
+
session.providerCheckoutSessionPsp !== psp) {
|
|
95
|
+
throw new server_js_1.ProxyCheckoutValidationError(`Proxy session ${session.id} is not bound to provider checkout session ${checkoutSessionId}.`, { code: "provider_binding_mismatch", field: "provider_checkout_session_id" });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural Stripe types.
|
|
3
|
+
*
|
|
4
|
+
* The adapter is dependency-free: merchants inject their own `Stripe` instance
|
|
5
|
+
* and objects, which are structurally compatible with these narrow interfaces.
|
|
6
|
+
* This avoids a runtime/build dependency on the `stripe` package while keeping
|
|
7
|
+
* the bits the adapter actually touches type-safe.
|
|
8
|
+
*/
|
|
9
|
+
export type StripeMetadataValue = string | number | null;
|
|
10
|
+
export type StripeMetadata = Record<string, StripeMetadataValue> | null | undefined;
|
|
11
|
+
export type StripeMetadataParam = Record<string, StripeMetadataValue> | undefined;
|
|
12
|
+
export interface StripeCheckoutSessionLike {
|
|
13
|
+
readonly client_secret?: string | null;
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly metadata?: StripeMetadata;
|
|
16
|
+
}
|
|
17
|
+
export interface StripeSubscriptionLike {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly metadata?: StripeMetadata;
|
|
20
|
+
}
|
|
21
|
+
export interface StripePaymentIntentLike {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly metadata?: StripeMetadata;
|
|
24
|
+
}
|
|
25
|
+
export interface StripeCheckoutSessionUpdateParams {
|
|
26
|
+
readonly line_items?: unknown;
|
|
27
|
+
readonly metadata?: StripeMetadataParam;
|
|
28
|
+
}
|
|
29
|
+
export type StripeCheckoutSessionCreateParams = object & {
|
|
30
|
+
readonly metadata?: StripeMetadataParam;
|
|
31
|
+
readonly mode?: "payment" | "setup" | "subscription";
|
|
32
|
+
readonly payment_intent_data?: unknown;
|
|
33
|
+
readonly subscription_data?: unknown;
|
|
34
|
+
};
|
|
35
|
+
export interface StripeRequestOptionsLike {
|
|
36
|
+
readonly idempotencyKey?: string;
|
|
37
|
+
readonly [key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface StripeRequestOptionsWithoutIdempotencyLike {
|
|
40
|
+
readonly idempotencyKey?: never;
|
|
41
|
+
readonly [key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
export interface StripeCheckoutSessionsApiLike {
|
|
44
|
+
create(params: unknown, options?: unknown): Promise<StripeCheckoutSessionLike>;
|
|
45
|
+
retrieve(id: string, params?: unknown, options?: unknown): Promise<StripeCheckoutSessionLike>;
|
|
46
|
+
update(id: string, params: unknown, options?: unknown): Promise<StripeCheckoutSessionLike>;
|
|
47
|
+
}
|
|
48
|
+
export interface StripeClientLike {
|
|
49
|
+
readonly checkout: {
|
|
50
|
+
readonly sessions: StripeCheckoutSessionsApiLike;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* This avoids a runtime/build dependency on the `stripe` package while keeping
|
|
7
7
|
* the bits the adapter actually touches type-safe.
|
|
8
8
|
*/
|
|
9
|
-
export type
|
|
9
|
+
export type StripeMetadataValue = string | number | null;
|
|
10
|
+
export type StripeMetadata = Record<string, StripeMetadataValue> | null | undefined;
|
|
11
|
+
export type StripeMetadataParam = Record<string, StripeMetadataValue> | undefined;
|
|
10
12
|
export interface StripeCheckoutSessionLike {
|
|
11
13
|
readonly client_secret?: string | null;
|
|
12
14
|
readonly id: string;
|
|
@@ -22,11 +24,26 @@ export interface StripePaymentIntentLike {
|
|
|
22
24
|
}
|
|
23
25
|
export interface StripeCheckoutSessionUpdateParams {
|
|
24
26
|
readonly line_items?: unknown;
|
|
25
|
-
readonly metadata?:
|
|
27
|
+
readonly metadata?: StripeMetadataParam;
|
|
28
|
+
}
|
|
29
|
+
export type StripeCheckoutSessionCreateParams = object & {
|
|
30
|
+
readonly metadata?: StripeMetadataParam;
|
|
31
|
+
readonly mode?: "payment" | "setup" | "subscription";
|
|
32
|
+
readonly payment_intent_data?: unknown;
|
|
33
|
+
readonly subscription_data?: unknown;
|
|
34
|
+
};
|
|
35
|
+
export interface StripeRequestOptionsLike {
|
|
36
|
+
readonly idempotencyKey?: string;
|
|
37
|
+
readonly [key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface StripeRequestOptionsWithoutIdempotencyLike {
|
|
40
|
+
readonly idempotencyKey?: never;
|
|
41
|
+
readonly [key: string]: unknown;
|
|
26
42
|
}
|
|
27
43
|
export interface StripeCheckoutSessionsApiLike {
|
|
44
|
+
create(params: unknown, options?: unknown): Promise<StripeCheckoutSessionLike>;
|
|
28
45
|
retrieve(id: string, params?: unknown, options?: unknown): Promise<StripeCheckoutSessionLike>;
|
|
29
|
-
update(id: string, params:
|
|
46
|
+
update(id: string, params: unknown, options?: unknown): Promise<StripeCheckoutSessionLike>;
|
|
30
47
|
}
|
|
31
48
|
export interface StripeClientLike {
|
|
32
49
|
readonly checkout: {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural Stripe types.
|
|
4
|
+
*
|
|
5
|
+
* The adapter is dependency-free: merchants inject their own `Stripe` instance
|
|
6
|
+
* and objects, which are structurally compatible with these narrow interfaces.
|
|
7
|
+
* This avoids a runtime/build dependency on the `stripe` package while keeping
|
|
8
|
+
* the bits the adapter actually touches type-safe.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
2
|
-
export declare const proxyCheckoutStripeServerSdkVersion = "0.0.
|
|
2
|
+
export declare const proxyCheckoutStripeServerSdkVersion = "0.0.1-prx-103.66.1";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.proxyCheckoutStripeServerSdkVersion = exports.proxyCheckoutStripeServerSdkName = void 0;
|
|
4
|
+
exports.proxyCheckoutStripeServerSdkName = "@proxy-checkout/stripe-server-js";
|
|
5
|
+
exports.proxyCheckoutStripeServerSdkVersion = "0.0.1-prx-103.66.1";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter-specific error raised when a Stripe/Proxy cart synchronization cannot
|
|
3
|
+
* complete cleanly. The `reconciled` flag is true only when the SDK verified the
|
|
4
|
+
* Proxy and Stripe carts are back in a consistent state.
|
|
5
|
+
*/
|
|
6
|
+
export type ProxyStripeCartSyncCode = "reconciliation_failed" | "stripe_update_failed";
|
|
7
|
+
export type ProxyStripeOpenCheckoutCode = "provider_binding_failed";
|
|
8
|
+
/**
|
|
9
|
+
* Raised after Stripe Checkout creation succeeds but the Proxy binding cannot
|
|
10
|
+
* be recorded or reconciled. The provider id is exposed so operators can find
|
|
11
|
+
* and expire/refund the orphaned provider object deterministically.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ProxyStripeOpenCheckoutError extends Error {
|
|
14
|
+
readonly name = "ProxyStripeOpenCheckoutError";
|
|
15
|
+
readonly code: ProxyStripeOpenCheckoutCode;
|
|
16
|
+
readonly cause: unknown;
|
|
17
|
+
readonly providerCheckoutSessionId: string;
|
|
18
|
+
readonly reconciled = false;
|
|
19
|
+
constructor(message: string, details: {
|
|
20
|
+
cause: unknown;
|
|
21
|
+
code: ProxyStripeOpenCheckoutCode;
|
|
22
|
+
providerCheckoutSessionId: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export declare class ProxyStripeCartSyncError extends Error {
|
|
26
|
+
readonly name = "ProxyStripeCartSyncError";
|
|
27
|
+
readonly code: ProxyStripeCartSyncCode;
|
|
28
|
+
/** True only when the SDK verified Proxy and Stripe are in a consistent state. */
|
|
29
|
+
readonly reconciled: boolean;
|
|
30
|
+
readonly cause: unknown;
|
|
31
|
+
readonly rollbackCause: unknown;
|
|
32
|
+
constructor(message: string, details: {
|
|
33
|
+
code: ProxyStripeCartSyncCode;
|
|
34
|
+
reconciled: boolean;
|
|
35
|
+
cause: unknown;
|
|
36
|
+
rollbackCause?: unknown;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised after Stripe Checkout creation succeeds but the Proxy binding cannot
|
|
3
|
+
* be recorded or reconciled. The provider id is exposed so operators can find
|
|
4
|
+
* and expire/refund the orphaned provider object deterministically.
|
|
5
|
+
*/
|
|
6
|
+
export class ProxyStripeOpenCheckoutError extends Error {
|
|
7
|
+
name = "ProxyStripeOpenCheckoutError";
|
|
8
|
+
code;
|
|
9
|
+
cause;
|
|
10
|
+
providerCheckoutSessionId;
|
|
11
|
+
reconciled = false;
|
|
12
|
+
constructor(message, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.cause = details.cause;
|
|
15
|
+
this.code = details.code;
|
|
16
|
+
this.providerCheckoutSessionId = details.providerCheckoutSessionId;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class ProxyStripeCartSyncError extends Error {
|
|
20
|
+
name = "ProxyStripeCartSyncError";
|
|
21
|
+
code;
|
|
22
|
+
/** True only when the SDK verified Proxy and Stripe are in a consistent state. */
|
|
23
|
+
reconciled;
|
|
24
|
+
cause;
|
|
25
|
+
rollbackCause;
|
|
26
|
+
constructor(message, details) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.code = details.code;
|
|
29
|
+
this.reconciled = details.reconciled;
|
|
30
|
+
this.cause = details.cause;
|
|
31
|
+
this.rollbackCause = details.rollbackCause;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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";
|
|
4
|
+
export { normalizePsp } from "./psp.js";
|
|
5
|
+
export { type SyncCheckoutCartOptions, type SyncCheckoutCartResult, type SyncedCart, syncCheckoutCart, } from "./sync-checkout-cart.js";
|
|
6
|
+
export type { StripeCheckoutSessionCreateParams, StripeCheckoutSessionLike, StripeCheckoutSessionsApiLike, StripeCheckoutSessionUpdateParams, StripeClientLike, StripeMetadata, StripePaymentIntentLike, StripeRequestOptionsLike, StripeSubscriptionLike, } from "./types.js";
|
|
7
|
+
export { proxyCheckoutStripeServerSdkName, proxyCheckoutStripeServerSdkVersion, } from "./version.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ProxyStripeCartSyncError, ProxyStripeOpenCheckoutError, } from "./errors.js";
|
|
2
|
+
export { assertCheckoutSessionBelongsToProxy, assertPaymentIntentBelongsToProxy, assertSubscriptionBelongsToProxy, injectRequiredCheckoutSessionMetadata, PROXY_SESSION_METADATA_KEY, requiredMetadata, } from "./metadata.js";
|
|
3
|
+
export { openCheckout, } from "./open-checkout.js";
|
|
4
|
+
export { normalizePsp } from "./psp.js";
|
|
5
|
+
export { syncCheckoutCart, } from "./sync-checkout-cart.js";
|
|
6
|
+
export { proxyCheckoutStripeServerSdkName, proxyCheckoutStripeServerSdkVersion, } from "./version.js";
|