@agent-cards/checkout 0.19.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.
Files changed (106) hide show
  1. package/README.md +6 -753
  2. package/cdp.d.ts +1 -0
  3. package/cdp.js +2 -0
  4. package/index.d.ts +1 -0
  5. package/index.js +2 -0
  6. package/package.json +33 -33
  7. package/playwright.d.ts +1 -0
  8. package/playwright.js +2 -0
  9. package/preflight.d.ts +1 -0
  10. package/preflight.js +2 -0
  11. package/CHANGELOG.md +0 -132
  12. package/PREFLIGHT.md +0 -312
  13. package/dist/adyen-merchant-hosted.generated.d.ts +0 -277
  14. package/dist/adyen-merchant-hosted.generated.js +0 -1902
  15. package/dist/adyen.generated.d.ts +0 -24
  16. package/dist/adyen.generated.js +0 -64
  17. package/dist/attachment.d.ts +0 -11
  18. package/dist/attachment.js +0 -50
  19. package/dist/braintree.d.ts +0 -2
  20. package/dist/braintree.generated.d.ts +0 -10
  21. package/dist/braintree.generated.js +0 -302
  22. package/dist/braintree.js +0 -2
  23. package/dist/builtin-registry.generated.d.ts +0 -2
  24. package/dist/builtin-registry.generated.js +0 -1
  25. package/dist/card-fields.generated.d.ts +0 -3
  26. package/dist/card-fields.generated.js +0 -46
  27. package/dist/cdp.d.ts +0 -192
  28. package/dist/cdp.js +0 -2393
  29. package/dist/checkout-com.generated.d.ts +0 -4
  30. package/dist/checkout-com.generated.js +0 -183
  31. package/dist/client.d.ts +0 -849
  32. package/dist/client.js +0 -1754
  33. package/dist/cse-body.d.ts +0 -25
  34. package/dist/cse-body.js +0 -41
  35. package/dist/fiserv.d.ts +0 -65
  36. package/dist/fiserv.generated.d.ts +0 -73
  37. package/dist/fiserv.generated.js +0 -830
  38. package/dist/fiserv.js +0 -104
  39. package/dist/hosted-form.d.ts +0 -44
  40. package/dist/hosted-form.js +0 -78
  41. package/dist/index.d.ts +0 -18
  42. package/dist/index.js +0 -10
  43. package/dist/lifecycle.d.ts +0 -179
  44. package/dist/lifecycle.js +0 -395
  45. package/dist/mercado-checkout.d.ts +0 -20
  46. package/dist/mercado-checkout.generated.d.ts +0 -52
  47. package/dist/mercado-checkout.generated.js +0 -198
  48. package/dist/mercado-checkout.js +0 -108
  49. package/dist/merchant-handoff.d.ts +0 -54
  50. package/dist/merchant-handoff.js +0 -100
  51. package/dist/merchant-hosted.d.ts +0 -140
  52. package/dist/merchant-hosted.js +0 -170
  53. package/dist/merchant-total-watch.d.ts +0 -115
  54. package/dist/merchant-total-watch.js +0 -268
  55. package/dist/merchant-total.d.ts +0 -257
  56. package/dist/merchant-total.js +0 -383
  57. package/dist/owned-shop.generated.d.ts +0 -24
  58. package/dist/owned-shop.generated.js +0 -108
  59. package/dist/paysafe.generated.d.ts +0 -12
  60. package/dist/paysafe.generated.js +0 -87
  61. package/dist/playwright.d.ts +0 -3
  62. package/dist/playwright.js +0 -3
  63. package/dist/pre-claim.d.ts +0 -123
  64. package/dist/pre-claim.js +0 -386
  65. package/dist/preflight-capabilities.generated.d.ts +0 -1253
  66. package/dist/preflight-capabilities.generated.js +0 -1929
  67. package/dist/preflight-catalog.json +0 -4727
  68. package/dist/preflight-playwright.d.ts +0 -34
  69. package/dist/preflight-playwright.js +0 -355
  70. package/dist/preflight-schemas.json +0 -1122
  71. package/dist/preflight.d.ts +0 -1
  72. package/dist/preflight.generated.d.ts +0 -1965
  73. package/dist/preflight.generated.js +0 -570
  74. package/dist/preflight.js +0 -2
  75. package/dist/preparation.d.ts +0 -38
  76. package/dist/preparation.js +0 -191
  77. package/dist/prepared-processor.d.ts +0 -43
  78. package/dist/prepared-processor.js +0 -172
  79. package/dist/recurly.generated.d.ts +0 -1
  80. package/dist/recurly.generated.js +0 -87
  81. package/dist/registry.d.ts +0 -121
  82. package/dist/registry.js +0 -310
  83. package/dist/spreedly.generated.d.ts +0 -10
  84. package/dist/spreedly.generated.js +0 -332
  85. package/dist/stripe-checkout.d.ts +0 -81
  86. package/dist/stripe-checkout.generated.d.ts +0 -82
  87. package/dist/stripe-checkout.generated.js +0 -1093
  88. package/dist/stripe-checkout.js +0 -140
  89. package/dist/substitute.d.ts +0 -38
  90. package/dist/substitute.js +0 -23
  91. package/dist/substitutions.generated.d.ts +0 -11
  92. package/dist/substitutions.generated.js +0 -818
  93. package/examples/existing-browser.mjs +0 -63
  94. package/examples/preflight/classify-direct.mjs +0 -21
  95. package/examples/preflight/classify-kernel.mjs +0 -30
  96. package/examples/preflight/inspect-browser.mjs +0 -44
  97. package/examples/preflight/kernel-native/README.md +0 -112
  98. package/examples/preflight/kernel-native/documented-adapters.json +0 -113
  99. package/examples/preflight/kernel-native/inventory.json +0 -233
  100. package/examples/preflight/kernel-native/qualification.mjs +0 -182
  101. package/examples/preflight/kernel-profile.empty.json +0 -11
  102. package/examples/preflight/mollie-hosted.observations.json +0 -23
  103. package/examples/preflight/mollie-hosted.result.json +0 -103
  104. package/examples/preflight/stripe-script.direct.result.json +0 -92
  105. package/examples/preflight/stripe-script.observations.json +0 -16
  106. package/examples/preflight/stripe-script.result.json +0 -87
package/dist/preflight.js DELETED
@@ -1,2 +0,0 @@
1
- // Read-only discovery is separate from request interception and authorization.
2
- export * from './preflight.generated.js';
@@ -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
- }
@@ -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
- }