@agent-cards/checkout 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/PREFLIGHT.md +1 -1
- package/README.md +70 -13
- package/dist/cdp.js +2 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/lifecycle.d.ts +13 -2
- package/dist/lifecycle.js +23 -7
- package/dist/preflight-capabilities.generated.d.ts +17 -2
- package/dist/preflight-capabilities.generated.js +21 -2
- package/dist/preflight-catalog.json +24 -5
- package/dist/preflight-schemas.json +4 -4
- package/dist/preflight.generated.d.ts +18 -3
- package/dist/preflight.generated.js +2 -2
- package/dist/prepared-processor.d.ts +1 -1
- package/dist/prepared-processor.js +7 -1
- package/dist/recurly.generated.d.ts +1 -0
- package/dist/recurly.generated.js +87 -0
- package/dist/stripe-checkout.d.ts +9 -3
- package/dist/stripe-checkout.generated.d.ts +36 -1
- package/dist/stripe-checkout.generated.js +152 -31
- package/dist/stripe-checkout.js +32 -14
- package/examples/preflight/kernel-native/inventory.json +2 -2
- package/examples/preflight/kernel-profile.empty.json +1 -1
- package/examples/preflight/mollie-hosted.result.json +2 -2
- package/examples/preflight/stripe-script.direct.result.json +2 -2
- package/examples/preflight/stripe-script.result.json +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
+
// source-sha256: 6a1932841621aca1a7371165e0b54a161c4dd5bb077acc190a0c5399229fa350
|
|
3
|
+
/** Normal Recurly.js 4 new-card POST, before it consumes an approval.
|
|
4
|
+
* Native fields and indexed form serialization: recurly/recurly-js commit
|
|
5
|
+
* 7cd9a2b131bf12d7cebfbc7eea1ba86aa54c60e7, lib/recurly/{token,request,fraud}.js
|
|
6
|
+
* and risk/{risk,three-d-secure/three-d-secure}.js. Worldpay and Cybersource
|
|
7
|
+
* risk sessions depend on the original card; proactive Braintree also produces
|
|
8
|
+
* another token type. All nonempty risk results are refused until collection
|
|
9
|
+
* can use the approved card. This function never rewrites the body.
|
|
10
|
+
*/
|
|
11
|
+
const SCALARS = new Set(['number', 'month', 'year', 'cvv', 'type', 'key', 'version', 'deviceId', 'sessionId', 'instanceId',
|
|
12
|
+
'first_name', 'last_name', 'address1', 'address2', 'company', 'country', 'city', 'state', 'postal_code', 'phone',
|
|
13
|
+
'vat_number', 'tax_identifier', 'tax_identifier_type', 'fraud_session_id', 'card_network_preference']);
|
|
14
|
+
const BROWSER = new Set(['color_depth', 'java_enabled', 'language', 'referrer_url', 'screen_height', 'screen_width', 'time_zone_offset', 'user_agent']);
|
|
15
|
+
const FRAUD_PROCESSORS = new Set(['kount', 'litle_threat_metrix', 'braintree', 'fraudnet']);
|
|
16
|
+
const contiguous = (values) => [...values.keys()].sort((a, b) => Number(a) - Number(b)).every((value, index) => value === String(index));
|
|
17
|
+
export function isPreparedRecurlyRequest(body) {
|
|
18
|
+
if (typeof body !== 'string' || !body || body.length > 65536)
|
|
19
|
+
return false;
|
|
20
|
+
const chunks = body.split('&');
|
|
21
|
+
if (chunks.length > 200)
|
|
22
|
+
return false;
|
|
23
|
+
const fields = new Map();
|
|
24
|
+
const fraud = new Map();
|
|
25
|
+
try {
|
|
26
|
+
for (const chunk of chunks) {
|
|
27
|
+
const separator = chunk.indexOf('=');
|
|
28
|
+
if (separator < 1)
|
|
29
|
+
return false;
|
|
30
|
+
const name = decodeURIComponent(chunk.slice(0, separator).replace(/\+/g, ' '));
|
|
31
|
+
const value = decodeURIComponent(chunk.slice(separator + 1).replace(/\+/g, ' '));
|
|
32
|
+
if (fields.has(name) || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value))
|
|
33
|
+
return false;
|
|
34
|
+
fields.set(name, value);
|
|
35
|
+
if (SCALARS.has(name))
|
|
36
|
+
continue;
|
|
37
|
+
const browser = /^browser\[([a-z_]+)\]$/.exec(name);
|
|
38
|
+
if (browser && BROWSER.has(browser[1]))
|
|
39
|
+
continue;
|
|
40
|
+
const structured = /^fraud\[([0-9]|1[0-9])\]\[(.+)\]$/.exec(name);
|
|
41
|
+
if (!structured)
|
|
42
|
+
return false;
|
|
43
|
+
const [, index, leaf] = structured;
|
|
44
|
+
if (!['processor', 'session_id'].includes(leaf) && !/^udf\]\[([0-9]|1[0-9])\]\[(label|value)$/.test(leaf))
|
|
45
|
+
return false;
|
|
46
|
+
if (!fraud.has(index))
|
|
47
|
+
fraud.set(index, new Map());
|
|
48
|
+
fraud.get(index).set(leaf, value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const number = fields.get('number') ?? '', month = fields.get('month') ?? '', year = fields.get('year') ?? '';
|
|
55
|
+
if (!/^[0-9]{12,19}$/.test(number) || !/^[0-9]{1,2}$/.test(month) || Number(month) < 1 || Number(month) > 12
|
|
56
|
+
|| !/^(?:[0-9]{2}|20[0-9]{2})$/.test(year))
|
|
57
|
+
return false;
|
|
58
|
+
if (fields.has('cvv') && fields.get('cvv') !== '' && !/^[0-9]{3,4}$/.test(fields.get('cvv')))
|
|
59
|
+
return false;
|
|
60
|
+
if (fields.has('type') && fields.get('type') !== 'credit_card')
|
|
61
|
+
return false;
|
|
62
|
+
// A co-badged network choice belongs to the entered card's supported brands.
|
|
63
|
+
if (fields.get('card_network_preference'))
|
|
64
|
+
return false;
|
|
65
|
+
if (fields.has('key') && !fields.get('key')?.trim())
|
|
66
|
+
return false;
|
|
67
|
+
if (!contiguous(fraud))
|
|
68
|
+
return false;
|
|
69
|
+
for (const item of fraud.values()) {
|
|
70
|
+
if (!FRAUD_PROCESSORS.has(item.get('processor') ?? '') || !item.get('session_id'))
|
|
71
|
+
return false;
|
|
72
|
+
const udf = new Map();
|
|
73
|
+
for (const key of item.keys()) {
|
|
74
|
+
const field = /^udf\]\[([0-9]|1[0-9])\]\[(label|value)$/.exec(key);
|
|
75
|
+
if (!field)
|
|
76
|
+
continue;
|
|
77
|
+
if (item.get('processor') !== 'kount')
|
|
78
|
+
return false;
|
|
79
|
+
if (!udf.has(field[1]))
|
|
80
|
+
udf.set(field[1], new Set());
|
|
81
|
+
udf.get(field[1]).add(field[2]);
|
|
82
|
+
}
|
|
83
|
+
if (!contiguous(udf) || [...udf.values()].some(keys => keys.size !== 2))
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { STRIPE_CHECKOUT_VALIDATION_CODES } from './stripe-checkout.generated.js';
|
|
1
2
|
import type { PausedRequest } from './client.js';
|
|
2
|
-
/**
|
|
3
|
+
/** Test by default. Production requires explicit opt-in and an independently admitted live grant. */
|
|
3
4
|
export interface StripeCheckoutOptions {
|
|
5
|
+
environment?: 'test' | 'production';
|
|
4
6
|
sessionId: string;
|
|
5
7
|
publishableKey: string;
|
|
6
8
|
}
|
|
@@ -18,11 +20,12 @@ type Step = {
|
|
|
18
20
|
};
|
|
19
21
|
type Phase = Step['phase'] | 'unknown';
|
|
20
22
|
type GateState = 'fresh' | 'stubbed' | 'submitted' | 'stopped';
|
|
23
|
+
type ValidationCode = (typeof STRIPE_CHECKOUT_VALIDATION_CODES)[keyof typeof STRIPE_CHECKOUT_VALIDATION_CODES];
|
|
21
24
|
export type StripeCheckoutBlockStage = 'request_read' | 'classification' | 'claim' | 'readiness' | 'document' | 'stub_response';
|
|
22
25
|
type ReadinessReason = 'attachment_not_ready' | 'cancelled' | 'checkout_inactive' | 'approval_pending' | 'request_inactive';
|
|
23
26
|
export type StripeCheckoutBlockReason = ReadinessReason | 'request_unavailable' | 'request_validation_failed' | 'context_already_present' | 'duplicate_tokenization' | 'duplicate_confirmation' | 'preparation_unavailable' | 'preparation_missing' | 'claim_invalidated' | 'document_changed' | 'document_unavailable' | 'readiness_unavailable' | 'stub_response_failed';
|
|
24
27
|
/** Native pre-authorization refusal metadata. Never contains request or exception text.
|
|
25
|
-
*
|
|
28
|
+
* A classification code comes only from the shared core's fixed vocabulary.
|
|
26
29
|
* gate_state is observed before the adapter retires a failed claim. A preserved duplicate
|
|
27
30
|
* belongs to a competing request: consumers must not cancel the active checkout for it.
|
|
28
31
|
*/
|
|
@@ -33,6 +36,7 @@ export interface StripeCheckoutBlockedDetail {
|
|
|
33
36
|
phase: Phase;
|
|
34
37
|
stage: StripeCheckoutBlockStage;
|
|
35
38
|
reason: StripeCheckoutBlockReason;
|
|
39
|
+
validation_code?: ValidationCode;
|
|
36
40
|
gate_state: GateState;
|
|
37
41
|
disposition: 'active_claim_preserved' | 'checkout_stopped';
|
|
38
42
|
}
|
|
@@ -40,7 +44,8 @@ declare class StripeCheckoutRejectionError extends Error {
|
|
|
40
44
|
readonly stage: StripeCheckoutBlockStage;
|
|
41
45
|
readonly reason: StripeCheckoutBlockReason;
|
|
42
46
|
readonly phase?: Phase | undefined;
|
|
43
|
-
|
|
47
|
+
readonly validationCode?: ValidationCode | undefined;
|
|
48
|
+
constructor(message: string, stage: StripeCheckoutBlockStage, reason: StripeCheckoutBlockReason, phase?: Phase | undefined, validationCode?: ValidationCode | undefined);
|
|
44
49
|
}
|
|
45
50
|
export declare function stripeCheckoutReadinessError(reason: ReadinessReason): Error;
|
|
46
51
|
/** A valid competing request must not retire the request that already claimed this step. */
|
|
@@ -53,6 +58,7 @@ export declare class StripeCheckoutClaimConflictError extends StripeCheckoutReje
|
|
|
53
58
|
*/
|
|
54
59
|
export declare class StripeCheckoutGate {
|
|
55
60
|
private state;
|
|
61
|
+
private billing?;
|
|
56
62
|
private readonly binding?;
|
|
57
63
|
constructor(options: {
|
|
58
64
|
stripeCheckout?: StripeCheckoutOptions;
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
declare var STRIPE_CHECKOUT_OPERATION: string;
|
|
2
2
|
declare var STRIPE_CHECKOUT_CONTEXT_HEADER: string;
|
|
3
|
+
declare var STRIPE_CHECKOUT_VALIDATION_CODES: Readonly<{
|
|
4
|
+
unclassified: "unclassified";
|
|
5
|
+
request_shape: "request_shape";
|
|
6
|
+
request_body_bounds: "request_body_bounds";
|
|
7
|
+
request_headers: "request_headers";
|
|
8
|
+
form_encoding: "form_encoding";
|
|
9
|
+
form_field_unknown: "form_field_unknown";
|
|
10
|
+
form_field_duplicate: "form_field_duplicate";
|
|
11
|
+
form_value_bounds: "form_value_bounds";
|
|
12
|
+
context_invalid: "context_invalid";
|
|
13
|
+
session_binding: "session_binding";
|
|
14
|
+
publishable_key: "publishable_key";
|
|
15
|
+
mode_binding: "mode_binding";
|
|
16
|
+
attribution_session: "attribution_session";
|
|
17
|
+
tokenization_fields: "tokenization_fields";
|
|
18
|
+
billing_email: "billing_email";
|
|
19
|
+
billing_binding: "billing_binding";
|
|
20
|
+
billing_conflict: "billing_conflict";
|
|
21
|
+
expected_amount: "expected_amount";
|
|
22
|
+
payment_method_type: "payment_method_type";
|
|
23
|
+
synthetic_payment_method: "synthetic_payment_method";
|
|
24
|
+
required_final_fields: "required_final_fields";
|
|
25
|
+
}>;
|
|
26
|
+
declare function stripeCheckoutValidationCode(error: any): "unclassified" | "request_shape" | "request_body_bounds" | "request_headers" | "form_encoding" | "form_field_unknown" | "form_field_duplicate" | "form_value_bounds" | "context_invalid" | "session_binding" | "publishable_key" | "mode_binding" | "attribution_session" | "tokenization_fields" | "billing_email" | "billing_binding" | "billing_conflict" | "expected_amount" | "payment_method_type" | "synthetic_payment_method" | "required_final_fields";
|
|
27
|
+
declare function validateStripeCheckoutIdentity(value: any): Readonly<{
|
|
28
|
+
environment: any;
|
|
29
|
+
livemode: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
declare function validateStripeCheckoutCard(card: any, environment?: string): boolean;
|
|
3
32
|
declare function isStripeCheckoutHostedMode(value: any): boolean;
|
|
4
33
|
declare function encodeStripeCheckoutContext(value: any): string;
|
|
5
34
|
declare function hasStripeCheckoutMarker(request: any): boolean;
|
|
@@ -9,6 +38,12 @@ declare function parseStripeCheckoutContext(request: any): {
|
|
|
9
38
|
synthetic_payment_method_id: any;
|
|
10
39
|
} | null | undefined;
|
|
11
40
|
declare function isStripeCheckoutEndpoint(raw: any): boolean;
|
|
41
|
+
declare function captureStripeCheckoutBilling(request: any, options: any): Readonly<{
|
|
42
|
+
session_id: any;
|
|
43
|
+
synthetic_payment_method_id: any;
|
|
44
|
+
email: string | null;
|
|
45
|
+
}>;
|
|
46
|
+
declare function attachStripeCheckoutBilling(request: any, captured: any, options: any): any;
|
|
12
47
|
declare function classifyStripeCheckoutRequest(request: any, options?: {}): {
|
|
13
48
|
phase: string;
|
|
14
49
|
session_id: any;
|
|
@@ -44,4 +79,4 @@ declare function prepareStripeCheckoutReplay(request: any, card: any, options?:
|
|
|
44
79
|
};
|
|
45
80
|
where: string[];
|
|
46
81
|
};
|
|
47
|
-
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay };
|
|
82
|
+
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, STRIPE_CHECKOUT_VALIDATION_CODES, attachStripeCheckoutBilling, captureStripeCheckoutBilling, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay, stripeCheckoutValidationCode, validateStripeCheckoutCard, validateStripeCheckoutIdentity };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: edd2d9de25c51bf0f4ebdbe729366e6d94b9a045b054a0910571e0565bd3cae0
|
|
4
4
|
// src/definitions.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -524,15 +524,82 @@ var replayPolicy = Object.freeze({ contentTypeFor, HEADER_NEVER_FORWARD });
|
|
|
524
524
|
// src/stripe-checkout.js
|
|
525
525
|
var STRIPE_CHECKOUT_OPERATION = "stripe.device_checkout_confirm.v1";
|
|
526
526
|
var STRIPE_CHECKOUT_CONTEXT_HEADER = "x-agentcard-stripe-checkout-context";
|
|
527
|
-
var SESSION = /^
|
|
528
|
-
var KEY = /^
|
|
527
|
+
var SESSION = /^cs_(?:test|live)_[A-Za-z0-9]{1,200}(?![\s\S])/;
|
|
528
|
+
var KEY = /^pk_(?:test|live)_[A-Za-z0-9_]{1,256}(?![\s\S])/;
|
|
529
529
|
var SYNTHETIC = /^pm_agentcard_checkout_[A-Za-z0-9_-]{16,80}(?![\s\S])/;
|
|
530
|
-
var
|
|
531
|
-
|
|
530
|
+
var STRIPE_CHECKOUT_VALIDATION_CODES = Object.freeze({
|
|
531
|
+
unclassified: "unclassified",
|
|
532
|
+
request_shape: "request_shape",
|
|
533
|
+
request_body_bounds: "request_body_bounds",
|
|
534
|
+
request_headers: "request_headers",
|
|
535
|
+
form_encoding: "form_encoding",
|
|
536
|
+
form_field_unknown: "form_field_unknown",
|
|
537
|
+
form_field_duplicate: "form_field_duplicate",
|
|
538
|
+
form_value_bounds: "form_value_bounds",
|
|
539
|
+
context_invalid: "context_invalid",
|
|
540
|
+
session_binding: "session_binding",
|
|
541
|
+
publishable_key: "publishable_key",
|
|
542
|
+
mode_binding: "mode_binding",
|
|
543
|
+
attribution_session: "attribution_session",
|
|
544
|
+
tokenization_fields: "tokenization_fields",
|
|
545
|
+
billing_email: "billing_email",
|
|
546
|
+
billing_binding: "billing_binding",
|
|
547
|
+
billing_conflict: "billing_conflict",
|
|
548
|
+
expected_amount: "expected_amount",
|
|
549
|
+
payment_method_type: "payment_method_type",
|
|
550
|
+
synthetic_payment_method: "synthetic_payment_method",
|
|
551
|
+
required_final_fields: "required_final_fields"
|
|
552
|
+
});
|
|
553
|
+
var validationCodes = /* @__PURE__ */ new WeakMap();
|
|
554
|
+
var reject = (code = "unclassified") => {
|
|
555
|
+
const error = new Error("stripe_checkout_rejected");
|
|
556
|
+
validationCodes.set(error, code);
|
|
557
|
+
throw error;
|
|
532
558
|
};
|
|
559
|
+
function stripeCheckoutValidationCode(error) {
|
|
560
|
+
const code = validationCodes.get(error);
|
|
561
|
+
return Object.values(STRIPE_CHECKOUT_VALIDATION_CODES).find((value) => value === code) ?? STRIPE_CHECKOUT_VALIDATION_CODES.unclassified;
|
|
562
|
+
}
|
|
533
563
|
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
534
564
|
var opaque = (value) => typeof value === "string" && value.length <= 8192 && !/[\x00-\x1f\x7f]/.test(value);
|
|
535
565
|
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
566
|
+
function validateStripeCheckoutIdentity(value) {
|
|
567
|
+
if (!object(value) || !["test", "production"].includes(value.environment ?? "test") || value.environment === null || !["sessionId", "publishableKey", "livemode"].some((key) => Object.hasOwn(value, key)))
|
|
568
|
+
reject("mode_binding");
|
|
569
|
+
const environment = value.environment ?? "test", livemode = environment === "production";
|
|
570
|
+
const mode = livemode ? "live" : "test";
|
|
571
|
+
if (Object.hasOwn(value, "sessionId") && (typeof value.sessionId !== "string" || !SESSION.test(value.sessionId) || !value.sessionId.startsWith(`cs_${mode}_`)) || Object.hasOwn(value, "publishableKey") && (typeof value.publishableKey !== "string" || !KEY.test(value.publishableKey) || !value.publishableKey.startsWith(`pk_${mode}_`)) || Object.hasOwn(value, "livemode") && value.livemode !== livemode)
|
|
572
|
+
reject("mode_binding");
|
|
573
|
+
return Object.freeze({ environment, livemode });
|
|
574
|
+
}
|
|
575
|
+
var TEST_CARDS = ["4242424242424242", "4000000000000002", "4000002500003155"];
|
|
576
|
+
var SYNTHETIC_CARDS = [...TEST_CARDS, "4111111111111111"];
|
|
577
|
+
function validateStripeCheckoutCard(card, environment = "test") {
|
|
578
|
+
if (!["test", "production"].includes(environment) || !object(card) || typeof card.number !== "string" || typeof card.cvc !== "string" || !/^(?:0?[1-9]|1[0-2])(?![\s\S])/.test(String(card.month)) || !/^(?:\d{2}|\d{4})(?![\s\S])/.test(String(card.year)) || card.name !== void 0 && !opaque(card.name))
|
|
579
|
+
reject();
|
|
580
|
+
if (environment === "test") {
|
|
581
|
+
if (!TEST_CARDS.includes(card.number) || card.cvc !== "123")
|
|
582
|
+
reject();
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
if (!/^\d{13,19}(?![\s\S])/.test(card.number) || /^(\d)\1+(?![\s\S])/.test(card.number) || !/^\d{3,4}(?![\s\S])/.test(card.cvc) || SYNTHETIC_CARDS.includes(card.number))
|
|
586
|
+
reject();
|
|
587
|
+
let sum = 0, double = false;
|
|
588
|
+
for (let i = card.number.length - 1; i >= 0; i--) {
|
|
589
|
+
let digit = Number(card.number[i]);
|
|
590
|
+
if (double) {
|
|
591
|
+
digit *= 2;
|
|
592
|
+
if (digit > 9)
|
|
593
|
+
digit -= 9;
|
|
594
|
+
}
|
|
595
|
+
sum += digit;
|
|
596
|
+
double = !double;
|
|
597
|
+
}
|
|
598
|
+
if (sum % 10 !== 0)
|
|
599
|
+
reject();
|
|
600
|
+
}
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
536
603
|
function isStripeCheckoutHostedMode(value) {
|
|
537
604
|
return value === "hosted" || value === "hosted_page";
|
|
538
605
|
}
|
|
@@ -550,7 +617,7 @@ function encodeAscii(value) {
|
|
|
550
617
|
}
|
|
551
618
|
function context(value) {
|
|
552
619
|
if (!object(value) || Object.keys(value).length !== 3 || value.version !== 1 || !["version", "session_id", "synthetic_payment_method_id"].every((key) => Object.hasOwn(value, key)) || !SESSION.test(value.session_id) || !SYNTHETIC.test(value.synthetic_payment_method_id))
|
|
553
|
-
reject();
|
|
620
|
+
reject("context_invalid");
|
|
554
621
|
return { version: 1, session_id: value.session_id, synthetic_payment_method_id: value.synthetic_payment_method_id };
|
|
555
622
|
}
|
|
556
623
|
function encodeStripeCheckoutContext(value) {
|
|
@@ -564,7 +631,7 @@ function parseStripeCheckoutContext(request) {
|
|
|
564
631
|
if (!entries.length)
|
|
565
632
|
return null;
|
|
566
633
|
if (entries.length !== 1 || typeof entries[0][1] !== "string" || !/^[A-Za-z0-9_-]{1,1024}$/.test(entries[0][1]))
|
|
567
|
-
reject();
|
|
634
|
+
reject("context_invalid");
|
|
568
635
|
const encoded = entries[0][1];
|
|
569
636
|
let text = "", bits = 0, count = 0;
|
|
570
637
|
for (const char of encoded) {
|
|
@@ -580,10 +647,10 @@ function parseStripeCheckoutContext(request) {
|
|
|
580
647
|
value = context(JSON.parse(text));
|
|
581
648
|
}
|
|
582
649
|
catch {
|
|
583
|
-
reject();
|
|
650
|
+
reject("context_invalid");
|
|
584
651
|
}
|
|
585
652
|
if (encodeStripeCheckoutContext(value) !== encoded)
|
|
586
|
-
reject();
|
|
653
|
+
reject("context_invalid");
|
|
587
654
|
return value;
|
|
588
655
|
}
|
|
589
656
|
function endpoint(raw) {
|
|
@@ -600,7 +667,7 @@ function endpoint(raw) {
|
|
|
600
667
|
}
|
|
601
668
|
function isStripeCheckoutEndpoint(raw) {
|
|
602
669
|
const url = endpoint(raw);
|
|
603
|
-
const match = url && /^\/v1\/payment_pages\/(
|
|
670
|
+
const match = url && /^\/v1\/payment_pages\/(cs_(?:test|live)_[A-Za-z0-9]+)\/confirm$/.exec(url.pathname);
|
|
604
671
|
return !!match && SESSION.test(match[1]);
|
|
605
672
|
}
|
|
606
673
|
var finalFields = /* @__PURE__ */ new Set([
|
|
@@ -608,6 +675,7 @@ var finalFields = /* @__PURE__ */ new Set([
|
|
|
608
675
|
"key",
|
|
609
676
|
"expected_amount",
|
|
610
677
|
"expected_payment_method_type",
|
|
678
|
+
"payment_method_data[billing_details][email]",
|
|
611
679
|
"init_checksum",
|
|
612
680
|
"js_checksum",
|
|
613
681
|
"version",
|
|
@@ -619,6 +687,9 @@ var finalFields = /* @__PURE__ */ new Set([
|
|
|
619
687
|
"sid",
|
|
620
688
|
"passive_captcha_token",
|
|
621
689
|
"passive_captcha_ekey",
|
|
690
|
+
"pxcts",
|
|
691
|
+
"pxvid",
|
|
692
|
+
"px3",
|
|
622
693
|
...[
|
|
623
694
|
"checkout_config_id",
|
|
624
695
|
"checkout_session_id",
|
|
@@ -658,24 +729,32 @@ var tokenFields = /* @__PURE__ */ new Set([
|
|
|
658
729
|
].map((key) => `client_attribution_metadata[${key}]`)
|
|
659
730
|
]);
|
|
660
731
|
function form(request, fields) {
|
|
661
|
-
if (!object(request) || request.method !== "POST" || typeof request.body !== "string"
|
|
662
|
-
reject();
|
|
732
|
+
if (!object(request) || request.method !== "POST" || typeof request.body !== "string")
|
|
733
|
+
reject("request_shape");
|
|
734
|
+
if (request.body.length > 2e4)
|
|
735
|
+
reject("request_body_bounds");
|
|
736
|
+
if (!object(request.headers) || Object.keys(request.headers).length > 40 || Object.values(request.headers).some((value) => !opaque(value)))
|
|
737
|
+
reject("request_headers");
|
|
663
738
|
const pairs = [];
|
|
664
739
|
const seen = /* @__PURE__ */ new Set();
|
|
665
740
|
for (const raw of request.body.split("&")) {
|
|
666
741
|
const index = raw.indexOf("=");
|
|
667
742
|
if (index < 1)
|
|
668
|
-
reject();
|
|
743
|
+
reject("form_encoding");
|
|
669
744
|
let key, value;
|
|
670
745
|
try {
|
|
671
746
|
key = decodeURIComponent(raw.slice(0, index).replace(/\+/g, " "));
|
|
672
747
|
value = decodeURIComponent(raw.slice(index + 1).replace(/\+/g, " "));
|
|
673
748
|
}
|
|
674
749
|
catch {
|
|
675
|
-
reject();
|
|
750
|
+
reject("form_encoding");
|
|
676
751
|
}
|
|
677
|
-
if (!fields.has(key)
|
|
678
|
-
reject();
|
|
752
|
+
if (!fields.has(key))
|
|
753
|
+
reject("form_field_unknown");
|
|
754
|
+
if (seen.has(key))
|
|
755
|
+
reject("form_field_duplicate");
|
|
756
|
+
if (!opaque(value))
|
|
757
|
+
reject("form_value_bounds");
|
|
679
758
|
seen.add(key);
|
|
680
759
|
pairs.push({ key, value, raw });
|
|
681
760
|
}
|
|
@@ -684,31 +763,60 @@ function form(request, fields) {
|
|
|
684
763
|
function keyFor(request, values, expected) {
|
|
685
764
|
const authorization = Object.entries(request.headers).filter(([key2]) => key2.toLowerCase() === "authorization");
|
|
686
765
|
if (authorization.length > 1)
|
|
687
|
-
reject();
|
|
766
|
+
reject("publishable_key");
|
|
688
767
|
let headerKey;
|
|
689
768
|
if (authorization.length) {
|
|
690
|
-
const match = /^Bearer (
|
|
769
|
+
const match = /^Bearer (pk_(?:test|live)_[A-Za-z0-9_]{1,256})$/.exec(authorization[0][1]);
|
|
691
770
|
if (!match)
|
|
692
|
-
reject();
|
|
771
|
+
reject("publishable_key");
|
|
693
772
|
headerKey = match[1];
|
|
694
773
|
}
|
|
695
774
|
const key = values.get("key") || headerKey;
|
|
696
775
|
if (!KEY.test(key) || headerKey && headerKey !== key || expected !== void 0 && expected !== key)
|
|
697
|
-
reject();
|
|
776
|
+
reject("publishable_key");
|
|
698
777
|
return key;
|
|
699
778
|
}
|
|
779
|
+
var billingEmail = (value) => value === null || typeof value === "string" && value.length > 0 && value.length <= 320 && opaque(value);
|
|
780
|
+
function captureStripeCheckoutBilling(request, options) {
|
|
781
|
+
const phase = classifyStripeCheckoutRequest(request, options);
|
|
782
|
+
if (phase?.phase !== "tokenization" || !SYNTHETIC.test(options?.syntheticPaymentMethodId))
|
|
783
|
+
reject("billing_binding");
|
|
784
|
+
const email = form(request, tokenFields).values.get("billing_details[email]") ?? null;
|
|
785
|
+
if (!billingEmail(email))
|
|
786
|
+
reject("billing_email");
|
|
787
|
+
return Object.freeze({
|
|
788
|
+
session_id: phase.session_id,
|
|
789
|
+
synthetic_payment_method_id: options.syntheticPaymentMethodId,
|
|
790
|
+
email
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function attachStripeCheckoutBilling(request, captured, options) {
|
|
794
|
+
const phase = classifyStripeCheckoutRequest(request, options);
|
|
795
|
+
if (phase?.phase !== "final" || !object(captured) || Object.keys(captured).length !== 3 || !["session_id", "synthetic_payment_method_id", "email"].every((key) => Object.hasOwn(captured, key)) || captured.session_id !== phase.session_id || captured.synthetic_payment_method_id !== phase.synthetic_payment_method_id || !billingEmail(captured.email))
|
|
796
|
+
reject("billing_binding");
|
|
797
|
+
const field = "payment_method_data[billing_details][email]";
|
|
798
|
+
const existing = form(request, finalFields).values.get(field);
|
|
799
|
+
if (existing !== void 0 && existing !== captured.email)
|
|
800
|
+
reject("billing_conflict");
|
|
801
|
+
if (captured.email === null || existing !== void 0)
|
|
802
|
+
return { ...request };
|
|
803
|
+
const body = `${request.body}&${encodeURIComponent(field)}=${encodeURIComponent(captured.email)}`;
|
|
804
|
+
const result = { ...request, body };
|
|
805
|
+
classifyStripeCheckoutRequest(result, options);
|
|
806
|
+
return result;
|
|
807
|
+
}
|
|
700
808
|
function classifyStripeCheckoutRequest(request, options = {}) {
|
|
701
809
|
const url = endpoint(request?.url);
|
|
702
810
|
if (!url) {
|
|
703
811
|
if (hasStripeCheckoutMarker(request))
|
|
704
|
-
reject();
|
|
812
|
+
reject("context_invalid");
|
|
705
813
|
return null;
|
|
706
814
|
}
|
|
707
815
|
const final = isStripeCheckoutEndpoint(request.url);
|
|
708
816
|
const token = url.pathname === "/v1/payment_methods";
|
|
709
817
|
if (!final && !token) {
|
|
710
818
|
if (hasStripeCheckoutMarker(request))
|
|
711
|
-
reject();
|
|
819
|
+
reject("context_invalid");
|
|
712
820
|
return null;
|
|
713
821
|
}
|
|
714
822
|
if (token && options.sessionId === void 0 && !hasStripeCheckoutMarker(request))
|
|
@@ -716,22 +824,34 @@ function classifyStripeCheckoutRequest(request, options = {}) {
|
|
|
716
824
|
const marker = parseStripeCheckoutContext(request);
|
|
717
825
|
const sessionId = final ? url.pathname.split("/")[3] : options.sessionId;
|
|
718
826
|
if (!SESSION.test(sessionId) || options.sessionId !== void 0 && options.sessionId !== sessionId || marker && marker.session_id !== sessionId)
|
|
719
|
-
reject();
|
|
827
|
+
reject("session_binding");
|
|
720
828
|
const { values } = form(request, final ? finalFields : tokenFields);
|
|
721
829
|
const key = keyFor(request, values, options.publishableKey);
|
|
830
|
+
validateStripeCheckoutIdentity({ environment: options.environment, sessionId, publishableKey: key });
|
|
722
831
|
const attributionSession = values.get("client_attribution_metadata[checkout_session_id]");
|
|
723
832
|
if (attributionSession !== void 0 && attributionSession !== sessionId)
|
|
724
|
-
reject();
|
|
833
|
+
reject("attribution_session");
|
|
725
834
|
if (token) {
|
|
726
835
|
const number = values.get("card[number]");
|
|
727
836
|
if (values.get("type") !== "card" || typeof number !== "string" || number.length > 64 || /[^0-9 -]/.test(number) || !["4111111111111111", "4242424242424242"].includes(number.replace(/[ -]/g, "")) || !/^\d{3}$/.test(values.get("card[cvc]") ?? "") || !/^(?:0?[1-9]|1[0-2])$/.test(values.get("card[exp_month]") ?? "") || !/^(?:\d{2}|\d{4})$/.test(values.get("card[exp_year]") ?? "") || values.has("allow_redisplay") && values.get("allow_redisplay") !== "unspecified")
|
|
728
|
-
reject();
|
|
837
|
+
reject("tokenization_fields");
|
|
729
838
|
return { phase: "tokenization", session_id: sessionId, publishable_key: key };
|
|
730
839
|
}
|
|
731
840
|
const amount = values.get("expected_amount");
|
|
732
841
|
const syntheticId = values.get("payment_method");
|
|
733
|
-
|
|
734
|
-
|
|
842
|
+
const email = values.get("payment_method_data[billing_details][email]");
|
|
843
|
+
if (email !== void 0 && !billingEmail(email))
|
|
844
|
+
reject("billing_email");
|
|
845
|
+
if (!/^[1-9]\d{0,11}$/.test(amount ?? "") || !Number.isSafeInteger(Number(amount)) || options.amountCents !== void 0 && options.amountCents !== Number(amount))
|
|
846
|
+
reject("expected_amount");
|
|
847
|
+
if (values.get("expected_payment_method_type") !== "card")
|
|
848
|
+
reject("payment_method_type");
|
|
849
|
+
if (!SYNTHETIC.test(syntheticId))
|
|
850
|
+
reject("synthetic_payment_method");
|
|
851
|
+
if (["init_checksum", "js_checksum", "version", "rv_timestamp"].some((key2) => !values.get(key2)))
|
|
852
|
+
reject("required_final_fields");
|
|
853
|
+
if (options.syntheticPaymentMethodId !== void 0 && options.syntheticPaymentMethodId !== syntheticId || marker && marker.synthetic_payment_method_id !== syntheticId)
|
|
854
|
+
reject("synthetic_payment_method");
|
|
735
855
|
return {
|
|
736
856
|
phase: "final",
|
|
737
857
|
session_id: sessionId,
|
|
@@ -753,7 +873,7 @@ function createStripeCheckoutTokenizationStub(request, options) {
|
|
|
753
873
|
id: options.syntheticPaymentMethodId,
|
|
754
874
|
object: "payment_method",
|
|
755
875
|
type: "card",
|
|
756
|
-
livemode:
|
|
876
|
+
livemode: validateStripeCheckoutIdentity({ environment: options.environment, sessionId: phase.session_id }).livemode,
|
|
757
877
|
billing_details: { ...billing, address },
|
|
758
878
|
card: {
|
|
759
879
|
brand: "visa",
|
|
@@ -783,11 +903,12 @@ function parseStripeCheckoutResponse(raw, options) {
|
|
|
783
903
|
let value;
|
|
784
904
|
try {
|
|
785
905
|
value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
906
|
+
validateStripeCheckoutIdentity({ environment: options?.environment, sessionId: options?.sessionId, livemode: value?.livemode });
|
|
786
907
|
}
|
|
787
908
|
catch {
|
|
788
909
|
return null;
|
|
789
910
|
}
|
|
790
|
-
if (!object(value) || !SESSION.test(options?.sessionId) || value.session_id !== options.sessionId || Object.keys(value).some((key) => !terminalFields.has(key)) || !["id", "object", "state", "status", "payment_status", "currency", "livemode", "session_id", "mode", "ui_mode"].every((key) => Object.hasOwn(value, key)) || typeof value.id !== "string" || !/^[A-Za-z0-9_]{1,256}(?![\s\S])/.test(value.id) || value.object !== "checkout.session" || value.state !== "succeeded" || value.status !== "complete" || value.payment_status !== "paid" || value.currency !== "usd" || value.
|
|
911
|
+
if (!object(value) || !SESSION.test(options?.sessionId) || value.session_id !== options.sessionId || Object.keys(value).some((key) => !terminalFields.has(key)) || !["id", "object", "state", "status", "payment_status", "currency", "livemode", "session_id", "mode", "ui_mode"].every((key) => Object.hasOwn(value, key)) || typeof value.id !== "string" || !/^[A-Za-z0-9_]{1,256}(?![\s\S])/.test(value.id) || value.object !== "checkout.session" || value.state !== "succeeded" || value.status !== "complete" || value.payment_status !== "paid" || value.currency !== "usd" || value.mode !== "payment" || !isStripeCheckoutHostedMode(value.ui_mode))
|
|
791
912
|
return null;
|
|
792
913
|
for (const key of ["success_url", "return_url"]) {
|
|
793
914
|
if (!Object.hasOwn(value, key) || value[key] === null)
|
|
@@ -822,7 +943,7 @@ function prepareStripeCheckoutReplay(request, card, options = {}) {
|
|
|
822
943
|
sessionId: marker.session_id,
|
|
823
944
|
syntheticPaymentMethodId: marker.synthetic_payment_method_id
|
|
824
945
|
});
|
|
825
|
-
if (phase?.phase !== "final" || options.amountCents !== void 0 && phase.amount_cents !== options.amountCents || options.sessionId !== void 0 && options.sessionId !== marker.session_id || !card
|
|
946
|
+
if (phase?.phase !== "final" || options.amountCents !== void 0 && phase.amount_cents !== options.amountCents || options.sessionId !== void 0 && options.sessionId !== marker.session_id || !validateStripeCheckoutCard(card, options.environment))
|
|
826
947
|
reject();
|
|
827
948
|
const replacements = {
|
|
828
949
|
"payment_method_data[type]": "card",
|
|
@@ -840,4 +961,4 @@ function prepareStripeCheckoutReplay(request, card, options = {}) {
|
|
|
840
961
|
where: Object.keys(replacements)
|
|
841
962
|
};
|
|
842
963
|
}
|
|
843
|
-
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay };
|
|
964
|
+
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, STRIPE_CHECKOUT_VALIDATION_CODES, attachStripeCheckoutBilling, captureStripeCheckoutBilling, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay, stripeCheckoutValidationCode, validateStripeCheckoutCard, validateStripeCheckoutIdentity };
|
package/dist/stripe-checkout.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, isStripeCheckoutEndpoint, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
1
|
+
import { classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, isStripeCheckoutEndpoint, STRIPE_CHECKOUT_CONTEXT_HEADER, captureStripeCheckoutBilling, attachStripeCheckoutBilling, validateStripeCheckoutIdentity, stripeCheckoutValidationCode, STRIPE_CHECKOUT_VALIDATION_CODES, } from './stripe-checkout.generated.js';
|
|
2
2
|
class StripeCheckoutRejectionError extends Error {
|
|
3
3
|
stage;
|
|
4
4
|
reason;
|
|
5
5
|
phase;
|
|
6
|
-
|
|
6
|
+
validationCode;
|
|
7
|
+
constructor(message, stage, reason, phase, validationCode) {
|
|
7
8
|
super(message);
|
|
8
9
|
this.stage = stage;
|
|
9
10
|
this.reason = reason;
|
|
10
11
|
this.phase = phase;
|
|
12
|
+
this.validationCode = validationCode;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
export function stripeCheckoutReadinessError(reason) {
|
|
@@ -26,22 +28,23 @@ export class StripeCheckoutClaimConflictError extends StripeCheckoutRejectionErr
|
|
|
26
28
|
*/
|
|
27
29
|
export class StripeCheckoutGate {
|
|
28
30
|
state = 'fresh';
|
|
31
|
+
billing;
|
|
29
32
|
binding;
|
|
30
33
|
constructor(options) {
|
|
31
34
|
if (!options.stripeCheckout)
|
|
32
35
|
return;
|
|
33
|
-
const { sessionId, publishableKey } = options.stripeCheckout;
|
|
36
|
+
const { sessionId, publishableKey, environment } = options.stripeCheckout;
|
|
37
|
+
validateStripeCheckoutIdentity({ environment, sessionId, publishableKey });
|
|
34
38
|
if (options.executionMode !== 'autopilot' || !/^apg_[A-Za-z0-9_-]{1,128}(?![\s\S])/.test(options.grantId ?? '')
|
|
35
|
-
|| !Number.isSafeInteger(options.amount) || Number(options.amount) <= 0 || options.currency !== 'usd'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.binding = Object.freeze({ sessionId, publishableKey, amountCents: options.amount,
|
|
39
|
+
|| !Number.isSafeInteger(options.amount) || Number(options.amount) <= 0 || options.currency !== 'usd')
|
|
40
|
+
throw new Error('Native Stripe Checkout requires a mode-bound session, grant and numeric USD amount.');
|
|
41
|
+
this.binding = Object.freeze({ environment, sessionId, publishableKey, amountCents: options.amount,
|
|
39
42
|
syntheticPaymentMethodId: `pm_agentcard_checkout_${crypto.randomUUID().replaceAll('-', '')}` });
|
|
40
43
|
}
|
|
41
44
|
matches(url) { return !!this.binding && isStripeCheckoutEndpoint(url); }
|
|
42
45
|
isEnabled() { return !!this.binding; }
|
|
43
46
|
isPrepared() { return this.state !== 'fresh'; }
|
|
44
|
-
invalidate() { this.state = 'stopped'; }
|
|
47
|
+
invalidate() { this.state = 'stopped'; this.billing = undefined; }
|
|
45
48
|
/** Observability only: this endpoint label never participates in a payment decision. */
|
|
46
49
|
describeRejection(error, rawUrl, stage, phase = 'unknown') {
|
|
47
50
|
let family = 'other';
|
|
@@ -58,8 +61,10 @@ export class StripeCheckoutGate {
|
|
|
58
61
|
readiness: 'readiness_unavailable', document: 'document_unavailable', stub_response: 'stub_response_failed',
|
|
59
62
|
};
|
|
60
63
|
const rejection = error instanceof StripeCheckoutRejectionError ? error : undefined;
|
|
64
|
+
const reason = rejection?.reason ?? fallback[stage];
|
|
61
65
|
return { version: 1, processor: 'stripe', endpoint_family: family,
|
|
62
|
-
phase: rejection?.phase ?? phase, stage: rejection?.stage ?? stage, reason
|
|
66
|
+
phase: rejection?.phase ?? phase, stage: rejection?.stage ?? stage, reason,
|
|
67
|
+
...(reason === 'request_validation_failed' ? { validation_code: rejection?.validationCode ?? STRIPE_CHECKOUT_VALIDATION_CODES.unclassified } : {}),
|
|
63
68
|
gate_state: this.state, disposition: error instanceof StripeCheckoutClaimConflictError ? 'active_claim_preserved' : 'checkout_stopped' };
|
|
64
69
|
}
|
|
65
70
|
assertClaim(step) {
|
|
@@ -72,7 +77,7 @@ export class StripeCheckoutGate {
|
|
|
72
77
|
return;
|
|
73
78
|
const url = new URL(raw);
|
|
74
79
|
if (url.origin !== 'https://checkout.stripe.com' || url.username || url.password
|
|
75
|
-
|| ![`/c/pay/${this.binding.sessionId}`, `/pay/${this.binding.sessionId}`].includes(url.pathname)) {
|
|
80
|
+
|| ![`/c/pay/${this.binding.sessionId}`, `/pay/${this.binding.sessionId}`, `/g/pay/${this.binding.sessionId}`, `/f/pay/${this.binding.sessionId}`].includes(url.pathname)) {
|
|
76
81
|
this.invalidate();
|
|
77
82
|
throw new StripeCheckoutRejectionError('Native Stripe Checkout document changed.', 'document', 'document_changed');
|
|
78
83
|
}
|
|
@@ -86,8 +91,8 @@ export class StripeCheckoutGate {
|
|
|
86
91
|
try {
|
|
87
92
|
phase = classifyStripeCheckoutRequest(request, this.binding);
|
|
88
93
|
}
|
|
89
|
-
catch {
|
|
90
|
-
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'unknown');
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'unknown', stripeCheckoutValidationCode(error));
|
|
91
96
|
}
|
|
92
97
|
if (!phase)
|
|
93
98
|
return null;
|
|
@@ -102,9 +107,13 @@ export class StripeCheckoutGate {
|
|
|
102
107
|
this.state = 'stopped';
|
|
103
108
|
let response;
|
|
104
109
|
try {
|
|
110
|
+
this.billing = captureStripeCheckoutBilling(request, this.binding);
|
|
105
111
|
response = createStripeCheckoutTokenizationStub(request, this.binding);
|
|
106
112
|
}
|
|
107
|
-
catch {
|
|
113
|
+
catch (error) {
|
|
114
|
+
const code = stripeCheckoutValidationCode(error);
|
|
115
|
+
if (code !== STRIPE_CHECKOUT_VALIDATION_CODES.unclassified)
|
|
116
|
+
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'tokenization', code);
|
|
108
117
|
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'stub_response', 'stub_response_failed', 'tokenization');
|
|
109
118
|
}
|
|
110
119
|
this.state = 'stubbed';
|
|
@@ -114,8 +123,17 @@ export class StripeCheckoutGate {
|
|
|
114
123
|
throw new StripeCheckoutClaimConflictError('final');
|
|
115
124
|
if (phase.phase !== 'final' || this.state !== 'stubbed')
|
|
116
125
|
throw new StripeCheckoutRejectionError('Native Stripe Checkout has no matching prepared request.', 'claim', 'preparation_missing', diagnosticPhase);
|
|
126
|
+
let final;
|
|
127
|
+
try {
|
|
128
|
+
final = attachStripeCheckoutBilling(request, this.billing, this.binding);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.invalidate();
|
|
132
|
+
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'final', stripeCheckoutValidationCode(error));
|
|
133
|
+
}
|
|
117
134
|
this.state = 'submitted';
|
|
118
|
-
|
|
135
|
+
this.billing = undefined;
|
|
136
|
+
return { phase: 'final', request: { ...final, headers: { ...final.headers,
|
|
119
137
|
[STRIPE_CHECKOUT_CONTEXT_HEADER]: encodeStripeCheckoutContext({ version: 1,
|
|
120
138
|
session_id: this.binding.sessionId, synthetic_payment_method_id: this.binding.syntheticPaymentMethodId }) } } };
|
|
121
139
|
}
|