@agent-cards/checkout 0.18.0 → 0.21.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/README.md +6 -669
- package/cdp.d.ts +1 -0
- package/cdp.js +2 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +33 -33
- package/playwright.d.ts +1 -0
- package/playwright.js +2 -0
- package/preflight.d.ts +1 -0
- package/preflight.js +2 -0
- package/CHANGELOG.md +0 -124
- package/PREFLIGHT.md +0 -308
- package/dist/adyen.generated.d.ts +0 -24
- package/dist/adyen.generated.js +0 -64
- package/dist/attachment.d.ts +0 -11
- package/dist/attachment.js +0 -50
- package/dist/braintree.d.ts +0 -2
- package/dist/braintree.generated.d.ts +0 -10
- package/dist/braintree.generated.js +0 -302
- package/dist/braintree.js +0 -2
- package/dist/builtin-registry.generated.d.ts +0 -2
- package/dist/builtin-registry.generated.js +0 -1
- package/dist/card-fields.generated.d.ts +0 -3
- package/dist/card-fields.generated.js +0 -46
- package/dist/cdp.d.ts +0 -189
- package/dist/cdp.js +0 -2194
- package/dist/checkout-com.generated.d.ts +0 -4
- package/dist/checkout-com.generated.js +0 -183
- package/dist/client.d.ts +0 -618
- package/dist/client.js +0 -1251
- package/dist/hosted-form.d.ts +0 -44
- package/dist/hosted-form.js +0 -78
- package/dist/index.d.ts +0 -13
- package/dist/index.js +0 -6
- package/dist/lifecycle.d.ts +0 -165
- package/dist/lifecycle.js +0 -370
- package/dist/mercado-checkout.d.ts +0 -20
- package/dist/mercado-checkout.generated.d.ts +0 -52
- package/dist/mercado-checkout.generated.js +0 -198
- package/dist/mercado-checkout.js +0 -108
- package/dist/owned-shop.generated.d.ts +0 -24
- package/dist/owned-shop.generated.js +0 -108
- package/dist/paysafe.generated.d.ts +0 -12
- package/dist/paysafe.generated.js +0 -87
- package/dist/playwright.d.ts +0 -3
- package/dist/playwright.js +0 -3
- package/dist/preflight-capabilities.generated.d.ts +0 -1253
- package/dist/preflight-capabilities.generated.js +0 -1929
- package/dist/preflight-catalog.json +0 -4595
- package/dist/preflight-playwright.d.ts +0 -34
- package/dist/preflight-playwright.js +0 -355
- package/dist/preflight-schemas.json +0 -1110
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.generated.d.ts +0 -1965
- package/dist/preflight.generated.js +0 -556
- package/dist/preflight.js +0 -2
- package/dist/preparation.d.ts +0 -31
- package/dist/preparation.js +0 -164
- package/dist/prepared-processor.d.ts +0 -10
- package/dist/prepared-processor.js +0 -122
- package/dist/recurly.generated.d.ts +0 -1
- package/dist/recurly.generated.js +0 -87
- package/dist/registry.d.ts +0 -76
- package/dist/registry.js +0 -296
- package/dist/spreedly.generated.d.ts +0 -10
- package/dist/spreedly.generated.js +0 -332
- package/dist/stripe-checkout.d.ts +0 -81
- package/dist/stripe-checkout.generated.d.ts +0 -82
- package/dist/stripe-checkout.generated.js +0 -1004
- package/dist/stripe-checkout.js +0 -140
- package/dist/substitute.d.ts +0 -38
- package/dist/substitute.js +0 -23
- package/dist/substitutions.generated.d.ts +0 -10
- package/dist/substitutions.generated.js +0 -66
- package/examples/existing-browser.mjs +0 -63
- package/examples/preflight/classify-direct.mjs +0 -21
- package/examples/preflight/classify-kernel.mjs +0 -30
- package/examples/preflight/inspect-browser.mjs +0 -44
- package/examples/preflight/kernel-native/README.md +0 -112
- package/examples/preflight/kernel-native/documented-adapters.json +0 -113
- package/examples/preflight/kernel-native/inventory.json +0 -233
- package/examples/preflight/kernel-native/qualification.mjs +0 -182
- package/examples/preflight/kernel-profile.empty.json +0 -11
- package/examples/preflight/mollie-hosted.observations.json +0 -23
- package/examples/preflight/mollie-hosted.result.json +0 -103
- package/examples/preflight/stripe-script.direct.result.json +0 -92
- package/examples/preflight/stripe-script.observations.json +0 -16
- package/examples/preflight/stripe-script.result.json +0 -87
package/dist/preflight.js
DELETED
package/dist/preparation.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { type PreparedCheckout } from './client.js';
|
|
2
|
-
import type { AttachOptions } from './cdp.js';
|
|
3
|
-
import type { CheckoutLifecycle } from './lifecycle.js';
|
|
4
|
-
/** A local, one-use rendezvous. It never starts or retries a merchant request. */
|
|
5
|
-
export declare class PreparationGate {
|
|
6
|
-
private readonly opts;
|
|
7
|
-
private readonly lifecycle;
|
|
8
|
-
private readonly readDocumentUrl;
|
|
9
|
-
private state;
|
|
10
|
-
private observedRequest;
|
|
11
|
-
private documentUrl;
|
|
12
|
-
private prepared?;
|
|
13
|
-
private stop;
|
|
14
|
-
private expiryTimer?;
|
|
15
|
-
constructor(opts: AttachOptions, lifecycle: CheckoutLifecycle, readDocumentUrl: () => Promise<string>);
|
|
16
|
-
private prepare;
|
|
17
|
-
/** Called for every recognized card mutation, before any await or local retry guard. */
|
|
18
|
-
claim(requestUrl: string, requestBody?: string | null, requestHeaders?: Record<string, string>): PreparedCheckout | undefined;
|
|
19
|
-
assertDocument(): Promise<void>;
|
|
20
|
-
private readDocument;
|
|
21
|
-
isEngaged(): boolean;
|
|
22
|
-
retireUnboundClaim(): void;
|
|
23
|
-
/** A bound native request has its own cancellation/unknown-outcome machinery. */
|
|
24
|
-
invalidate(reason: string): void;
|
|
25
|
-
/**
|
|
26
|
-
* Retire the server handle first, then report the form outcome. Neither
|
|
27
|
-
* call waits on the other: a stalled report cannot delay the cancel, and a
|
|
28
|
-
* stalled cancel cannot lose the report.
|
|
29
|
-
*/
|
|
30
|
-
private cancelThenObserve;
|
|
31
|
-
}
|
package/dist/preparation.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { CheckoutPreparationError } from './client.js';
|
|
2
|
-
import { validAmountInput } from './client.js';
|
|
3
|
-
import { matchesPreparedRequest, validPreparationEnvironment, preparationEndpoint } from './prepared-processor.js';
|
|
4
|
-
/** A local, one-use rendezvous. It never starts or retries a merchant request. */
|
|
5
|
-
export class PreparationGate {
|
|
6
|
-
opts;
|
|
7
|
-
lifecycle;
|
|
8
|
-
readDocumentUrl;
|
|
9
|
-
state = 'unused';
|
|
10
|
-
observedRequest = false;
|
|
11
|
-
documentUrl = '';
|
|
12
|
-
prepared;
|
|
13
|
-
stop = new AbortController();
|
|
14
|
-
expiryTimer;
|
|
15
|
-
constructor(opts, lifecycle, readDocumentUrl) {
|
|
16
|
-
this.opts = opts;
|
|
17
|
-
this.lifecycle = lifecycle;
|
|
18
|
-
this.readDocumentUrl = readDocumentUrl;
|
|
19
|
-
lifecycle.setPreparationHandler(options => this.prepare(options));
|
|
20
|
-
lifecycle.abort.signal.addEventListener('abort', () => this.invalidate('cancelled'), { once: true });
|
|
21
|
-
}
|
|
22
|
-
async prepare(options) {
|
|
23
|
-
if (this.opts.stripeCheckout)
|
|
24
|
-
throw new CheckoutPreparationError(null, 'unsupported_processor');
|
|
25
|
-
options = { ...options };
|
|
26
|
-
if (this.state !== 'unused' || this.observedRequest || this.lifecycle.getState().status !== 'idle' || this.lifecycle.isBlocked()) {
|
|
27
|
-
// A late opt-in cannot turn the next native retry into ordinary approval.
|
|
28
|
-
// Preserve any existing authorization's state for reconciliation.
|
|
29
|
-
if (this.state === 'unused')
|
|
30
|
-
this.state = 'failed';
|
|
31
|
-
throw new CheckoutPreparationError(this.prepared?.id ?? null, 'must_prepare_before_first_request');
|
|
32
|
-
}
|
|
33
|
-
// Reserve synchronously, including while origin discovery/OAuth is pending.
|
|
34
|
-
this.state = 'preparing';
|
|
35
|
-
this.lifecycle.preparing();
|
|
36
|
-
const signal = AbortSignal.any([this.stop.signal, this.lifecycle.abort.signal, ...(options?.signal ? [options.signal] : [])]);
|
|
37
|
-
const onAbort = () => this.invalidate('cancelled');
|
|
38
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
39
|
-
try {
|
|
40
|
-
if (!options || !validPreparationEnvironment(options.psp, options.environment))
|
|
41
|
-
throw new CheckoutPreparationError(null, 'unsupported_processor');
|
|
42
|
-
const tokenizer = preparationEndpoint(options.psp, options.environment);
|
|
43
|
-
if (!this.opts.vault.isCardRequest(tokenizer, 'POST'))
|
|
44
|
-
throw new CheckoutPreparationError(null, 'processor_interception_unavailable');
|
|
45
|
-
if (!validAmountInput(this.opts.amount) || this.opts.amount === 0 || !/^[a-z]{3}$/i.test(this.opts.currency ?? ''))
|
|
46
|
-
throw new CheckoutPreparationError(null, 'amount_required');
|
|
47
|
-
if (signal.aborted)
|
|
48
|
-
throw new CheckoutPreparationError(null, 'cancelled');
|
|
49
|
-
this.documentUrl = await this.readDocument();
|
|
50
|
-
const page = new URL(this.documentUrl);
|
|
51
|
-
if (!(page.protocol === 'https:' || (page.protocol === 'http:' && page.hostname === 'localhost')) || page.username || page.password)
|
|
52
|
-
throw new CheckoutPreparationError(null, 'merchant_origin_invalid');
|
|
53
|
-
const prepared = await this.opts.vault.prepareCheckout({
|
|
54
|
-
...options, user: this.opts.user, merchant: this.opts.merchant,
|
|
55
|
-
amount: this.opts.amount, currency: this.opts.currency, cardId: this.opts.cardId,
|
|
56
|
-
merchantOrigin: page.origin, checkoutKey: crypto.randomUUID(), timeoutMs: this.opts.timeoutMs, signal,
|
|
57
|
-
onPreparationCreated: id => this.lifecycle.preparationCreated(id),
|
|
58
|
-
onApprovalUrl: url => {
|
|
59
|
-
if (!signal.aborted) {
|
|
60
|
-
this.lifecycle.approvalUrl(url);
|
|
61
|
-
try {
|
|
62
|
-
Promise.resolve(this.opts.onApprovalUrl?.(url)).catch(() => { });
|
|
63
|
-
}
|
|
64
|
-
catch { /* observer only */ }
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
this.prepared = prepared;
|
|
69
|
-
if (prepared.psp !== options.psp || prepared.environment !== options.environment)
|
|
70
|
-
throw new CheckoutPreparationError(prepared.id, 'checkout_changed');
|
|
71
|
-
if (signal.aborted || this.state !== 'preparing') {
|
|
72
|
-
void this.cancelThenObserve(prepared.id, 'cancelled');
|
|
73
|
-
throw new CheckoutPreparationError(prepared.id, 'cancelled');
|
|
74
|
-
}
|
|
75
|
-
await this.assertDocument();
|
|
76
|
-
if (signal.aborted || this.state !== 'preparing')
|
|
77
|
-
throw new CheckoutPreparationError(prepared.id, 'cancelled');
|
|
78
|
-
const remaining = Date.parse(prepared.expiresAt) - Date.now();
|
|
79
|
-
if (!(remaining > 0))
|
|
80
|
-
throw new CheckoutPreparationError(prepared.id, 'expired');
|
|
81
|
-
this.state = 'ready';
|
|
82
|
-
this.expiryTimer = setTimeout(() => this.invalidate('expired'), remaining);
|
|
83
|
-
this.expiryTimer.unref?.();
|
|
84
|
-
this.lifecycle.prepared(prepared);
|
|
85
|
-
return prepared;
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
const failure = error instanceof CheckoutPreparationError ? error : new CheckoutPreparationError(this.prepared?.id ?? null, 'preparation_unconfirmed');
|
|
89
|
-
this.invalidate(failure.reason);
|
|
90
|
-
throw failure;
|
|
91
|
-
}
|
|
92
|
-
// Keep the signal listener after ready: caller cancellation retires the handle too.
|
|
93
|
-
}
|
|
94
|
-
/** Called for every recognized card mutation, before any await or local retry guard. */
|
|
95
|
-
claim(requestUrl, requestBody, requestHeaders) {
|
|
96
|
-
this.observedRequest = true;
|
|
97
|
-
if (this.state === 'unused')
|
|
98
|
-
return undefined;
|
|
99
|
-
if (this.state !== 'ready' || !this.prepared) {
|
|
100
|
-
this.invalidate(this.state === 'preparing' ? 'submitted_before_ready' : 'already_used_or_unavailable');
|
|
101
|
-
throw new CheckoutPreparationError(this.prepared?.id ?? null, 'already_used_or_unavailable');
|
|
102
|
-
}
|
|
103
|
-
const prepared = this.prepared;
|
|
104
|
-
if (Date.parse(prepared.expiresAt) <= Date.now() || !matchesPreparedRequest(prepared.psp, prepared.environment, requestUrl, 'POST', requestBody, requestHeaders)) {
|
|
105
|
-
const reason = Date.parse(prepared.expiresAt) <= Date.now() ? 'expired' : 'checkout_changed';
|
|
106
|
-
this.invalidate(reason);
|
|
107
|
-
throw new CheckoutPreparationError(prepared.id, reason);
|
|
108
|
-
}
|
|
109
|
-
this.state = 'consumed';
|
|
110
|
-
clearTimeout(this.expiryTimer);
|
|
111
|
-
return prepared;
|
|
112
|
-
}
|
|
113
|
-
async assertDocument() {
|
|
114
|
-
if (this.documentUrl && await this.readDocument() !== this.documentUrl)
|
|
115
|
-
throw new CheckoutPreparationError(this.prepared?.id ?? null, 'merchant_document_changed');
|
|
116
|
-
}
|
|
117
|
-
async readDocument() {
|
|
118
|
-
const signal = AbortSignal.any([this.stop.signal, this.lifecycle.abort.signal, AbortSignal.timeout(5_000)]);
|
|
119
|
-
const failure = () => new CheckoutPreparationError(this.prepared?.id ?? null, 'merchant_document_unavailable');
|
|
120
|
-
if (signal.aborted)
|
|
121
|
-
throw failure();
|
|
122
|
-
let aborted;
|
|
123
|
-
const stopped = new Promise((_, reject) => {
|
|
124
|
-
aborted = () => reject(failure());
|
|
125
|
-
signal.addEventListener('abort', aborted, { once: true });
|
|
126
|
-
});
|
|
127
|
-
try {
|
|
128
|
-
return await Promise.race([this.readDocumentUrl(), stopped]);
|
|
129
|
-
}
|
|
130
|
-
finally {
|
|
131
|
-
signal.removeEventListener('abort', aborted);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
isEngaged() { return this.state !== 'unused'; }
|
|
135
|
-
retireUnboundClaim() {
|
|
136
|
-
if (this.state !== 'consumed' || !this.prepared || this.lifecycle.getState().authorizationId)
|
|
137
|
-
return;
|
|
138
|
-
void this.cancelThenObserve(this.prepared.id, 'request_not_bound');
|
|
139
|
-
// No bound ID means the adapter must not leave a spent handle appearing ready.
|
|
140
|
-
if (this.lifecycle.getState().status === 'ready_to_submit')
|
|
141
|
-
this.lifecycle.preparationFailed(new CheckoutPreparationError(this.prepared.id, 'request_not_bound'));
|
|
142
|
-
}
|
|
143
|
-
/** A bound native request has its own cancellation/unknown-outcome machinery. */
|
|
144
|
-
invalidate(reason) {
|
|
145
|
-
if (this.state === 'unused' || this.state === 'consumed' || this.state === 'failed')
|
|
146
|
-
return;
|
|
147
|
-
this.state = 'failed';
|
|
148
|
-
clearTimeout(this.expiryTimer);
|
|
149
|
-
this.stop.abort();
|
|
150
|
-
if (this.prepared)
|
|
151
|
-
void this.cancelThenObserve(this.prepared.id, reason);
|
|
152
|
-
this.lifecycle.preparationFailed(new CheckoutPreparationError(this.prepared?.id ?? null, reason));
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Retire the server handle first, then report the form outcome. Neither
|
|
156
|
-
* call waits on the other: a stalled report cannot delay the cancel, and a
|
|
157
|
-
* stalled cancel cannot lose the report.
|
|
158
|
-
*/
|
|
159
|
-
async cancelThenObserve(id, reason) {
|
|
160
|
-
const cancellation = this.opts.vault.cancelPreparation(id).catch(() => { });
|
|
161
|
-
const observation = this.opts.vault.observePreparation?.(id, this.observedRequest ? 'presented_not_filled' : 'not_presented', reason);
|
|
162
|
-
await Promise.all([cancellation, observation?.catch(() => { })]);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe';
|
|
2
|
-
export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
|
|
3
|
-
/** How the approved card reaches the processor: the device's own request (token), or ciphertext the device produces for this browser to send (cse, Adyen). */
|
|
4
|
-
export type PreparationMode = 'token' | 'cse';
|
|
5
|
-
export declare function preparationMode(psp: string): PreparationMode;
|
|
6
|
-
/** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. Adyen can: test and live are separate hosts and key prefixes. */
|
|
7
|
-
export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
|
|
8
|
-
export declare function preparationEndpoint(psp: PreparationProcessor, environment: PreparationEnvironment): string;
|
|
9
|
-
/** Processor identity and environment are part of the device's prior consent. */
|
|
10
|
-
export declare function matchesPreparedRequest(psp: PreparationProcessor, environment: PreparationEnvironment, requestUrl: string, method: string, body?: string | null, headers?: Record<string, string>): boolean;
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { paysafePreparationUrl, isPreparedPaysafeRequest } from './paysafe.generated.js';
|
|
2
|
-
import { adyenPreparationUrl, isPreparedAdyenRequest } from './adyen.generated.js';
|
|
3
|
-
import { braintreeEnvironment, isPreparedBraintreeRequest, readTokenizationJson } from './braintree.js';
|
|
4
|
-
import { isPreparedRecurlyRequest } from './recurly.generated.js';
|
|
5
|
-
import { isPreparedSpreedlyRequest, isSpreedlyTokenRequest, SPREEDLY_TOKEN_ENDPOINT } from './spreedly.generated.js';
|
|
6
|
-
import { checkoutComPreparationUrl, isPreparedCheckoutComRequest } from './checkout-com.generated.js';
|
|
7
|
-
export function preparationMode(psp) {
|
|
8
|
-
return psp === 'adyen' ? 'cse' : 'token';
|
|
9
|
-
}
|
|
10
|
-
/** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. Adyen can: test and live are separate hosts and key prefixes. */
|
|
11
|
-
export function validPreparationEnvironment(psp, environment) {
|
|
12
|
-
if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
|
|
13
|
-
return environment === 'shared';
|
|
14
|
-
return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
15
|
-
}
|
|
16
|
-
export function preparationEndpoint(psp, environment) {
|
|
17
|
-
if (!validPreparationEnvironment(psp, environment))
|
|
18
|
-
throw new Error('unsupported_preparation_processor');
|
|
19
|
-
if (psp === 'paysafe')
|
|
20
|
-
return paysafePreparationUrl(environment);
|
|
21
|
-
if (psp === 'checkout_com')
|
|
22
|
-
return checkoutComPreparationUrl(environment);
|
|
23
|
-
if (psp === 'adyen')
|
|
24
|
-
return adyenPreparationUrl(environment);
|
|
25
|
-
if (psp === 'bambora')
|
|
26
|
-
return 'https://api.bam.shift4api.net/scripts/tokenization/tokens';
|
|
27
|
-
if (psp === 'mercado_pago')
|
|
28
|
-
return 'https://api.mercadopago.com/v1/card_tokens';
|
|
29
|
-
if (psp === 'recurly')
|
|
30
|
-
return 'https://api.recurly.com/js/v1/token';
|
|
31
|
-
if (psp === 'spreedly')
|
|
32
|
-
return SPREEDLY_TOKEN_ENDPOINT;
|
|
33
|
-
if (psp === 'worldpay')
|
|
34
|
-
return environment === 'production'
|
|
35
|
-
? 'https://access.worldpay.com/sessions/card' : 'https://try.access.worldpay.com/sessions/card';
|
|
36
|
-
if (psp === 'braintree')
|
|
37
|
-
return environment === 'production'
|
|
38
|
-
? 'https://payments.braintree-api.com/graphql' : 'https://payments.sandbox.braintree-api.com/graphql';
|
|
39
|
-
return environment === 'production'
|
|
40
|
-
? 'https://pci-connect.squareup.com/v2/card-nonce' : 'https://pci-connect.squareupsandbox.com/v2/card-nonce';
|
|
41
|
-
}
|
|
42
|
-
function record(value) {
|
|
43
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
-
}
|
|
45
|
-
const keys = (value, allowed) => Object.keys(value).every(key => allowed.includes(key));
|
|
46
|
-
const string = (value, max = 1024) => typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
47
|
-
const digits = (value, min, max) => typeof value === 'string' && value.length >= min && value.length <= max && !/[^0-9]/.test(value);
|
|
48
|
-
const month = (value) => (typeof value === 'number' && Number.isInteger(value) || digits(value, 1, 2)) && Number(value) >= 1 && Number(value) <= 12;
|
|
49
|
-
const year = (value) => (typeof value === 'number' && Number.isInteger(value) || digits(value, 2, 4)) && (Number(value) >= 0 && Number(value) <= 99 || Number(value) >= 2000 && Number(value) <= 9999);
|
|
50
|
-
/** Native fresh-card shapes only. Saved-card, charge and recurring siblings never consume consent. */
|
|
51
|
-
function freshCardBody(psp, body) {
|
|
52
|
-
const value = readTokenizationJson(body);
|
|
53
|
-
if (!value)
|
|
54
|
-
return false;
|
|
55
|
-
if (psp === 'worldpay')
|
|
56
|
-
return keys(value, ['identity', 'cardNumber', 'cardExpiryDate', 'cvc'])
|
|
57
|
-
&& string(value.identity) && digits(value.cardNumber, 12, 19)
|
|
58
|
-
&& record(value.cardExpiryDate) && keys(value.cardExpiryDate, ['month', 'year'])
|
|
59
|
-
&& month(value.cardExpiryDate.month) && year(value.cardExpiryDate.year)
|
|
60
|
-
&& (value.cvc === undefined || digits(value.cvc, 3, 4));
|
|
61
|
-
if (psp === 'bambora')
|
|
62
|
-
return keys(value, ['number', 'expiry_month', 'expiry_year', 'cvd'])
|
|
63
|
-
&& digits(value.number, 12, 19) && month(value.expiry_month) && year(value.expiry_year)
|
|
64
|
-
&& (value.cvd === undefined || digits(value.cvd, 3, 4));
|
|
65
|
-
if (psp !== 'mercado_pago' || !keys(value, ['card_number', 'expiration_month', 'expiration_year', 'security_code', 'cardholder', 'device'])
|
|
66
|
-
|| !digits(value.card_number, 12, 19) || !month(value.expiration_month) || !year(value.expiration_year)
|
|
67
|
-
|| (value.security_code !== undefined && value.security_code !== '' && !digits(value.security_code, 3, 4)) || !record(value.cardholder)
|
|
68
|
-
|| !keys(value.cardholder, ['name', 'identification']))
|
|
69
|
-
return false;
|
|
70
|
-
const holder = value.cardholder;
|
|
71
|
-
if (holder.name !== undefined && holder.name !== '' && !string(holder.name))
|
|
72
|
-
return false;
|
|
73
|
-
if (holder.identification !== undefined && (!record(holder.identification) || !keys(holder.identification, ['type', 'number'])
|
|
74
|
-
|| Object.values(holder.identification).some(v => v !== '' && !string(v))))
|
|
75
|
-
return false;
|
|
76
|
-
if (value.device !== undefined && (!record(value.device) || !keys(value.device, ['meli'])
|
|
77
|
-
|| !record(value.device.meli) || !keys(value.device.meli, ['session_id']) || !string(value.device.meli.session_id, 4096)))
|
|
78
|
-
return false;
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
/** Processor identity and environment are part of the device's prior consent. */
|
|
82
|
-
export function matchesPreparedRequest(psp, environment, requestUrl, method, body, headers) {
|
|
83
|
-
if (method.toUpperCase() !== 'POST' || !validPreparationEnvironment(psp, environment))
|
|
84
|
-
return false;
|
|
85
|
-
if (psp === 'paysafe')
|
|
86
|
-
return isPreparedPaysafeRequest(requestUrl, method, body ?? null, environment, headers);
|
|
87
|
-
if (psp === 'checkout_com')
|
|
88
|
-
return isPreparedCheckoutComRequest(requestUrl, method, body ?? null, environment, headers);
|
|
89
|
-
if (psp === 'adyen')
|
|
90
|
-
return isPreparedAdyenRequest(requestUrl, method, body ?? null, environment);
|
|
91
|
-
if (psp === 'braintree')
|
|
92
|
-
return braintreeEnvironment(requestUrl) === environment && isPreparedBraintreeRequest(body ?? null);
|
|
93
|
-
if (psp === 'spreedly')
|
|
94
|
-
return isSpreedlyTokenRequest(requestUrl, method) && isPreparedSpreedlyRequest(body ?? null);
|
|
95
|
-
try {
|
|
96
|
-
const request = new URL(requestUrl), endpoint = new URL(preparationEndpoint(psp, environment));
|
|
97
|
-
if (request.username || request.password || request.hash)
|
|
98
|
-
return false;
|
|
99
|
-
if (psp === 'recurly')
|
|
100
|
-
return [endpoint.href, 'https://api.eu.recurly.com/js/v1/token'].includes(requestUrl)
|
|
101
|
-
&& isPreparedRecurlyRequest(body ?? null);
|
|
102
|
-
if (psp === 'bambora') {
|
|
103
|
-
if (![endpoint.href, 'https://api.na.bambora.com/scripts/tokenization/tokens'].includes(requestUrl))
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
else if (psp === 'worldpay') {
|
|
107
|
-
if (requestUrl !== endpoint.href)
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
else if (request.origin !== endpoint.origin || request.pathname !== endpoint.pathname)
|
|
111
|
-
return false;
|
|
112
|
-
if (psp === 'mercado_pago') {
|
|
113
|
-
const names = [...request.searchParams.keys()];
|
|
114
|
-
if (new Set(names).size !== names.length || names.some(name => !['public_key', 'locale', 'js_version', 'referer'].includes(name)))
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
return psp === 'square' || freshCardBody(psp, body ?? null);
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function isPreparedRecurlyRequest(body: string | null): boolean;
|
|
@@ -1,87 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/registry.d.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which outbound requests carry a raw card, and where the card sits inside them.
|
|
3
|
-
*
|
|
4
|
-
* This is the only merchant-specific knowledge in the system, and it is keyed by
|
|
5
|
-
* PSP rather than by merchant: every Shopify store shares one entry, every Stripe
|
|
6
|
-
* site shares another. The list is served from the Agentcard API at runtime so
|
|
7
|
-
* integrators pick up new processors without shipping a release.
|
|
8
|
-
*/
|
|
9
|
-
export type Encoding = 'json' | 'form';
|
|
10
|
-
/**
|
|
11
|
-
* How a paused request gets its card. `token` (the default when absent): the
|
|
12
|
-
* cardholder's device calls the processor and the runtime replays the
|
|
13
|
-
* processor's response. `cse`: the processor encrypts the card in-page
|
|
14
|
-
* (Adyen); the device returns ciphertext and the runtime substitutes it into
|
|
15
|
-
* the paused body and lets the request continue. `hosted_form`: the device
|
|
16
|
-
* submits the processor's own form and the paused request resolves with no
|
|
17
|
-
* processor answer (served only to SDKs that ask for it on sync).
|
|
18
|
-
*/
|
|
19
|
-
export type CheckoutMode = 'token' | 'cse' | 'hosted_form';
|
|
20
|
-
export interface Recognizer {
|
|
21
|
-
/** Stable id, e.g. "shopify" */
|
|
22
|
-
psp: string;
|
|
23
|
-
/** Matches the tokenization endpoint. */
|
|
24
|
-
match: RegExp;
|
|
25
|
-
/**
|
|
26
|
-
* Anchored (`^…$`) regex SOURCES for the hostnames this PSP may ever receive
|
|
27
|
-
* a card on — the server's own allowlist, served next to `match` and carried
|
|
28
|
-
* through syncRegistry() verbatim.
|
|
29
|
-
*
|
|
30
|
-
* Strings, not RegExps, because that is the wire shape and because nothing
|
|
31
|
-
* here matches with them: they exist so a transport that can only pre-filter
|
|
32
|
-
* by URL (CDP's `Fetch.enable`, which takes globs) can be armed from the
|
|
33
|
-
* registry instead of a hardcoded list. Optional so a hand-built registry
|
|
34
|
-
* (tests, pinning) still type-checks.
|
|
35
|
-
*/
|
|
36
|
-
hosts?: string[];
|
|
37
|
-
encoding: Encoding;
|
|
38
|
-
/**
|
|
39
|
-
* Request headers that must be replayed verbatim for the merchant to accept
|
|
40
|
-
* the call (signatures, client tokens). Matched case-insensitively.
|
|
41
|
-
*/
|
|
42
|
-
passthroughHeaders: RegExp[];
|
|
43
|
-
/**
|
|
44
|
-
* Form keys that carry the card number. When present, a request on this
|
|
45
|
-
* recognizer's endpoints is a card request only if its body carries one of
|
|
46
|
-
* them: a Stripe confirmation that pays with a method the page already
|
|
47
|
-
* created (`payment_method=pm_...`) carries none, so there is nothing for
|
|
48
|
-
* the cardholder to approve. Served by the API and carried through
|
|
49
|
-
* syncRegistry() verbatim.
|
|
50
|
-
*/
|
|
51
|
-
cardFields?: string[];
|
|
52
|
-
/**
|
|
53
|
-
* With cardFields: the endpoints (regex source over hostname + pathname,
|
|
54
|
-
* anchored) where a request without a card is routine and never ours, so
|
|
55
|
-
* it continues untouched; a request without a card anywhere else is refused.
|
|
56
|
-
*/
|
|
57
|
-
passWithoutCard?: string;
|
|
58
|
-
/**
|
|
59
|
-
* True when the card is encrypted inside the page before the request leaves.
|
|
60
|
-
* A digit swap is useless here; the vault must re-run the PSP's client-side
|
|
61
|
-
* crypto instead. Refused locally unless the entry also carries
|
|
62
|
-
* `mode: 'cse'`, which says the vault does exactly that.
|
|
63
|
-
*/
|
|
64
|
-
clientSideEncrypted?: boolean;
|
|
65
|
-
/** How the card reaches the processor; absent means 'token'. Carried verbatim by syncRegistry. */
|
|
66
|
-
mode?: CheckoutMode;
|
|
67
|
-
}
|
|
68
|
-
import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
|
|
69
|
-
export { BUILTIN_REGISTRY };
|
|
70
|
-
export declare function findRecognizer(url: string, registry?: Recognizer[]): Recognizer | null;
|
|
71
|
-
/**
|
|
72
|
-
* Glob url patterns covering every host in `registry` — what to hand
|
|
73
|
-
* `Fetch.enable` so a card request is ever paused. Deliberately a superset of
|
|
74
|
-
* the recognizers; isCardRequest is the exact check.
|
|
75
|
-
*/
|
|
76
|
-
export declare function cardUrlPatterns(registry: Recognizer[]): string[];
|