@agent-cards/checkout 0.19.0 → 0.22.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 -753
- 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 -132
- package/PREFLIGHT.md +0 -312
- package/dist/adyen-merchant-hosted.generated.d.ts +0 -277
- package/dist/adyen-merchant-hosted.generated.js +0 -1902
- 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 -192
- package/dist/cdp.js +0 -2393
- package/dist/checkout-com.generated.d.ts +0 -4
- package/dist/checkout-com.generated.js +0 -183
- package/dist/client.d.ts +0 -849
- package/dist/client.js +0 -1754
- package/dist/cse-body.d.ts +0 -25
- package/dist/cse-body.js +0 -41
- package/dist/fiserv.d.ts +0 -65
- package/dist/fiserv.generated.d.ts +0 -73
- package/dist/fiserv.generated.js +0 -830
- package/dist/fiserv.js +0 -104
- package/dist/hosted-form.d.ts +0 -44
- package/dist/hosted-form.js +0 -78
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -10
- package/dist/lifecycle.d.ts +0 -179
- package/dist/lifecycle.js +0 -395
- 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/merchant-handoff.d.ts +0 -54
- package/dist/merchant-handoff.js +0 -100
- package/dist/merchant-hosted.d.ts +0 -140
- package/dist/merchant-hosted.js +0 -170
- package/dist/merchant-total-watch.d.ts +0 -115
- package/dist/merchant-total-watch.js +0 -268
- package/dist/merchant-total.d.ts +0 -257
- package/dist/merchant-total.js +0 -383
- 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/pre-claim.d.ts +0 -123
- package/dist/pre-claim.js +0 -386
- package/dist/preflight-capabilities.generated.d.ts +0 -1253
- package/dist/preflight-capabilities.generated.js +0 -1929
- package/dist/preflight-catalog.json +0 -4727
- package/dist/preflight-playwright.d.ts +0 -34
- package/dist/preflight-playwright.js +0 -355
- package/dist/preflight-schemas.json +0 -1122
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.generated.d.ts +0 -1965
- package/dist/preflight.generated.js +0 -570
- package/dist/preflight.js +0 -2
- package/dist/preparation.d.ts +0 -38
- package/dist/preparation.js +0 -191
- package/dist/prepared-processor.d.ts +0 -43
- package/dist/prepared-processor.js +0 -172
- package/dist/recurly.generated.d.ts +0 -1
- package/dist/recurly.generated.js +0 -87
- package/dist/registry.d.ts +0 -121
- package/dist/registry.js +0 -310
- 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 -1093
- 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 -11
- package/dist/substitutions.generated.js +0 -818
- 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,38 +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
|
-
/**
|
|
23
|
-
* A card request that only a prepared checkout may pay (an Adyen merchant
|
|
24
|
-
* profile's, or a Fiserv card capture, pre-claim.ts) arrived with no
|
|
25
|
-
* preparation and was refused. Like claim() on an unused gate, a later
|
|
26
|
-
* prepare() is then refused too.
|
|
27
|
-
*/
|
|
28
|
-
observeUnprepared(): void;
|
|
29
|
-
retireUnboundClaim(): void;
|
|
30
|
-
/** A bound native request has its own cancellation/unknown-outcome machinery. */
|
|
31
|
-
invalidate(reason: string): void;
|
|
32
|
-
/**
|
|
33
|
-
* Retire the server handle first, then report the form outcome. Neither
|
|
34
|
-
* call waits on the other: a stalled report cannot delay the cancel, and a
|
|
35
|
-
* stalled cancel cannot lose the report.
|
|
36
|
-
*/
|
|
37
|
-
private cancelThenObserve;
|
|
38
|
-
}
|
package/dist/preparation.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { CheckoutPreparationError } from './client.js';
|
|
2
|
-
import { validAmountInput } from './client.js';
|
|
3
|
-
import { matchesPreparation, 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
|
-
// A Fiserv checkout's merchantProfile names a key pin, which prepareCheckout checks: its
|
|
43
|
-
// capture endpoint is checked below like any processor-hosted checkout's.
|
|
44
|
-
if (options.merchantProfile !== undefined && options.psp !== 'fiserv') {
|
|
45
|
-
// An Adyen merchant's own endpoint: a profile this build reviewed, which
|
|
46
|
-
// this attachment pauses, and one Agentcard turned on for this client.
|
|
47
|
-
const profile = typeof this.opts.vault.merchantProfile === 'function' && typeof options.merchantProfile === 'string'
|
|
48
|
-
? this.opts.vault.merchantProfile(options.merchantProfile) : null;
|
|
49
|
-
if (options.psp !== 'adyen')
|
|
50
|
-
throw new CheckoutPreparationError(null, 'unsupported_processor');
|
|
51
|
-
if (!profile)
|
|
52
|
-
throw new CheckoutPreparationError(null, 'processor_interception_unavailable');
|
|
53
|
-
if (profile.status !== 'enabled')
|
|
54
|
-
throw new CheckoutPreparationError(null, 'unsupported_checkout');
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
const tokenizer = preparationEndpoint(options.psp, options.environment);
|
|
58
|
-
if (!this.opts.vault.isCardRequest(tokenizer, 'POST'))
|
|
59
|
-
throw new CheckoutPreparationError(null, 'processor_interception_unavailable');
|
|
60
|
-
}
|
|
61
|
-
if (!validAmountInput(this.opts.amount) || this.opts.amount === 0 || !/^[a-z]{3}$/i.test(this.opts.currency ?? ''))
|
|
62
|
-
throw new CheckoutPreparationError(null, 'amount_required');
|
|
63
|
-
if (signal.aborted)
|
|
64
|
-
throw new CheckoutPreparationError(null, 'cancelled');
|
|
65
|
-
this.documentUrl = await this.readDocument();
|
|
66
|
-
const page = new URL(this.documentUrl);
|
|
67
|
-
if (!(page.protocol === 'https:' || (page.protocol === 'http:' && page.hostname === 'localhost')) || page.username || page.password)
|
|
68
|
-
throw new CheckoutPreparationError(null, 'merchant_origin_invalid');
|
|
69
|
-
const prepared = await this.opts.vault.prepareCheckout({
|
|
70
|
-
...options, user: this.opts.user, merchant: this.opts.merchant,
|
|
71
|
-
amount: this.opts.amount, currency: this.opts.currency, cardId: this.opts.cardId,
|
|
72
|
-
merchantOrigin: page.origin, checkoutKey: crypto.randomUUID(), timeoutMs: this.opts.timeoutMs, signal,
|
|
73
|
-
onPreparationCreated: id => this.lifecycle.preparationCreated(id),
|
|
74
|
-
onApprovalUrl: url => {
|
|
75
|
-
if (!signal.aborted) {
|
|
76
|
-
this.lifecycle.approvalUrl(url);
|
|
77
|
-
try {
|
|
78
|
-
Promise.resolve(this.opts.onApprovalUrl?.(url)).catch(() => { });
|
|
79
|
-
}
|
|
80
|
-
catch { /* observer only */ }
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
this.prepared = prepared;
|
|
85
|
-
// A Fiserv preparation also names the key pin the cardholder approves (merchantProfile).
|
|
86
|
-
if (prepared.psp !== options.psp || prepared.environment !== options.environment
|
|
87
|
-
|| prepared.merchantProfile !== options.merchantProfile)
|
|
88
|
-
throw new CheckoutPreparationError(prepared.id, 'checkout_changed');
|
|
89
|
-
if (signal.aborted || this.state !== 'preparing') {
|
|
90
|
-
void this.cancelThenObserve(prepared.id, 'cancelled');
|
|
91
|
-
throw new CheckoutPreparationError(prepared.id, 'cancelled');
|
|
92
|
-
}
|
|
93
|
-
await this.assertDocument();
|
|
94
|
-
if (signal.aborted || this.state !== 'preparing')
|
|
95
|
-
throw new CheckoutPreparationError(prepared.id, 'cancelled');
|
|
96
|
-
const remaining = Date.parse(prepared.expiresAt) - Date.now();
|
|
97
|
-
if (!(remaining > 0))
|
|
98
|
-
throw new CheckoutPreparationError(prepared.id, 'expired');
|
|
99
|
-
this.state = 'ready';
|
|
100
|
-
this.expiryTimer = setTimeout(() => this.invalidate('expired'), remaining);
|
|
101
|
-
this.expiryTimer.unref?.();
|
|
102
|
-
this.lifecycle.prepared(prepared);
|
|
103
|
-
return prepared;
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
const failure = error instanceof CheckoutPreparationError ? error : new CheckoutPreparationError(this.prepared?.id ?? null, 'preparation_unconfirmed');
|
|
107
|
-
this.invalidate(failure.reason);
|
|
108
|
-
throw failure;
|
|
109
|
-
}
|
|
110
|
-
// Keep the signal listener after ready: caller cancellation retires the handle too.
|
|
111
|
-
}
|
|
112
|
-
/** Called for every recognized card mutation, before any await or local retry guard. */
|
|
113
|
-
claim(requestUrl, requestBody, requestHeaders) {
|
|
114
|
-
this.observedRequest = true;
|
|
115
|
-
if (this.state === 'unused')
|
|
116
|
-
return undefined;
|
|
117
|
-
if (this.state !== 'ready' || !this.prepared) {
|
|
118
|
-
this.invalidate(this.state === 'preparing' ? 'submitted_before_ready' : 'already_used_or_unavailable');
|
|
119
|
-
throw new CheckoutPreparationError(this.prepared?.id ?? null, 'already_used_or_unavailable');
|
|
120
|
-
}
|
|
121
|
-
const prepared = this.prepared;
|
|
122
|
-
// A merchant-hosted preparation claims only its profile's reviewed card request.
|
|
123
|
-
// A Fiserv preparation claims only a capture under the key pin it names.
|
|
124
|
-
if (Date.parse(prepared.expiresAt) <= Date.now() || !matchesPreparation(prepared, requestUrl, 'POST', requestBody, requestHeaders)) {
|
|
125
|
-
const reason = Date.parse(prepared.expiresAt) <= Date.now() ? 'expired' : 'checkout_changed';
|
|
126
|
-
this.invalidate(reason);
|
|
127
|
-
throw new CheckoutPreparationError(prepared.id, reason);
|
|
128
|
-
}
|
|
129
|
-
this.state = 'consumed';
|
|
130
|
-
clearTimeout(this.expiryTimer);
|
|
131
|
-
return prepared;
|
|
132
|
-
}
|
|
133
|
-
async assertDocument() {
|
|
134
|
-
if (this.documentUrl && await this.readDocument() !== this.documentUrl)
|
|
135
|
-
throw new CheckoutPreparationError(this.prepared?.id ?? null, 'merchant_document_changed');
|
|
136
|
-
}
|
|
137
|
-
async readDocument() {
|
|
138
|
-
const signal = AbortSignal.any([this.stop.signal, this.lifecycle.abort.signal, AbortSignal.timeout(5_000)]);
|
|
139
|
-
const failure = () => new CheckoutPreparationError(this.prepared?.id ?? null, 'merchant_document_unavailable');
|
|
140
|
-
if (signal.aborted)
|
|
141
|
-
throw failure();
|
|
142
|
-
let aborted;
|
|
143
|
-
const stopped = new Promise((_, reject) => {
|
|
144
|
-
aborted = () => reject(failure());
|
|
145
|
-
signal.addEventListener('abort', aborted, { once: true });
|
|
146
|
-
});
|
|
147
|
-
try {
|
|
148
|
-
return await Promise.race([this.readDocumentUrl(), stopped]);
|
|
149
|
-
}
|
|
150
|
-
finally {
|
|
151
|
-
signal.removeEventListener('abort', aborted);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
isEngaged() { return this.state !== 'unused'; }
|
|
155
|
-
/**
|
|
156
|
-
* A card request that only a prepared checkout may pay (an Adyen merchant
|
|
157
|
-
* profile's, or a Fiserv card capture, pre-claim.ts) arrived with no
|
|
158
|
-
* preparation and was refused. Like claim() on an unused gate, a later
|
|
159
|
-
* prepare() is then refused too.
|
|
160
|
-
*/
|
|
161
|
-
observeUnprepared() { this.observedRequest = true; }
|
|
162
|
-
retireUnboundClaim() {
|
|
163
|
-
if (this.state !== 'consumed' || !this.prepared || this.lifecycle.getState().authorizationId)
|
|
164
|
-
return;
|
|
165
|
-
void this.cancelThenObserve(this.prepared.id, 'request_not_bound');
|
|
166
|
-
// No bound ID means the adapter must not leave a spent handle appearing ready.
|
|
167
|
-
if (this.lifecycle.getState().status === 'ready_to_submit')
|
|
168
|
-
this.lifecycle.preparationFailed(new CheckoutPreparationError(this.prepared.id, 'request_not_bound'));
|
|
169
|
-
}
|
|
170
|
-
/** A bound native request has its own cancellation/unknown-outcome machinery. */
|
|
171
|
-
invalidate(reason) {
|
|
172
|
-
if (this.state === 'unused' || this.state === 'consumed' || this.state === 'failed')
|
|
173
|
-
return;
|
|
174
|
-
this.state = 'failed';
|
|
175
|
-
clearTimeout(this.expiryTimer);
|
|
176
|
-
this.stop.abort();
|
|
177
|
-
if (this.prepared)
|
|
178
|
-
void this.cancelThenObserve(this.prepared.id, reason);
|
|
179
|
-
this.lifecycle.preparationFailed(new CheckoutPreparationError(this.prepared?.id ?? null, reason));
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Retire the server handle first, then report the form outcome. Neither
|
|
183
|
-
* call waits on the other: a stalled report cannot delay the cancel, and a
|
|
184
|
-
* stalled cancel cannot lose the report.
|
|
185
|
-
*/
|
|
186
|
-
async cancelThenObserve(id, reason) {
|
|
187
|
-
const cancellation = this.opts.vault.cancelPreparation(id).catch(() => { });
|
|
188
|
-
const observation = this.opts.vault.observePreparation?.(id, this.observedRequest ? 'presented_not_filled' : 'not_presented', reason);
|
|
189
|
-
await Promise.all([cancellation, observation?.catch(() => { })]);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv';
|
|
2
|
-
export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
|
|
3
|
-
/**
|
|
4
|
-
* How the approved card reaches the processor: the device's own request (token), or
|
|
5
|
-
* ciphertext the device produces for this browser to send (cse: Adyen's four encrypted
|
|
6
|
-
* fields, or Fiserv's Secure Data Capture envelope under a key Agentcard pinned).
|
|
7
|
-
*/
|
|
8
|
-
export type PreparationMode = 'token' | 'cse';
|
|
9
|
-
export declare function preparationMode(psp: string): PreparationMode;
|
|
10
|
-
/**
|
|
11
|
-
* Shared endpoint processors cannot attest test/live mode from their URL or key prefix.
|
|
12
|
-
* Adyen can: test and live are separate hosts and key prefixes. Fiserv's are separate
|
|
13
|
-
* hosts too: connect (production) and connect-cert (sandbox).
|
|
14
|
-
*/
|
|
15
|
-
export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
|
|
16
|
-
export declare function preparationEndpoint(psp: PreparationProcessor, environment: PreparationEnvironment): string;
|
|
17
|
-
/** Processor identity and environment are part of the device's prior consent. */
|
|
18
|
-
export declare function matchesPreparedRequest(psp: PreparationProcessor, environment: PreparationEnvironment, requestUrl: string, method: string, body?: string | null, headers?: Record<string, string>): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* The one request a merchant-hosted preparation pays: the reviewed profile's
|
|
21
|
-
* card request itself (classifyMerchantRequest pauses it), at the profile's exact
|
|
22
|
-
* endpoint, as its method, with a body the profile reviewed. A sibling endpoint, a
|
|
23
|
-
* stored card, a store-the-card body or any other shape never consumes consent.
|
|
24
|
-
*/
|
|
25
|
-
export declare function matchesPreparedMerchantRequest(profileId: string, requestUrl: string, method: string, body?: string | null, sandboxDeclaration?: {
|
|
26
|
-
endpoint: string;
|
|
27
|
-
}): boolean;
|
|
28
|
-
/**
|
|
29
|
-
* matchesPreparedRequest for a preparation, through its merchant profile (at its declared
|
|
30
|
-
* endpoint, when it names one) when it names one. A Fiserv preparation also names the key its
|
|
31
|
-
* cardholder approved a payment under (merchantProfile, a pin id): the capture's own pin,
|
|
32
|
-
* the one its envelope's key is pinned as for its endpoint and merchant, must be that
|
|
33
|
-
* pin, as the API's bind requires. Two pins in one environment (a merchant's old and new
|
|
34
|
-
* keys during a rotation) never spend each other's approval. No other processor names one.
|
|
35
|
-
*/
|
|
36
|
-
export declare function matchesPreparation(preparation: {
|
|
37
|
-
psp: PreparationProcessor;
|
|
38
|
-
environment: PreparationEnvironment;
|
|
39
|
-
merchantProfile?: string;
|
|
40
|
-
sandboxDeclaration?: {
|
|
41
|
-
endpoint: string;
|
|
42
|
-
};
|
|
43
|
-
}, requestUrl: string, method: string, body?: string | null, headers?: Record<string, string>): boolean;
|
|
@@ -1,172 +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
|
-
import { classifyDeclaredMerchantRequest, classifyMerchantRequest } from './merchant-hosted.js';
|
|
8
|
-
import { fiservCapturePinId, fiservPreparationUrl, isPreparedFiservRequest } from './fiserv.js';
|
|
9
|
-
export function preparationMode(psp) {
|
|
10
|
-
return psp === 'adyen' || psp === 'fiserv' ? 'cse' : 'token';
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Shared endpoint processors cannot attest test/live mode from their URL or key prefix.
|
|
14
|
-
* Adyen can: test and live are separate hosts and key prefixes. Fiserv's are separate
|
|
15
|
-
* hosts too: connect (production) and connect-cert (sandbox).
|
|
16
|
-
*/
|
|
17
|
-
export function validPreparationEnvironment(psp, environment) {
|
|
18
|
-
if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
|
|
19
|
-
return environment === 'shared';
|
|
20
|
-
return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe', 'fiserv'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
21
|
-
}
|
|
22
|
-
export function preparationEndpoint(psp, environment) {
|
|
23
|
-
if (!validPreparationEnvironment(psp, environment))
|
|
24
|
-
throw new Error('unsupported_preparation_processor');
|
|
25
|
-
if (psp === 'paysafe')
|
|
26
|
-
return paysafePreparationUrl(environment);
|
|
27
|
-
if (psp === 'fiserv')
|
|
28
|
-
return fiservPreparationUrl(environment);
|
|
29
|
-
if (psp === 'checkout_com')
|
|
30
|
-
return checkoutComPreparationUrl(environment);
|
|
31
|
-
if (psp === 'adyen')
|
|
32
|
-
return adyenPreparationUrl(environment);
|
|
33
|
-
if (psp === 'bambora')
|
|
34
|
-
return 'https://api.bam.shift4api.net/scripts/tokenization/tokens';
|
|
35
|
-
if (psp === 'mercado_pago')
|
|
36
|
-
return 'https://api.mercadopago.com/v1/card_tokens';
|
|
37
|
-
if (psp === 'recurly')
|
|
38
|
-
return 'https://api.recurly.com/js/v1/token';
|
|
39
|
-
if (psp === 'spreedly')
|
|
40
|
-
return SPREEDLY_TOKEN_ENDPOINT;
|
|
41
|
-
if (psp === 'worldpay')
|
|
42
|
-
return environment === 'production'
|
|
43
|
-
? 'https://access.worldpay.com/sessions/card' : 'https://try.access.worldpay.com/sessions/card';
|
|
44
|
-
if (psp === 'braintree')
|
|
45
|
-
return environment === 'production'
|
|
46
|
-
? 'https://payments.braintree-api.com/graphql' : 'https://payments.sandbox.braintree-api.com/graphql';
|
|
47
|
-
return environment === 'production'
|
|
48
|
-
? 'https://pci-connect.squareup.com/v2/card-nonce' : 'https://pci-connect.squareupsandbox.com/v2/card-nonce';
|
|
49
|
-
}
|
|
50
|
-
function record(value) {
|
|
51
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
52
|
-
}
|
|
53
|
-
const keys = (value, allowed) => Object.keys(value).every(key => allowed.includes(key));
|
|
54
|
-
const string = (value, max = 1024) => typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
55
|
-
const digits = (value, min, max) => typeof value === 'string' && value.length >= min && value.length <= max && !/[^0-9]/.test(value);
|
|
56
|
-
const month = (value) => (typeof value === 'number' && Number.isInteger(value) || digits(value, 1, 2)) && Number(value) >= 1 && Number(value) <= 12;
|
|
57
|
-
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);
|
|
58
|
-
/** Native fresh-card shapes only. Saved-card, charge and recurring siblings never consume consent. */
|
|
59
|
-
function freshCardBody(psp, body) {
|
|
60
|
-
const value = readTokenizationJson(body);
|
|
61
|
-
if (!value)
|
|
62
|
-
return false;
|
|
63
|
-
if (psp === 'worldpay')
|
|
64
|
-
return keys(value, ['identity', 'cardNumber', 'cardExpiryDate', 'cvc'])
|
|
65
|
-
&& string(value.identity) && digits(value.cardNumber, 12, 19)
|
|
66
|
-
&& record(value.cardExpiryDate) && keys(value.cardExpiryDate, ['month', 'year'])
|
|
67
|
-
&& month(value.cardExpiryDate.month) && year(value.cardExpiryDate.year)
|
|
68
|
-
&& (value.cvc === undefined || digits(value.cvc, 3, 4));
|
|
69
|
-
if (psp === 'bambora')
|
|
70
|
-
return keys(value, ['number', 'expiry_month', 'expiry_year', 'cvd'])
|
|
71
|
-
&& digits(value.number, 12, 19) && month(value.expiry_month) && year(value.expiry_year)
|
|
72
|
-
&& (value.cvd === undefined || digits(value.cvd, 3, 4));
|
|
73
|
-
if (psp !== 'mercado_pago' || !keys(value, ['card_number', 'expiration_month', 'expiration_year', 'security_code', 'cardholder', 'device'])
|
|
74
|
-
|| !digits(value.card_number, 12, 19) || !month(value.expiration_month) || !year(value.expiration_year)
|
|
75
|
-
|| (value.security_code !== undefined && value.security_code !== '' && !digits(value.security_code, 3, 4)) || !record(value.cardholder)
|
|
76
|
-
|| !keys(value.cardholder, ['name', 'identification']))
|
|
77
|
-
return false;
|
|
78
|
-
const holder = value.cardholder;
|
|
79
|
-
if (holder.name !== undefined && holder.name !== '' && !string(holder.name))
|
|
80
|
-
return false;
|
|
81
|
-
if (holder.identification !== undefined && (!record(holder.identification) || !keys(holder.identification, ['type', 'number'])
|
|
82
|
-
|| Object.values(holder.identification).some(v => v !== '' && !string(v))))
|
|
83
|
-
return false;
|
|
84
|
-
if (value.device !== undefined && (!record(value.device) || !keys(value.device, ['meli'])
|
|
85
|
-
|| !record(value.device.meli) || !keys(value.device.meli, ['session_id']) || !string(value.device.meli.session_id, 4096)))
|
|
86
|
-
return false;
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
/** Processor identity and environment are part of the device's prior consent. */
|
|
90
|
-
export function matchesPreparedRequest(psp, environment, requestUrl, method, body, headers) {
|
|
91
|
-
if (method.toUpperCase() !== 'POST' || !validPreparationEnvironment(psp, environment))
|
|
92
|
-
return false;
|
|
93
|
-
if (psp === 'paysafe')
|
|
94
|
-
return isPreparedPaysafeRequest(requestUrl, method, body ?? null, environment, headers);
|
|
95
|
-
// Fiserv: that environment's card-capture endpoint exactly, the capture rules on the whole
|
|
96
|
-
// request (missing headers refuse), and an envelope under a key pinned for that environment
|
|
97
|
-
// and for the merchant the body names (payment-core isPreparedFiservRequest).
|
|
98
|
-
if (psp === 'fiserv')
|
|
99
|
-
return isPreparedFiservRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers);
|
|
100
|
-
if (psp === 'checkout_com')
|
|
101
|
-
return isPreparedCheckoutComRequest(requestUrl, method, body ?? null, environment, headers);
|
|
102
|
-
if (psp === 'adyen')
|
|
103
|
-
return isPreparedAdyenRequest(requestUrl, method, body ?? null, environment);
|
|
104
|
-
if (psp === 'braintree')
|
|
105
|
-
return braintreeEnvironment(requestUrl) === environment && isPreparedBraintreeRequest(body ?? null);
|
|
106
|
-
if (psp === 'spreedly')
|
|
107
|
-
return isSpreedlyTokenRequest(requestUrl, method) && isPreparedSpreedlyRequest(body ?? null);
|
|
108
|
-
try {
|
|
109
|
-
const request = new URL(requestUrl), endpoint = new URL(preparationEndpoint(psp, environment));
|
|
110
|
-
if (request.username || request.password || request.hash)
|
|
111
|
-
return false;
|
|
112
|
-
if (psp === 'recurly')
|
|
113
|
-
return [endpoint.href, 'https://api.eu.recurly.com/js/v1/token'].includes(requestUrl)
|
|
114
|
-
&& isPreparedRecurlyRequest(body ?? null);
|
|
115
|
-
if (psp === 'bambora') {
|
|
116
|
-
if (![endpoint.href, 'https://api.na.bambora.com/scripts/tokenization/tokens'].includes(requestUrl))
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
else if (psp === 'worldpay') {
|
|
120
|
-
if (requestUrl !== endpoint.href)
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
else if (request.origin !== endpoint.origin || request.pathname !== endpoint.pathname)
|
|
124
|
-
return false;
|
|
125
|
-
if (psp === 'mercado_pago') {
|
|
126
|
-
const names = [...request.searchParams.keys()];
|
|
127
|
-
if (new Set(names).size !== names.length || names.some(name => !['public_key', 'locale', 'js_version', 'referer'].includes(name)))
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
return psp === 'square' || freshCardBody(psp, body ?? null);
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* The one request a merchant-hosted preparation pays: the reviewed profile's
|
|
138
|
-
* card request itself (classifyMerchantRequest pauses it), at the profile's exact
|
|
139
|
-
* endpoint, as its method, with a body the profile reviewed. A sibling endpoint, a
|
|
140
|
-
* stored card, a store-the-card body or any other shape never consumes consent.
|
|
141
|
-
*/
|
|
142
|
-
export function matchesPreparedMerchantRequest(profileId, requestUrl, method, body, sandboxDeclaration) {
|
|
143
|
-
try {
|
|
144
|
-
const verdict = sandboxDeclaration
|
|
145
|
-
? classifyDeclaredMerchantRequest(profileId, sandboxDeclaration.endpoint, requestUrl, method, body ?? '')
|
|
146
|
-
: classifyMerchantRequest(profileId, requestUrl, method, body ?? '');
|
|
147
|
-
return verdict.verdict === 'pause';
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* matchesPreparedRequest for a preparation, through its merchant profile (at its declared
|
|
155
|
-
* endpoint, when it names one) when it names one. A Fiserv preparation also names the key its
|
|
156
|
-
* cardholder approved a payment under (merchantProfile, a pin id): the capture's own pin,
|
|
157
|
-
* the one its envelope's key is pinned as for its endpoint and merchant, must be that
|
|
158
|
-
* pin, as the API's bind requires. Two pins in one environment (a merchant's old and new
|
|
159
|
-
* keys during a rotation) never spend each other's approval. No other processor names one.
|
|
160
|
-
*/
|
|
161
|
-
export function matchesPreparation(preparation, requestUrl, method, body, headers) {
|
|
162
|
-
if (preparation.psp === 'fiserv') {
|
|
163
|
-
if (!matchesPreparedRequest(preparation.psp, preparation.environment, requestUrl, method, body, headers))
|
|
164
|
-
return false;
|
|
165
|
-
return typeof preparation.merchantProfile === 'string' && fiservCapturePinId(requestUrl, body ?? null) === preparation.merchantProfile;
|
|
166
|
-
}
|
|
167
|
-
if (preparation.merchantProfile !== undefined) {
|
|
168
|
-
return preparation.psp === 'adyen'
|
|
169
|
-
&& matchesPreparedMerchantRequest(preparation.merchantProfile, requestUrl, method, body, preparation.sandboxDeclaration);
|
|
170
|
-
}
|
|
171
|
-
return matchesPreparedRequest(preparation.psp, preparation.environment, requestUrl, method, body, headers);
|
|
172
|
-
}
|
|
@@ -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
|
-
}
|