@proxy-checkout/server-js 0.0.4-pr-76.21.1 → 0.0.4-pr-76.23.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/dist/cjs/cart.js +26 -0
- package/dist/cjs/client.js +41 -0
- package/dist/cjs/consistency.js +36 -0
- package/dist/cjs/errors.js +59 -0
- package/dist/cjs/events.js +151 -0
- package/dist/cjs/http-client.js +84 -0
- package/dist/cjs/index.js +55 -0
- package/dist/cjs/lifecycle.js +109 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/response-validators.js +60 -0
- package/dist/cjs/sessions.js +303 -0
- package/dist/cjs/subscriptions.js +59 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/version.js +6 -0
- package/dist/cjs/webhook-events.js +69 -0
- package/dist/cjs/webhook-handler.js +177 -0
- package/dist/cjs/webhooks.js +182 -0
- package/dist/{version.d.ts → esm/version.d.ts} +2 -2
- package/dist/{version.js → esm/version.js} +1 -1
- package/package.json +8 -6
- /package/dist/{cart.d.ts → esm/cart.d.ts} +0 -0
- /package/dist/{cart.js → esm/cart.js} +0 -0
- /package/dist/{client.d.ts → esm/client.d.ts} +0 -0
- /package/dist/{client.js → esm/client.js} +0 -0
- /package/dist/{consistency.d.ts → esm/consistency.d.ts} +0 -0
- /package/dist/{consistency.js → esm/consistency.js} +0 -0
- /package/dist/{errors.d.ts → esm/errors.d.ts} +0 -0
- /package/dist/{errors.js → esm/errors.js} +0 -0
- /package/dist/{events.d.ts → esm/events.d.ts} +0 -0
- /package/dist/{events.js → esm/events.js} +0 -0
- /package/dist/{http-client.d.ts → esm/http-client.d.ts} +0 -0
- /package/dist/{http-client.js → esm/http-client.js} +0 -0
- /package/dist/{index.d.ts → esm/index.d.ts} +0 -0
- /package/dist/{index.js → esm/index.js} +0 -0
- /package/dist/{lifecycle.d.ts → esm/lifecycle.d.ts} +0 -0
- /package/dist/{lifecycle.js → esm/lifecycle.js} +0 -0
- /package/dist/{response-validators.d.ts → esm/response-validators.d.ts} +0 -0
- /package/dist/{response-validators.js → esm/response-validators.js} +0 -0
- /package/dist/{sessions.d.ts → esm/sessions.d.ts} +0 -0
- /package/dist/{sessions.js → esm/sessions.js} +0 -0
- /package/dist/{subscriptions.d.ts → esm/subscriptions.d.ts} +0 -0
- /package/dist/{subscriptions.js → esm/subscriptions.js} +0 -0
- /package/dist/{types.d.ts → esm/types.d.ts} +0 -0
- /package/dist/{types.js → esm/types.js} +0 -0
- /package/dist/{webhook-events.d.ts → esm/webhook-events.d.ts} +0 -0
- /package/dist/{webhook-events.js → esm/webhook-events.js} +0 -0
- /package/dist/{webhook-handler.d.ts → esm/webhook-handler.d.ts} +0 -0
- /package/dist/{webhook-handler.js → esm/webhook-handler.js} +0 -0
- /package/dist/{webhooks.d.ts → esm/webhooks.d.ts} +0 -0
- /package/dist/{webhooks.js → esm/webhooks.js} +0 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProxySessionCartResource = exports.ProxySessionsResource = exports.proxyCheckoutServerEndpoints = void 0;
|
|
4
|
+
const cart_js_1 = require("./cart.js");
|
|
5
|
+
const consistency_js_1 = require("./consistency.js");
|
|
6
|
+
const response_validators_js_1 = require("./response-validators.js");
|
|
7
|
+
exports.proxyCheckoutServerEndpoints = [
|
|
8
|
+
{
|
|
9
|
+
method: "POST",
|
|
10
|
+
operation: "sessions.create",
|
|
11
|
+
path: "/proxy_sessions",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
method: "POST",
|
|
15
|
+
operation: "sessions.createHandoff",
|
|
16
|
+
path: "/proxy_sessions/handoff",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
method: "GET",
|
|
20
|
+
operation: "sessions.retrieve",
|
|
21
|
+
path: "/proxy_sessions/:id/merchant",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
method: "PUT",
|
|
25
|
+
operation: "sessions.cart.set",
|
|
26
|
+
path: "/proxy_sessions/:id/cart",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
method: "POST",
|
|
30
|
+
operation: "sessions.payerHandoff",
|
|
31
|
+
path: "/proxy_sessions/:id/payer_handoff",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
method: "POST",
|
|
35
|
+
operation: "sessions.payerOpened",
|
|
36
|
+
path: "/proxy_sessions/:id/payer_opened",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
method: "POST",
|
|
40
|
+
operation: "sessions.providerBindings.create",
|
|
41
|
+
path: "/proxy_sessions/:id/provider_bindings",
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
class ProxySessionsResource {
|
|
45
|
+
httpClient;
|
|
46
|
+
options;
|
|
47
|
+
cart;
|
|
48
|
+
constructor(httpClient, options = {}) {
|
|
49
|
+
this.httpClient = httpClient;
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.cart = new ProxySessionCartResource(httpClient);
|
|
52
|
+
}
|
|
53
|
+
async create(input) {
|
|
54
|
+
const response = await this.httpClient.request("POST", "/proxy_sessions", toCreateProxySessionBody(input), {
|
|
55
|
+
idempotencyKey: input.idempotencyKey,
|
|
56
|
+
requestId: input.requestId,
|
|
57
|
+
});
|
|
58
|
+
return toProxySession(response);
|
|
59
|
+
}
|
|
60
|
+
async createHandoff(input) {
|
|
61
|
+
const publishableKey = input.publishableKey ?? this.options.publishableKey;
|
|
62
|
+
if (!publishableKey) {
|
|
63
|
+
throw new Error("Proxy Checkout sessions.createHandoff requires a publishableKey option or input field.");
|
|
64
|
+
}
|
|
65
|
+
const payHost = input.payHost ?? this.options.payHost;
|
|
66
|
+
if (!payHost && this.httpClient.apiHost !== "https://api.proxycheckout.com") {
|
|
67
|
+
throw new Error("Proxy Checkout sessions.createHandoff requires a payHost option or input field when apiHost is overridden.");
|
|
68
|
+
}
|
|
69
|
+
const response = await this.httpClient.request("POST", "/proxy_sessions/handoff", {
|
|
70
|
+
...toCreateProxySessionBody(input),
|
|
71
|
+
publishable_key: publishableKey,
|
|
72
|
+
}, {
|
|
73
|
+
idempotencyKey: input.idempotencyKey,
|
|
74
|
+
requestId: input.requestId,
|
|
75
|
+
});
|
|
76
|
+
const session = toProxySessionHandoff(response);
|
|
77
|
+
return {
|
|
78
|
+
...session,
|
|
79
|
+
handoffUrl: buildHandoffUrl({
|
|
80
|
+
payHost,
|
|
81
|
+
proxySessionId: session.id,
|
|
82
|
+
publishableKey,
|
|
83
|
+
}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async retrieve(proxySessionId, options = {}) {
|
|
87
|
+
const response = await this.httpClient.request("GET", `/proxy_sessions/${encodeURIComponent(proxySessionId)}/merchant`, undefined, options);
|
|
88
|
+
return toMerchantProxySession(response);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Retrieve a session and validate its cart snapshot against a merchant schema,
|
|
92
|
+
* returning the session with a typed `cart`. Throws a structured error if the
|
|
93
|
+
* cart fails validation or its buyer reference disagrees with the session.
|
|
94
|
+
*/
|
|
95
|
+
async retrieveTyped(proxySessionId, cartSchema, options = {}) {
|
|
96
|
+
const session = await this.retrieve(proxySessionId, { requestId: options.requestId });
|
|
97
|
+
return { ...session, cart: this.parseCart(session, cartSchema, options) };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Validate the cart snapshot of an already-retrieved session against a merchant
|
|
101
|
+
* schema. When `getBuyerReference` is supplied, the cart buyer reference is
|
|
102
|
+
* asserted to match the session buyer reference.
|
|
103
|
+
*/
|
|
104
|
+
parseCart(session, cartSchema, options = {}) {
|
|
105
|
+
const cart = (0, cart_js_1.parseProxyCart)(session.cartSnapshot, cartSchema);
|
|
106
|
+
if (options.getBuyerReference !== undefined) {
|
|
107
|
+
(0, consistency_js_1.assertCartBuyerReference)(options.getBuyerReference(cart), session);
|
|
108
|
+
}
|
|
109
|
+
return cart;
|
|
110
|
+
}
|
|
111
|
+
async payerHandoff(proxySessionId, options = {}) {
|
|
112
|
+
const response = await this.httpClient.request("POST", `/proxy_sessions/${encodeURIComponent(proxySessionId)}/payer_handoff`, {}, options);
|
|
113
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.payerHandoff");
|
|
114
|
+
return {
|
|
115
|
+
status: requirePayerHandoffStatus(body.status),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async payerOpened(proxySessionId, options = {}) {
|
|
119
|
+
const response = await this.httpClient.request("POST", `/proxy_sessions/${encodeURIComponent(proxySessionId)}/payer_opened`, {}, options);
|
|
120
|
+
return toPayerOpenedResult(response);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Bind a provider (PSP) checkout object to a Proxy session so later cart sync
|
|
124
|
+
* can verify ownership. Idempotent for the same provider object; conflicts when
|
|
125
|
+
* the session is already bound to a different one.
|
|
126
|
+
*/
|
|
127
|
+
async recordProviderBinding(proxySessionId, input) {
|
|
128
|
+
const response = await this.httpClient.request("POST", `/proxy_sessions/${encodeURIComponent(proxySessionId)}/provider_bindings`, {
|
|
129
|
+
provider_checkout_session_id: input.providerCheckoutSessionId,
|
|
130
|
+
psp: input.psp,
|
|
131
|
+
}, { requestId: input.requestId });
|
|
132
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.providerBindings.create");
|
|
133
|
+
return {
|
|
134
|
+
providerCheckoutSessionId: (0, response_validators_js_1.requireString)(body.provider_checkout_session_id, "sessions.providerBindings.create.providerCheckoutSessionId"),
|
|
135
|
+
proxySessionId: (0, response_validators_js_1.requireString)(body.proxy_session_id, "sessions.providerBindings.create.proxySessionId"),
|
|
136
|
+
psp: (0, response_validators_js_1.requireString)(body.psp, "sessions.providerBindings.create.psp"),
|
|
137
|
+
updatedAt: (0, response_validators_js_1.requireString)(body.updated_at, "sessions.providerBindings.create.updatedAt"),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.ProxySessionsResource = ProxySessionsResource;
|
|
142
|
+
class ProxySessionCartResource {
|
|
143
|
+
httpClient;
|
|
144
|
+
constructor(httpClient) {
|
|
145
|
+
this.httpClient = httpClient;
|
|
146
|
+
}
|
|
147
|
+
async set(proxySessionId, input) {
|
|
148
|
+
const response = await this.httpClient.request("PUT", `/proxy_sessions/${encodeURIComponent(proxySessionId)}/cart`, toProxySessionCartBody(input), {
|
|
149
|
+
requestId: input.requestId,
|
|
150
|
+
});
|
|
151
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.cart.set");
|
|
152
|
+
return {
|
|
153
|
+
amountMinor: (0, response_validators_js_1.requireInteger)(body.amount_minor, "sessions.cart.set.amountMinor"),
|
|
154
|
+
cartSnapshot: (0, response_validators_js_1.requireJsonObject)(body.cart_snapshot, "sessions.cart.set"),
|
|
155
|
+
cartVersion: (0, response_validators_js_1.requireInteger)(body.cart_version, "sessions.cart.set.cartVersion"),
|
|
156
|
+
currency: (0, response_validators_js_1.requireString)(body.currency, "sessions.cart.set.currency"),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.ProxySessionCartResource = ProxySessionCartResource;
|
|
161
|
+
function toCreateProxySessionBody(input) {
|
|
162
|
+
const body = {
|
|
163
|
+
amount_minor: input.amountMinor,
|
|
164
|
+
buyer_reference: input.buyerReference,
|
|
165
|
+
cart_snapshot: input.cartSnapshot,
|
|
166
|
+
currency: input.currency,
|
|
167
|
+
};
|
|
168
|
+
if (input.expiresAt !== undefined) {
|
|
169
|
+
body.expires_at =
|
|
170
|
+
input.expiresAt instanceof Date ? input.expiresAt.toISOString() : input.expiresAt;
|
|
171
|
+
}
|
|
172
|
+
if (input.integrationMode !== undefined) {
|
|
173
|
+
body.integration_mode = input.integrationMode;
|
|
174
|
+
}
|
|
175
|
+
if (input.metadata !== undefined) {
|
|
176
|
+
body.metadata = input.metadata;
|
|
177
|
+
}
|
|
178
|
+
if (input.payerContact !== undefined) {
|
|
179
|
+
body.payer_contact = removeUndefinedValues({
|
|
180
|
+
email: input.payerContact.email,
|
|
181
|
+
phone: input.payerContact.phone,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return body;
|
|
185
|
+
}
|
|
186
|
+
function toProxySessionCartBody(input) {
|
|
187
|
+
return {
|
|
188
|
+
amount_minor: input.amountMinor,
|
|
189
|
+
cart_snapshot: input.cartSnapshot,
|
|
190
|
+
currency: input.currency,
|
|
191
|
+
expected_cart_version: input.expectedCartVersion,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function removeUndefinedValues(input) {
|
|
195
|
+
return Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== undefined));
|
|
196
|
+
}
|
|
197
|
+
function toProxySession(response) {
|
|
198
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.create");
|
|
199
|
+
const status = (0, response_validators_js_1.requireString)(body.status, "sessions.create.status");
|
|
200
|
+
const integrationMode = (0, response_validators_js_1.requireString)(body.integration_mode, "sessions.create.integrationMode");
|
|
201
|
+
return {
|
|
202
|
+
expiresAt: (0, response_validators_js_1.requireString)(body.expires_at, "sessions.create.expiresAt"),
|
|
203
|
+
id: (0, response_validators_js_1.requireString)(body.id, "sessions.create.id"),
|
|
204
|
+
integrationMode,
|
|
205
|
+
status,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function toProxySessionHandoff(response) {
|
|
209
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.createHandoff");
|
|
210
|
+
const status = requirePayerHandoffStatus(body.status);
|
|
211
|
+
const integrationMode = (0, response_validators_js_1.requireString)(body.integration_mode, "sessions.createHandoff.integrationMode");
|
|
212
|
+
return {
|
|
213
|
+
expiresAt: (0, response_validators_js_1.requireString)(body.expires_at, "sessions.createHandoff.expiresAt"),
|
|
214
|
+
handoffUrl: "",
|
|
215
|
+
id: (0, response_validators_js_1.requireString)(body.id, "sessions.createHandoff.id"),
|
|
216
|
+
integrationMode,
|
|
217
|
+
status,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function toMerchantProxySession(response) {
|
|
221
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.retrieve");
|
|
222
|
+
return {
|
|
223
|
+
amountMinor: (0, response_validators_js_1.requireInteger)(body.amount_minor, "sessions.retrieve.amountMinor"),
|
|
224
|
+
buyerReference: (0, response_validators_js_1.requireString)(body.buyer_reference, "sessions.retrieve.buyerReference"),
|
|
225
|
+
cartSnapshot: (0, response_validators_js_1.requireJsonObject)(body.cart_snapshot, "sessions.retrieve.cartSnapshot"),
|
|
226
|
+
cartVersion: (0, response_validators_js_1.requireInteger)(body.cart_version, "sessions.retrieve.cartVersion"),
|
|
227
|
+
createdAt: (0, response_validators_js_1.requireString)(body.created_at, "sessions.retrieve.createdAt"),
|
|
228
|
+
currency: (0, response_validators_js_1.requireString)(body.currency, "sessions.retrieve.currency"),
|
|
229
|
+
expiresAt: (0, response_validators_js_1.requireString)(body.expires_at, "sessions.retrieve.expiresAt"),
|
|
230
|
+
id: (0, response_validators_js_1.requireString)(body.id, "sessions.retrieve.id"),
|
|
231
|
+
idempotencyKey: (0, response_validators_js_1.requireNullableString)(body.idempotency_key, "sessions.retrieve.idempotencyKey"),
|
|
232
|
+
integrationMode: (0, response_validators_js_1.requireString)(body.integration_mode, "sessions.retrieve.integrationMode"),
|
|
233
|
+
metadata: (0, response_validators_js_1.requireJsonObject)(body.metadata, "sessions.retrieve.metadata"),
|
|
234
|
+
payerContact: requireNullablePayerContact(body.payer_contact),
|
|
235
|
+
providerCheckoutSessionId: (0, response_validators_js_1.requireNullableString)(body.provider_checkout_session_id, "sessions.retrieve.providerCheckoutSessionId"),
|
|
236
|
+
providerCheckoutSessionPsp: (0, response_validators_js_1.requireNullableString)(body.provider_checkout_session_psp, "sessions.retrieve.providerCheckoutSessionPsp"),
|
|
237
|
+
status: (0, response_validators_js_1.requireString)(body.status, "sessions.retrieve.status"),
|
|
238
|
+
updatedAt: (0, response_validators_js_1.requireString)(body.updated_at, "sessions.retrieve.updatedAt"),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function toPayerOpenedResult(response) {
|
|
242
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "sessions.payerOpened");
|
|
243
|
+
const status = (0, response_validators_js_1.requireString)(body.status, "sessions.payerOpened.status");
|
|
244
|
+
if (status !== "payer_opened") {
|
|
245
|
+
throw new Error("Proxy API response field sessions.payerOpened.status is unsupported.");
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
amountMinor: (0, response_validators_js_1.requireInteger)(body.amount_minor, "sessions.payerOpened.amountMinor"),
|
|
249
|
+
buyerReference: (0, response_validators_js_1.requireString)(body.buyer_reference, "sessions.payerOpened.buyerReference"),
|
|
250
|
+
cartSnapshot: (0, response_validators_js_1.requireJsonObject)(body.cart_snapshot, "sessions.payerOpened.cartSnapshot"),
|
|
251
|
+
cartVersion: (0, response_validators_js_1.requireInteger)(body.cart_version, "sessions.payerOpened.cartVersion"),
|
|
252
|
+
createdAt: (0, response_validators_js_1.requireString)(body.created_at, "sessions.payerOpened.createdAt"),
|
|
253
|
+
currency: (0, response_validators_js_1.requireString)(body.currency, "sessions.payerOpened.currency"),
|
|
254
|
+
expiresAt: (0, response_validators_js_1.requireString)(body.expires_at, "sessions.payerOpened.expiresAt"),
|
|
255
|
+
id: (0, response_validators_js_1.requireString)(body.id, "sessions.payerOpened.id"),
|
|
256
|
+
idempotencyKey: (0, response_validators_js_1.requireNullableString)(body.idempotency_key, "sessions.payerOpened.idempotencyKey"),
|
|
257
|
+
integrationMode: (0, response_validators_js_1.requireString)(body.integration_mode, "sessions.payerOpened.integrationMode"),
|
|
258
|
+
metadata: (0, response_validators_js_1.requireJsonObject)(body.metadata, "sessions.payerOpened.metadata"),
|
|
259
|
+
payerContact: requireNullablePayerContact(body.payer_contact, "sessions.payerOpened"),
|
|
260
|
+
providerCheckoutSessionId: (0, response_validators_js_1.requireNullableString)(body.provider_checkout_session_id, "sessions.payerOpened.providerCheckoutSessionId"),
|
|
261
|
+
providerCheckoutSessionPsp: (0, response_validators_js_1.requireNullableString)(body.provider_checkout_session_psp, "sessions.payerOpened.providerCheckoutSessionPsp"),
|
|
262
|
+
status,
|
|
263
|
+
updatedAt: (0, response_validators_js_1.requireString)(body.updated_at, "sessions.payerOpened.updatedAt"),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function requireNullablePayerContact(value, operation = "sessions.retrieve") {
|
|
267
|
+
if (value === null) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const contact = (0, response_validators_js_1.requireJsonObject)(value, `${operation}.payerContact`);
|
|
271
|
+
return {
|
|
272
|
+
email: (0, response_validators_js_1.requireNullableString)(contact.email, `${operation}.payerContact.email`),
|
|
273
|
+
phone: (0, response_validators_js_1.requireNullableString)(contact.phone, `${operation}.payerContact.phone`),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function requirePayerHandoffStatus(value) {
|
|
277
|
+
const status = (0, response_validators_js_1.requireString)(value, "sessions.payerHandoff.status");
|
|
278
|
+
if (status === "payer_handoff_pending" || status === "payer_opened") {
|
|
279
|
+
return status;
|
|
280
|
+
}
|
|
281
|
+
throw new Error("Proxy API response field sessions.payerHandoff.status is unsupported.");
|
|
282
|
+
}
|
|
283
|
+
function buildHandoffUrl({ payHost, proxySessionId, publishableKey, }) {
|
|
284
|
+
const url = new URL(`${normalizePayHost(payHost)}/s/${encodeURIComponent(proxySessionId)}`);
|
|
285
|
+
url.searchParams.set("pk", publishableKey);
|
|
286
|
+
return url.toString();
|
|
287
|
+
}
|
|
288
|
+
function normalizePayHost(payHost) {
|
|
289
|
+
if (payHost === undefined) {
|
|
290
|
+
return "https://pay.proxycheckout.com";
|
|
291
|
+
}
|
|
292
|
+
let url;
|
|
293
|
+
try {
|
|
294
|
+
url = new URL(payHost);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
throw new Error(`Invalid payHost URL: ${payHost}. Make sure it is an absolute HTTP or HTTPS URL.`);
|
|
298
|
+
}
|
|
299
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
300
|
+
throw new Error(`Invalid payHost URL: ${payHost}. Make sure it is an absolute HTTP or HTTPS URL.`);
|
|
301
|
+
}
|
|
302
|
+
return `${url.origin}${url.pathname}`.replace(/\/$/, "");
|
|
303
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProxySubscriptionsResource = exports.proxyCheckoutSubscriptionEndpoints = void 0;
|
|
4
|
+
const consistency_js_1 = require("./consistency.js");
|
|
5
|
+
const response_validators_js_1 = require("./response-validators.js");
|
|
6
|
+
exports.proxyCheckoutSubscriptionEndpoints = [
|
|
7
|
+
{
|
|
8
|
+
method: "GET",
|
|
9
|
+
operation: "subscriptions.retrieve",
|
|
10
|
+
path: "/subscriptions/:id",
|
|
11
|
+
},
|
|
12
|
+
];
|
|
13
|
+
class ProxySubscriptionsResource {
|
|
14
|
+
httpClient;
|
|
15
|
+
sessions;
|
|
16
|
+
constructor(httpClient, sessions) {
|
|
17
|
+
this.httpClient = httpClient;
|
|
18
|
+
this.sessions = sessions;
|
|
19
|
+
}
|
|
20
|
+
async retrieve(subscriptionId, options = {}) {
|
|
21
|
+
const response = await this.httpClient.request("GET", `/subscriptions/${encodeURIComponent(subscriptionId)}`, undefined, options);
|
|
22
|
+
return toMerchantProxySubscription(response);
|
|
23
|
+
}
|
|
24
|
+
async retrieveWithOriginalSession(subscriptionId, options = {}) {
|
|
25
|
+
const requestOptions = { requestId: options.requestId };
|
|
26
|
+
const subscription = await this.retrieve(subscriptionId, requestOptions);
|
|
27
|
+
const session = await this.sessions.retrieve(subscription.originalProxySessionId, requestOptions);
|
|
28
|
+
(0, consistency_js_1.assertSubscriptionMatchesSession)(subscription, session);
|
|
29
|
+
if (options.cartSchema === undefined) {
|
|
30
|
+
return { session, subscription };
|
|
31
|
+
}
|
|
32
|
+
const cart = this.sessions.parseCart(session, options.cartSchema, options);
|
|
33
|
+
return { cart, session: { ...session, cart }, subscription };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.ProxySubscriptionsResource = ProxySubscriptionsResource;
|
|
37
|
+
function toMerchantProxySubscription(response) {
|
|
38
|
+
const body = (0, response_validators_js_1.requireJsonObject)(response, "subscriptions.retrieve");
|
|
39
|
+
return {
|
|
40
|
+
buyerReference: (0, response_validators_js_1.requireString)(body.buyer_reference, "subscriptions.retrieve.buyerReference"),
|
|
41
|
+
cancelAtPeriodEnd: (0, response_validators_js_1.requireBoolean)(body.cancel_at_period_end, "subscriptions.retrieve.cancelAtPeriodEnd"),
|
|
42
|
+
createdAt: (0, response_validators_js_1.requireString)(body.created_at, "subscriptions.retrieve.createdAt"),
|
|
43
|
+
currentPeriodEnd: (0, response_validators_js_1.requireNullableString)(body.current_period_end, "subscriptions.retrieve.currentPeriodEnd"),
|
|
44
|
+
currentPeriodStart: (0, response_validators_js_1.requireNullableString)(body.current_period_start, "subscriptions.retrieve.currentPeriodStart"),
|
|
45
|
+
endedAt: (0, response_validators_js_1.requireNullableString)(body.ended_at, "subscriptions.retrieve.endedAt"),
|
|
46
|
+
id: (0, response_validators_js_1.requireString)(body.id, "subscriptions.retrieve.id"),
|
|
47
|
+
latestInvoiceAmountMinor: (0, response_validators_js_1.requireNullableInteger)(body.latest_invoice_amount_minor, "subscriptions.retrieve.latestInvoiceAmountMinor"),
|
|
48
|
+
latestInvoiceCurrency: (0, response_validators_js_1.requireNullableString)(body.latest_invoice_currency, "subscriptions.retrieve.latestInvoiceCurrency"),
|
|
49
|
+
latestInvoiceId: (0, response_validators_js_1.requireNullableString)(body.latest_invoice_id, "subscriptions.retrieve.latestInvoiceId"),
|
|
50
|
+
latestPaymentStatus: (0, response_validators_js_1.requireNullableString)(body.latest_payment_status, "subscriptions.retrieve.latestPaymentStatus"),
|
|
51
|
+
originalProxySessionId: (0, response_validators_js_1.requireString)(body.original_proxy_session_id, "subscriptions.retrieve.originalProxySessionId"),
|
|
52
|
+
providerCheckoutSessionId: (0, response_validators_js_1.requireNullableString)(body.provider_checkout_session_id, "subscriptions.retrieve.providerCheckoutSessionId"),
|
|
53
|
+
providerSubscriptionId: (0, response_validators_js_1.requireString)(body.provider_subscription_id, "subscriptions.retrieve.providerSubscriptionId"),
|
|
54
|
+
psp: (0, response_validators_js_1.requireString)(body.psp, "subscriptions.retrieve.psp"),
|
|
55
|
+
status: (0, response_validators_js_1.requireString)(body.status, "subscriptions.retrieve.status"),
|
|
56
|
+
trialEnd: (0, response_validators_js_1.requireNullableString)(body.trial_end, "subscriptions.retrieve.trialEnd"),
|
|
57
|
+
updatedAt: (0, response_validators_js_1.requireString)(body.updated_at, "subscriptions.retrieve.updatedAt"),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.proxyCheckoutServerSdkUserAgent = exports.proxyCheckoutServerSdkVersion = exports.proxyCheckoutServerSdkName = void 0;
|
|
4
|
+
exports.proxyCheckoutServerSdkName = "@proxy-checkout/server-js";
|
|
5
|
+
exports.proxyCheckoutServerSdkVersion = "0.0.4-pr-76.23.1";
|
|
6
|
+
exports.proxyCheckoutServerSdkUserAgent = `${exports.proxyCheckoutServerSdkName}/${exports.proxyCheckoutServerSdkVersion}`;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical outbound Proxy webhook event types and predicates.
|
|
4
|
+
*
|
|
5
|
+
* These mirror the event types the Proxy API can deliver to merchant webhook
|
|
6
|
+
* endpoints. Use the predicates instead of comparing raw strings so customer
|
|
7
|
+
* integration code does not encode the event taxonomy by hand.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES = void 0;
|
|
11
|
+
exports.isProxySessionEvent = isProxySessionEvent;
|
|
12
|
+
exports.isProxySubscriptionEvent = isProxySubscriptionEvent;
|
|
13
|
+
exports.isProxyPaymentAttemptEvent = isProxyPaymentAttemptEvent;
|
|
14
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES = {
|
|
15
|
+
paymentAttemptCancelled: "payment_attempt.cancelled",
|
|
16
|
+
paymentAttemptDisputed: "payment_attempt.disputed",
|
|
17
|
+
paymentAttemptDisputeLost: "payment_attempt.dispute_lost",
|
|
18
|
+
paymentAttemptDisputeWon: "payment_attempt.dispute_won",
|
|
19
|
+
paymentAttemptFailed: "payment_attempt.failed",
|
|
20
|
+
paymentAttemptPartiallyRefunded: "payment_attempt.partially_refunded",
|
|
21
|
+
paymentAttemptRefunded: "payment_attempt.refunded",
|
|
22
|
+
paymentAttemptSucceeded: "payment_attempt.succeeded",
|
|
23
|
+
sessionCancelled: "proxy_session.cancelled",
|
|
24
|
+
sessionExpired: "proxy_session.expired",
|
|
25
|
+
sessionFailed: "proxy_session.failed",
|
|
26
|
+
sessionMerchantActionRequired: "proxy_session.merchant_action_required",
|
|
27
|
+
sessionPaid: "proxy_session.paid",
|
|
28
|
+
sessionProvisionable: "proxy_session.provisionable",
|
|
29
|
+
subscriptionCancelled: "subscription.cancelled",
|
|
30
|
+
subscriptionCancelScheduled: "subscription.cancel_scheduled",
|
|
31
|
+
subscriptionPaymentFailed: "subscription.payment_failed",
|
|
32
|
+
subscriptionRenewed: "subscription.renewed",
|
|
33
|
+
};
|
|
34
|
+
const SESSION_EVENT_TYPES = new Set([
|
|
35
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.sessionCancelled,
|
|
36
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.sessionExpired,
|
|
37
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.sessionFailed,
|
|
38
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.sessionMerchantActionRequired,
|
|
39
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.sessionPaid,
|
|
40
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.sessionProvisionable,
|
|
41
|
+
]);
|
|
42
|
+
const SUBSCRIPTION_EVENT_TYPES = new Set([
|
|
43
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.subscriptionCancelScheduled,
|
|
44
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.subscriptionCancelled,
|
|
45
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.subscriptionPaymentFailed,
|
|
46
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.subscriptionRenewed,
|
|
47
|
+
]);
|
|
48
|
+
const PAYMENT_ATTEMPT_EVENT_TYPES = new Set([
|
|
49
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptCancelled,
|
|
50
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptDisputeLost,
|
|
51
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptDisputeWon,
|
|
52
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptDisputed,
|
|
53
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptFailed,
|
|
54
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptPartiallyRefunded,
|
|
55
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptRefunded,
|
|
56
|
+
exports.PROXY_WEBHOOK_EVENT_TYPES.paymentAttemptSucceeded,
|
|
57
|
+
]);
|
|
58
|
+
/** True for `proxy_session.*` events. */
|
|
59
|
+
function isProxySessionEvent(eventType) {
|
|
60
|
+
return SESSION_EVENT_TYPES.has(eventType);
|
|
61
|
+
}
|
|
62
|
+
/** True for `subscription.*` events. */
|
|
63
|
+
function isProxySubscriptionEvent(eventType) {
|
|
64
|
+
return SUBSCRIPTION_EVENT_TYPES.has(eventType);
|
|
65
|
+
}
|
|
66
|
+
/** True for `payment_attempt.*` events. */
|
|
67
|
+
function isProxyPaymentAttemptEvent(eventType) {
|
|
68
|
+
return PAYMENT_ATTEMPT_EVENT_TYPES.has(eventType);
|
|
69
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server SDK webhook delivery handler.
|
|
4
|
+
*
|
|
5
|
+
* `handle` turns a verified Proxy webhook into a current-state lifecycle
|
|
6
|
+
* decision and runs the merchant's entitlement callback, with optional
|
|
7
|
+
* idempotency via a pluggable {@link ProxyWebhookStore}. It owns signature
|
|
8
|
+
* verification, duplicate/in-flight handling, retryable responses, and
|
|
9
|
+
* current-state retrieval so customer webhook routes stay tiny.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ProxyWebhooksResource = exports.PROXY_WEBHOOK_RETRY_AFTER_SECONDS = void 0;
|
|
13
|
+
const webhooks_js_1 = require("./webhooks.js");
|
|
14
|
+
/** Seconds advertised in `Retry-After` when an event is already in flight. */
|
|
15
|
+
exports.PROXY_WEBHOOK_RETRY_AFTER_SECONDS = 30;
|
|
16
|
+
class ProxyWebhooksResource {
|
|
17
|
+
events;
|
|
18
|
+
constructor(events) {
|
|
19
|
+
this.events = events;
|
|
20
|
+
}
|
|
21
|
+
/** Low-level: verify + construct the event without resolving lifecycle. */
|
|
22
|
+
constructEvent(input) {
|
|
23
|
+
return (0, webhooks_js_1.constructProxyWebhookEvent)(input);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Framework-agnostic processing. Verifies the signature, applies the store,
|
|
27
|
+
* resolves current state, runs `onResolved`, and returns response metadata.
|
|
28
|
+
* Throws {@link ProxyWebhookSignatureVerificationError} on a bad signature so
|
|
29
|
+
* non-Response frameworks (Express) can map it to 400.
|
|
30
|
+
*/
|
|
31
|
+
async process(input, options) {
|
|
32
|
+
const { body, signature } = await readWebhookPayload(input);
|
|
33
|
+
const event = (0, webhooks_js_1.constructProxyWebhookEvent)({
|
|
34
|
+
body,
|
|
35
|
+
header: signature,
|
|
36
|
+
secret: options.secret,
|
|
37
|
+
toleranceSeconds: options.toleranceSeconds,
|
|
38
|
+
});
|
|
39
|
+
const { store } = options;
|
|
40
|
+
if (store !== undefined) {
|
|
41
|
+
const claim = await store.claim(event);
|
|
42
|
+
if (claim === "duplicate") {
|
|
43
|
+
return {
|
|
44
|
+
event,
|
|
45
|
+
outcome: "duplicate",
|
|
46
|
+
resolved: null,
|
|
47
|
+
retryable: false,
|
|
48
|
+
statusCode: 200,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (claim === "processing") {
|
|
52
|
+
return {
|
|
53
|
+
event,
|
|
54
|
+
outcome: "processing",
|
|
55
|
+
resolved: null,
|
|
56
|
+
retryable: true,
|
|
57
|
+
statusCode: 409,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let resolved;
|
|
62
|
+
try {
|
|
63
|
+
resolved = await this.events.resolveCurrentState(event, { requestId: options.requestId });
|
|
64
|
+
await options.onResolved(resolved);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (store !== undefined) {
|
|
68
|
+
try {
|
|
69
|
+
await store.markFailed(event.id, error);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Never let a store failure shadow the original fulfillment error.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
if (store !== undefined) {
|
|
78
|
+
try {
|
|
79
|
+
await store.markProcessed(event.id, toProcessRecord(resolved));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// The fulfillment ran, but committing idempotency failed. Best-effort
|
|
83
|
+
// release the claim so the event is reclaimable on a later delivery
|
|
84
|
+
// instead of being stuck in the in-flight branch forever.
|
|
85
|
+
try {
|
|
86
|
+
await store.markFailed(event.id, error);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Never let a store failure shadow the original markProcessed error.
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
event,
|
|
96
|
+
outcome: resolved.kind === "ignored" ? "ignored" : "processed",
|
|
97
|
+
resolved,
|
|
98
|
+
retryable: false,
|
|
99
|
+
statusCode: 200,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Turnkey handler for web `Request` frameworks. Returns a `Response` with the
|
|
104
|
+
* right status (200 success/duplicate, 409 + `Retry-After` in flight, 400 on
|
|
105
|
+
* bad signature). Re-throws merchant `onResolved` errors so the platform can
|
|
106
|
+
* surface a 500 and Proxy retries.
|
|
107
|
+
*/
|
|
108
|
+
async handle(input, options) {
|
|
109
|
+
let result;
|
|
110
|
+
try {
|
|
111
|
+
result = await this.process(input, options);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof webhooks_js_1.ProxyWebhookSignatureVerificationError) {
|
|
115
|
+
return jsonResponse(400, {
|
|
116
|
+
error: { code: "invalid_signature", message: error.message },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
return toWebhookResponse(result);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.ProxyWebhooksResource = ProxyWebhooksResource;
|
|
125
|
+
function toWebhookResponse(result) {
|
|
126
|
+
const headers = { "content-type": "application/json" };
|
|
127
|
+
if (result.retryable) {
|
|
128
|
+
headers["retry-after"] = String(exports.PROXY_WEBHOOK_RETRY_AFTER_SECONDS);
|
|
129
|
+
}
|
|
130
|
+
return new Response(JSON.stringify({ ok: !result.retryable, outcome: result.outcome }), {
|
|
131
|
+
headers,
|
|
132
|
+
status: result.statusCode,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function jsonResponse(status, body) {
|
|
136
|
+
return new Response(JSON.stringify(body), {
|
|
137
|
+
headers: { "content-type": "application/json" },
|
|
138
|
+
status,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function toProcessRecord(resolved) {
|
|
142
|
+
switch (resolved.kind) {
|
|
143
|
+
case "ignored":
|
|
144
|
+
return {
|
|
145
|
+
kind: resolved.kind,
|
|
146
|
+
sessionId: resolved.sessionId,
|
|
147
|
+
subscriptionId: resolved.subscriptionId,
|
|
148
|
+
};
|
|
149
|
+
case "terminal_session":
|
|
150
|
+
return { kind: resolved.kind, sessionId: resolved.session.id, subscriptionId: null };
|
|
151
|
+
case "initial_provision":
|
|
152
|
+
return {
|
|
153
|
+
kind: resolved.kind,
|
|
154
|
+
sessionId: resolved.session.id,
|
|
155
|
+
subscriptionId: resolved.subscription?.id ?? null,
|
|
156
|
+
};
|
|
157
|
+
default:
|
|
158
|
+
return {
|
|
159
|
+
kind: resolved.kind,
|
|
160
|
+
sessionId: resolved.session.id,
|
|
161
|
+
subscriptionId: resolved.subscription.id,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function readWebhookPayload(input) {
|
|
166
|
+
if (isFetchRequest(input)) {
|
|
167
|
+
return {
|
|
168
|
+
body: await input.text(),
|
|
169
|
+
signature: input.headers.get(webhooks_js_1.PROXY_SIGNATURE_HEADER),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const body = typeof input.body === "string" ? input.body : input.body.toString("utf8");
|
|
173
|
+
return { body, signature: input.signature ?? null };
|
|
174
|
+
}
|
|
175
|
+
function isFetchRequest(input) {
|
|
176
|
+
return typeof input.text === "function";
|
|
177
|
+
}
|