@agent-cards/sdk 0.22.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -8
- package/PREFLIGHT.md +3 -1
- package/README.md +120 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +2 -1
- package/dist/adyen-merchant-hosted.generated.js +51 -39
- package/dist/card-endpoint-facts.d.ts +96 -0
- package/dist/card-endpoint-facts.js +275 -0
- package/dist/card-endpoint-page.d.ts +54 -0
- package/dist/card-endpoint-page.js +69 -0
- package/dist/card-endpoint.d.ts +132 -11
- package/dist/card-endpoint.js +169 -17
- package/dist/cdp.js +336 -28
- package/dist/client.d.ts +61 -9
- package/dist/client.js +133 -16
- package/dist/creditguard.generated.d.ts +1 -1
- package/dist/cybersource-flex.d.ts +30 -0
- package/dist/cybersource-flex.generated.d.ts +27 -1
- package/dist/cybersource-flex.generated.js +42 -2
- package/dist/cybersource-flex.js +46 -0
- package/dist/ekashu.d.ts +35 -0
- package/dist/ekashu.js +55 -0
- package/dist/lifecycle.d.ts +7 -4
- package/dist/lifecycle.js +6 -3
- package/dist/merchant-card-endpoints.generated.d.ts +60 -1
- package/dist/merchant-card-endpoints.generated.js +3264 -95
- package/dist/pre-claim.d.ts +9 -0
- package/dist/pre-claim.js +179 -13
- package/dist/preflight-catalog.json +48 -0
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.js +11 -1
- package/dist/preparation.d.ts +7 -1
- package/dist/preparation.js +27 -2
- package/dist/stripe-checkout.generated.js +66 -1
- package/dist/verifone.generated.js +20 -8
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ import { VoiceModule, type VoiceOptions } from './voice.js';
|
|
|
9
9
|
import { BuyModule } from './buy.js';
|
|
10
10
|
import { RequestRegistry } from './requests.js';
|
|
11
11
|
import type { ApiTransport } from './api-module.js';
|
|
12
|
-
import { type MicroformSidecar } from './cybersource-flex.js';
|
|
12
|
+
import { type MicroformField, type MicroformSidecar } from './cybersource-flex.js';
|
|
13
13
|
import { type CardEndpointSession } from './card-endpoint.js';
|
|
14
|
+
import type { CardEndpointFactsRecord } from './card-endpoint-facts.js';
|
|
14
15
|
import { type PayrailsSidecar } from './payrails.js';
|
|
15
16
|
export interface PausedRequest {
|
|
16
17
|
url: string;
|
|
@@ -29,12 +30,15 @@ export interface PausedRequest {
|
|
|
29
30
|
microform?: MicroformSidecar;
|
|
30
31
|
/**
|
|
31
32
|
* A reviewed merchant's own card endpoint (see card-endpoint.ts): the profile and the
|
|
32
|
-
* paused request's session, which the adapters capture
|
|
33
|
-
*
|
|
33
|
+
* paused request's session, which the adapters capture, and, for a merchant that keeps
|
|
34
|
+
* its own record of the order, that record as the adapters read it from the page, cut
|
|
35
|
+
* to the values payment-core's reader reads (card-endpoint-facts.ts). Cardholder data:
|
|
36
|
+
* it travels only in the create's `merchant_card_endpoint` member, never in `request`.
|
|
34
37
|
*/
|
|
35
38
|
merchant_card_endpoint?: {
|
|
36
39
|
profile: string;
|
|
37
40
|
session: CardEndpointSession;
|
|
41
|
+
facts?: CardEndpointFactsRecord;
|
|
38
42
|
};
|
|
39
43
|
/**
|
|
40
44
|
* A Payrails card save (mode device_encrypt) only: the session key the page's
|
|
@@ -169,6 +173,9 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
|
|
|
169
173
|
* verifone.js blob sent to the merchant's server), which this build pauses and pays
|
|
170
174
|
* only through a prepared checkout (verifone.ts); the API serves the Verifone
|
|
171
175
|
* profiles only to a build that lists it, for a company Agentcard turned Verifone on for.
|
|
176
|
+
* `ekashu_hosted_payment_page`: NMI's hosted payment page, whose card-free entry this
|
|
177
|
+
* build pauses for the cardholder's device and whose card page this build never lets
|
|
178
|
+
* the agent's browser post to (pre-claim.ts, ekashu.ts); served on the same terms.
|
|
172
179
|
*/
|
|
173
180
|
export declare const SUPPORTED_CAPABILITIES: readonly string[];
|
|
174
181
|
/**
|
|
@@ -364,11 +371,12 @@ export interface HostedFormReplay extends ExecutionMetadata {
|
|
|
364
371
|
* `setCookies` written into the agent's browser on the endpoint host, or on the parent
|
|
365
372
|
* domain of a cookie the browser already holds there, which the write replaces.
|
|
366
373
|
*
|
|
367
|
-
* `withheld` is set when the device found the card in the answer,
|
|
368
|
-
* stream it cannot read
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
* mode: the agent's amount is the only
|
|
374
|
+
* `withheld` is set when the device found the card in the answer, a compressed
|
|
375
|
+
* stream it cannot read, or a step the review refuses (FastSpring's EBANX step): nothing
|
|
376
|
+
* of the answer travels, the adapters answer the paused request with a fixed error, and
|
|
377
|
+
* the outcome is unknown until the merchant order is checked. The card request was sent
|
|
378
|
+
* either way. Amounts are not reconciled on this mode: the agent's amount is the only
|
|
379
|
+
* authority, held before the create to the merchant's own total where its page keeps one.
|
|
372
380
|
*/
|
|
373
381
|
export interface CardEndpointReplay extends ExecutionMetadata {
|
|
374
382
|
mode: 'card_endpoint';
|
|
@@ -380,7 +388,7 @@ export interface CardEndpointReplay extends ExecutionMetadata {
|
|
|
380
388
|
headers: Record<string, string>;
|
|
381
389
|
setCookies: string[];
|
|
382
390
|
body: string;
|
|
383
|
-
withheld: 'card_in_response' | 'unreadable_in_response' | null;
|
|
391
|
+
withheld: 'card_in_response' | 'unreadable_in_response' | 'refused_step_in_response' | null;
|
|
384
392
|
amountAuthority?: AmountAuthority;
|
|
385
393
|
}
|
|
386
394
|
/**
|
|
@@ -448,8 +456,25 @@ export interface PrepareCheckoutOptions {
|
|
|
448
456
|
* processor.
|
|
449
457
|
*/
|
|
450
458
|
paypageId?: string;
|
|
459
|
+
/**
|
|
460
|
+
* CyberSource Flex only: the cardholder's device tokenizes the card when it approves, before
|
|
461
|
+
* Pay, under the capture context of the card fields the page already loaded, so the page's
|
|
462
|
+
* createToken is answered at once. For a page that gives createToken only seconds (Alaska
|
|
463
|
+
* Airlines' flight checkout gives it three). Call prepare() once the card fields show; the
|
|
464
|
+
* attachment reads their capture context and fields and refuses with `microform_not_loaded`,
|
|
465
|
+
* `microform_ambiguous` or `microform_unsupported` when it cannot.
|
|
466
|
+
*/
|
|
467
|
+
tokenizeBeforePay?: boolean;
|
|
451
468
|
signal?: AbortSignal;
|
|
452
469
|
}
|
|
470
|
+
/** A CyberSource Flex preparation that tokenizes before Pay: the loaded card field's capture context and fields. */
|
|
471
|
+
export interface PreparedCybersourceFlex {
|
|
472
|
+
captureContext: string;
|
|
473
|
+
microform: {
|
|
474
|
+
version: string;
|
|
475
|
+
fields: MicroformField[];
|
|
476
|
+
};
|
|
477
|
+
}
|
|
453
478
|
export interface PrepareCheckoutInput extends PrepareCheckoutOptions {
|
|
454
479
|
user: string;
|
|
455
480
|
merchant: string;
|
|
@@ -462,6 +487,12 @@ export interface PrepareCheckoutInput extends PrepareCheckoutOptions {
|
|
|
462
487
|
timeoutMs?: number;
|
|
463
488
|
onPreparationCreated?: (id: string) => void;
|
|
464
489
|
onApprovalUrl?: (url: string) => void;
|
|
490
|
+
/**
|
|
491
|
+
* CyberSource Flex, tokenized before Pay: the capture context and fields of the card Microform
|
|
492
|
+
* the page loaded (the attachments read them for prepare({ tokenizeBeforePay: true })). Verified
|
|
493
|
+
* here and by the API before any preparation exists.
|
|
494
|
+
*/
|
|
495
|
+
cybersourceFlex?: PreparedCybersourceFlex;
|
|
465
496
|
}
|
|
466
497
|
/** Real user consent and an unlocked device; no processor request or payment yet. */
|
|
467
498
|
export interface PreparedCheckout {
|
|
@@ -494,6 +525,18 @@ export interface PreparedCheckout {
|
|
|
494
525
|
readonly paymentPageUrl?: string;
|
|
495
526
|
/** Worldpay eProtect only: the paypageId the API confirmed, the one merchant whose registration may spend this approval. */
|
|
496
527
|
readonly paypageId?: string;
|
|
528
|
+
/**
|
|
529
|
+
* CyberSource Flex, tokenized before Pay: the capture context and fields the cardholder's device
|
|
530
|
+
* tokenized the card under. Only a field request loaded under this context, with these fields,
|
|
531
|
+
* binds this preparation.
|
|
532
|
+
*/
|
|
533
|
+
readonly cybersourceFlex?: Readonly<{
|
|
534
|
+
captureContext: string;
|
|
535
|
+
microform: Readonly<{
|
|
536
|
+
version: string;
|
|
537
|
+
fields: readonly MicroformField[];
|
|
538
|
+
}>;
|
|
539
|
+
}>;
|
|
497
540
|
}
|
|
498
541
|
export declare class CheckoutPreparationError extends Error {
|
|
499
542
|
preparationId: string | null;
|
|
@@ -703,6 +746,15 @@ export declare const RUNTIME_CANCEL_REASONS: readonly RuntimeCancelReason[];
|
|
|
703
746
|
export declare class PaymentOutcomeUnknownError extends Error {
|
|
704
747
|
authorizationId: string | null;
|
|
705
748
|
reason: string;
|
|
749
|
+
/**
|
|
750
|
+
* False when the API confirmed it retired this authorization before any processor
|
|
751
|
+
* request started: authorize() cancels an authorization it could not finish (a card
|
|
752
|
+
* endpoint, a prepared or a native checkout, or a request the page abandoned), and the
|
|
753
|
+
* cancel came back with `processor_request_started: false`, so nothing reached the
|
|
754
|
+
* processor or the merchant. Null when that is not known. The controller still reads
|
|
755
|
+
* `outcome_unknown`; a card form navigation's page reads this to say nothing was sent.
|
|
756
|
+
*/
|
|
757
|
+
processorRequestStarted: false | null;
|
|
706
758
|
constructor(authorizationId: string | null, reason: string);
|
|
707
759
|
}
|
|
708
760
|
export declare class ApprovalDeclinedError extends Error {
|
package/dist/client.js
CHANGED
|
@@ -21,10 +21,11 @@ import { Languages } from './languages.js';
|
|
|
21
21
|
import { RequestRegistry } from './requests.js';
|
|
22
22
|
import { AgentcardError } from './voice-types.js';
|
|
23
23
|
import { GMO_CAPABILITY, GMO_PSP, gmoTokenAnswer } from './gmo.js';
|
|
24
|
-
import { isCybersourceFlexTokenShape } from './cybersource-flex.generated.js';
|
|
25
|
-
import { CYBERSOURCE_FLEX_CAPABILITY, CYBERSOURCE_FLEX_PSP, validCybersourceFlexSidecar } from './cybersource-flex.js';
|
|
24
|
+
import { admitCybersourceFlexPreparation, isCybersourceFlexTokenShape, sameCybersourceFlexPreparation } from './cybersource-flex.generated.js';
|
|
25
|
+
import { CYBERSOURCE_FLEX_CAPABILITY, CYBERSOURCE_FLEX_PSP, validCybersourceFlexSidecar, } from './cybersource-flex.js';
|
|
26
26
|
import { JUSPAY_CAPABILITY, JUSPAY_PSP, juspayPageAnswer } from './juspay-gate.js';
|
|
27
27
|
import { CARD_ENDPOINT_MODE, CARD_ENDPOINT_PSP, CardEndpointRefusedError, cardEndpointById, cardEndpointLive, cardEndpointSessionRefusal, } from './card-endpoint.js';
|
|
28
|
+
import { merchantCardEndpointFactsRefusal as sharedFactsRefusal, readMerchantCardEndpointFacts as sharedReadFacts, } from './merchant-card-endpoints.generated.js';
|
|
28
29
|
import { DATATRANS_CAPABILITY } from './datatrans.js';
|
|
29
30
|
import { PELECARD_CAPABILITY, PELECARD_PSP } from './pelecard.js';
|
|
30
31
|
import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
|
|
@@ -154,8 +155,11 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
154
155
|
* verifone.js blob sent to the merchant's server), which this build pauses and pays
|
|
155
156
|
* only through a prepared checkout (verifone.ts); the API serves the Verifone
|
|
156
157
|
* profiles only to a build that lists it, for a company Agentcard turned Verifone on for.
|
|
158
|
+
* `ekashu_hosted_payment_page`: NMI's hosted payment page, whose card-free entry this
|
|
159
|
+
* build pauses for the cardholder's device and whose card page this build never lets
|
|
160
|
+
* the agent's browser post to (pre-claim.ts, ekashu.ts); served on the same terms.
|
|
157
161
|
*/
|
|
158
|
-
export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture'];
|
|
162
|
+
export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page'];
|
|
159
163
|
/**
|
|
160
164
|
* Processors whose paused request names no amount at all, so the pair the
|
|
161
165
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -436,6 +440,15 @@ export const RUNTIME_CANCEL_REASONS = ['merchant_request_aborted', 'merchant_nev
|
|
|
436
440
|
export class PaymentOutcomeUnknownError extends Error {
|
|
437
441
|
authorizationId;
|
|
438
442
|
reason;
|
|
443
|
+
/**
|
|
444
|
+
* False when the API confirmed it retired this authorization before any processor
|
|
445
|
+
* request started: authorize() cancels an authorization it could not finish (a card
|
|
446
|
+
* endpoint, a prepared or a native checkout, or a request the page abandoned), and the
|
|
447
|
+
* cancel came back with `processor_request_started: false`, so nothing reached the
|
|
448
|
+
* processor or the merchant. Null when that is not known. The controller still reads
|
|
449
|
+
* `outcome_unknown`; a card form navigation's page reads this to say nothing was sent.
|
|
450
|
+
*/
|
|
451
|
+
processorRequestStarted = null;
|
|
439
452
|
constructor(authorizationId, reason) {
|
|
440
453
|
super(`payment outcome unknown${authorizationId ? ` for ${authorizationId}` : ''}: ${reason}; check the merchant order before retrying`);
|
|
441
454
|
this.authorizationId = authorizationId;
|
|
@@ -919,6 +932,26 @@ const FISERV_PREPARATION_REFUSALS = [
|
|
|
919
932
|
* holds that pin). Only for an eProtect checkout.
|
|
920
933
|
*/
|
|
921
934
|
const EPROTECT_PREPARATION_REFUSALS = ['merchant_profile_mismatch'];
|
|
935
|
+
/**
|
|
936
|
+
* Refusals a CyberSource Flex preparation that tokenizes before Pay answers (4xx) before any
|
|
937
|
+
* preparation exists, surfaced as the CheckoutPreparationError reason: the processor is off for
|
|
938
|
+
* this company (`processor_unavailable`), the environment is not the client's mode, or the capture
|
|
939
|
+
* context or fields read from the page cannot be verified or rebuilt. Only for such a preparation;
|
|
940
|
+
* a Flex preparation without a capture context keeps `preparation_unconfirmed`, as before.
|
|
941
|
+
*/
|
|
942
|
+
const CYBERSOURCE_FLEX_PREPARATION_REFUSALS = [
|
|
943
|
+
'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'capture_context_invalid', 'microform_unsupported',
|
|
944
|
+
'cybersource_environment_mismatch',
|
|
945
|
+
];
|
|
946
|
+
/** Why payment-core refused a Flex preparation's capture context or fields, as a CheckoutPreparationError reason. */
|
|
947
|
+
function cybersourceFlexPreparationRefusal(error) {
|
|
948
|
+
const reason = error?.reason;
|
|
949
|
+
if (reason === 'microform_version' || reason === 'fields')
|
|
950
|
+
return 'microform_unsupported';
|
|
951
|
+
if (reason === 'environment_mismatch')
|
|
952
|
+
return 'cybersource_environment_mismatch';
|
|
953
|
+
return 'capture_context_invalid';
|
|
954
|
+
}
|
|
922
955
|
/** Default backoff for a 502 amount_unverifiable at create: two retries, then give up. */
|
|
923
956
|
const UNVERIFIABLE_RETRY_DELAYS_MS = [500, 1500];
|
|
924
957
|
const PREPARATION_READ_RETRY_DELAYS_MS = [250, 500];
|
|
@@ -1370,6 +1403,25 @@ export class VaultClient {
|
|
|
1370
1403
|
throw fail('paypage_id_invalid');
|
|
1371
1404
|
if (eprotect && !input.cardId)
|
|
1372
1405
|
throw fail('card_required');
|
|
1406
|
+
// CyberSource Flex, tokenized before Pay: the loaded card field's capture context and fields,
|
|
1407
|
+
// verified here against the keys payment-core pins before anything is created (the API and the
|
|
1408
|
+
// cardholder's device verify them again).
|
|
1409
|
+
if (input.tokenizeBeforePay !== undefined && typeof input.tokenizeBeforePay !== 'boolean')
|
|
1410
|
+
throw fail('tokenize_before_pay_invalid');
|
|
1411
|
+
const flexContext = input.cybersourceFlex;
|
|
1412
|
+
if (input.tokenizeBeforePay === true && !flexContext)
|
|
1413
|
+
throw fail('microform_not_loaded');
|
|
1414
|
+
if (flexContext) {
|
|
1415
|
+
if (input.psp !== CYBERSOURCE_FLEX_PSP)
|
|
1416
|
+
throw fail('unsupported_processor');
|
|
1417
|
+
try {
|
|
1418
|
+
await admitCybersourceFlexPreparation({ capture_context: flexContext.captureContext,
|
|
1419
|
+
microform: { version: flexContext.microform.version, fields: [...flexContext.microform.fields] }, environment: input.environment }, { crypto: globalThis.crypto });
|
|
1420
|
+
}
|
|
1421
|
+
catch (error) {
|
|
1422
|
+
throw fail(cybersourceFlexPreparationRefusal(error));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1373
1425
|
if (!validAmountInput(input.amount) || typeof input.currency !== 'string' || !/^[a-z]{3}$/i.test(input.currency)) {
|
|
1374
1426
|
throw new CheckoutPreparationError(null, 'amount_required', undefined, input.amount != null && !validAmountInput(input.amount) ? amountRefusal(input.amount, input.currency) : undefined);
|
|
1375
1427
|
}
|
|
@@ -1417,6 +1469,8 @@ export class VaultClient {
|
|
|
1417
1469
|
...(declaration ? { sandbox_declaration: { endpoint: declaration.endpoint, client_key: declaration.clientKey, environment: 'test' } } : {}),
|
|
1418
1470
|
...(input.psp === 'creditguard' ? { payment_page_url: input.paymentPageUrl } : {}),
|
|
1419
1471
|
...(eprotect ? { paypage_id: input.paypageId } : {}),
|
|
1472
|
+
...(flexContext ? { capture_context: flexContext.captureContext,
|
|
1473
|
+
microform: { version: flexContext.microform.version, fields: [...flexContext.microform.fields] } } : {}),
|
|
1420
1474
|
}, AbortSignal.timeout(30_000), signal);
|
|
1421
1475
|
if (!created || typeof created.id !== 'string' || !/^cprep_[A-Za-z0-9_-]{1,128}$/.test(created.id))
|
|
1422
1476
|
throw fail('create_unconfirmed');
|
|
@@ -1456,7 +1510,9 @@ export class VaultClient {
|
|
|
1456
1510
|
|| state.psp !== input.psp || state.mode !== preparationMode(input.psp) || state.environment !== input.environment
|
|
1457
1511
|
|| state.checkout_key !== input.checkoutKey
|
|
1458
1512
|
// eProtect: the merchant and the card the cardholder approved are the ones asked for.
|
|
1459
|
-
|| (eprotect && (state.paypage_id !== input.paypageId || state.card_id !== input.cardId))
|
|
1513
|
+
|| (eprotect && (state.paypage_id !== input.paypageId || state.card_id !== input.cardId))
|
|
1514
|
+
// The API says a preparation tokenizes before Pay exactly when this one carried a capture context.
|
|
1515
|
+
|| (state.tokenize_before_pay === true) !== !!flexContext)
|
|
1460
1516
|
throw fail('ready_unconfirmed', id);
|
|
1461
1517
|
const prepared = Object.freeze({
|
|
1462
1518
|
id: preparationId, status: 'ready', psp: input.psp, environment: input.environment, mode: preparationMode(input.psp), expiresAt: state.ready_expires_at,
|
|
@@ -1470,6 +1526,8 @@ export class VaultClient {
|
|
|
1470
1526
|
...(declaration ? { sandboxDeclaration: declaration } : {}),
|
|
1471
1527
|
...(input.psp === 'creditguard' ? { paymentPageUrl: input.paymentPageUrl } : {}),
|
|
1472
1528
|
...(eprotect ? { paypageId: input.paypageId } : {}),
|
|
1529
|
+
...(flexContext ? { cybersourceFlex: Object.freeze({ captureContext: flexContext.captureContext,
|
|
1530
|
+
microform: Object.freeze({ version: flexContext.microform.version, fields: Object.freeze([...flexContext.microform.fields]) }) }) } : {}),
|
|
1473
1531
|
});
|
|
1474
1532
|
this.preparations.add(prepared);
|
|
1475
1533
|
ready = true;
|
|
@@ -1494,7 +1552,8 @@ export class VaultClient {
|
|
|
1494
1552
|
if (!id && !signal.aborted && error instanceof CheckoutApiError && error.code
|
|
1495
1553
|
&& ((error.status >= 400 && error.status < 500 && (PREPARATION_REFUSALS.includes(error.code)
|
|
1496
1554
|
|| (input.psp === 'fiserv' && FISERV_PREPARATION_REFUSALS.includes(error.code))
|
|
1497
|
-
|| (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
|
|
1555
|
+
|| (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
|
|
1556
|
+
|| (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
|
|
1498
1557
|
|| (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
|
|
1499
1558
|
throw fail(error.code);
|
|
1500
1559
|
throw fail(signal.aborted ? 'cancelled' : 'preparation_unconfirmed', id);
|
|
@@ -1717,6 +1776,11 @@ export class VaultClient {
|
|
|
1717
1776
|
if (mode === 'device_encrypt' && (!sidecar || typeof sidecar.encryption_public_key !== 'string' || !sidecar.encryption_public_key)) {
|
|
1718
1777
|
throw new Error('A Payrails card save needs request.payrails.encryption_public_key, the session key from the checkout\'s Payrails init payload (the adapters capture it). Nothing was created.');
|
|
1719
1778
|
}
|
|
1779
|
+
// A preparation that tokenized before Pay pays only a field request loaded under the capture
|
|
1780
|
+
// context and fields its cardholder approved: the device holds CyberSource's token for that context.
|
|
1781
|
+
if (preparation?.cybersourceFlex && !(flex && sameCybersourceFlexPreparation({ capture_context: preparation.cybersourceFlex.captureContext, microform: preparation.cybersourceFlex.microform }, { capture_context: input.request.capture_context, microform: input.request.microform }))) {
|
|
1782
|
+
throw new CheckoutPreparationError(preparation.id, 'checkout_changed');
|
|
1783
|
+
}
|
|
1720
1784
|
// Say no rather than coerce. Each recognizer's card request has exactly one
|
|
1721
1785
|
// method (recognizerRequestMethod): a POST body, or for Worldpay eProtect the
|
|
1722
1786
|
// GET whose query carries the card. The service stores the replay template
|
|
@@ -1791,7 +1855,7 @@ export class VaultClient {
|
|
|
1791
1855
|
execution_mode: 'user_approval',
|
|
1792
1856
|
checkout_origin: cardEndpoint.session.page_origin,
|
|
1793
1857
|
request: { url: cardEndpoint.session.url, method: 'POST', headers: {}, body: '' },
|
|
1794
|
-
merchant_card_endpoint: { profile: cardEndpoint.profile, session: cardEndpoint.session },
|
|
1858
|
+
merchant_card_endpoint: { profile: cardEndpoint.profile, session: cardEndpoint.session, ...(cardEndpoint.facts ? { facts: cardEndpoint.facts } : {}) },
|
|
1795
1859
|
} : {
|
|
1796
1860
|
user: input.user,
|
|
1797
1861
|
merchant: input.merchant,
|
|
@@ -1882,6 +1946,13 @@ export class VaultClient {
|
|
|
1882
1946
|
// registration is read every 500 ms instead of waiting out 2 seconds.
|
|
1883
1947
|
const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
|
|
1884
1948
|
: preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000 : 0;
|
|
1949
|
+
// A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
|
|
1950
|
+
// token and approves as soon as this bind reaches it, while the page may give createToken only
|
|
1951
|
+
// seconds (Alaska Airlines' flight checkout gives it three). Poll every 100 ms for 10 seconds,
|
|
1952
|
+
// then at the caller's interval; at the default 2 seconds the first read alone would miss it.
|
|
1953
|
+
// The API's bind names 100 ms as such a row's poll interval, so slow_down admits these reads
|
|
1954
|
+
// (as an eProtect row names the 500 ms read above).
|
|
1955
|
+
const preparedFlexPollUntil = preparation?.cybersourceFlex ? Date.now() + 10_000 : 0;
|
|
1885
1956
|
let execution = {};
|
|
1886
1957
|
let approvalDelivered = false;
|
|
1887
1958
|
const deliverApproval = (state) => {
|
|
@@ -1897,6 +1968,8 @@ export class VaultClient {
|
|
|
1897
1968
|
catch { /* observer only */ }
|
|
1898
1969
|
};
|
|
1899
1970
|
let failed = false;
|
|
1971
|
+
// What this attempt throws, once it fails: a confirmed retirement below is written onto it.
|
|
1972
|
+
let failure = null;
|
|
1900
1973
|
let actionRequired = false;
|
|
1901
1974
|
const checkAutopilotAction = (state) => {
|
|
1902
1975
|
if (state.autopilot_status !== 'action_required')
|
|
@@ -1927,7 +2000,8 @@ export class VaultClient {
|
|
|
1927
2000
|
while (Date.now() < deadline) {
|
|
1928
2001
|
if (stopSignal?.aborted)
|
|
1929
2002
|
throw new PaymentOutcomeUnknownError(authorizationId, input.merchantSignal?.aborted ? 'merchant_request_aborted' : 'local_cancel');
|
|
1930
|
-
const pollInterval = Date.now() < preparedFastPollUntil ? Math.min(this.pollIntervalMs, 500)
|
|
2003
|
+
const pollInterval = Date.now() < preparedFastPollUntil ? Math.min(this.pollIntervalMs, 500)
|
|
2004
|
+
: Date.now() < preparedFlexPollUntil ? Math.min(this.pollIntervalMs, 100) : this.pollIntervalMs;
|
|
1931
2005
|
await interruptibleSleep(Math.min(pollInterval, Math.max(0, deadline - Date.now())), stopSignal);
|
|
1932
2006
|
if (stopSignal?.aborted)
|
|
1933
2007
|
throw new PaymentOutcomeUnknownError(authorizationId, input.merchantSignal?.aborted ? 'merchant_request_aborted' : 'local_cancel');
|
|
@@ -2257,9 +2331,8 @@ export class VaultClient {
|
|
|
2257
2331
|
}
|
|
2258
2332
|
catch (error) {
|
|
2259
2333
|
failed = true;
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
throw error;
|
|
2334
|
+
failure = input.merchantSignal?.aborted ? new PaymentOutcomeUnknownError(authorizationId, 'merchant_request_aborted') : error;
|
|
2335
|
+
throw failure;
|
|
2263
2336
|
}
|
|
2264
2337
|
finally {
|
|
2265
2338
|
// An attested action-required result retains its claim and reservation.
|
|
@@ -2271,7 +2344,12 @@ export class VaultClient {
|
|
|
2271
2344
|
// known ID can be retired. An unacknowledged create remains unknown.
|
|
2272
2345
|
// A started/finalized replay or failed cleanup never becomes a claimed
|
|
2273
2346
|
// cancellation, and this best-effort cleanup never retries a payment.
|
|
2274
|
-
await this.cancelAuthorization(authorizationId).catch(() =>
|
|
2347
|
+
const retired = await this.cancelAuthorization(authorizationId).catch(() => null);
|
|
2348
|
+
// A retirement whose acknowledgement says no processor request started: the thrown error
|
|
2349
|
+
// says so. The API echoes the reason the row carries, so a row retired earlier as
|
|
2350
|
+
// merchant_never_retried can answer that a processor request did start; then nothing changes.
|
|
2351
|
+
if (retired?.processor_request_started === false && failure instanceof PaymentOutcomeUnknownError)
|
|
2352
|
+
failure.processorRequestStarted = false;
|
|
2275
2353
|
}
|
|
2276
2354
|
}
|
|
2277
2355
|
}
|
|
@@ -2632,9 +2710,11 @@ export class VaultClient {
|
|
|
2632
2710
|
* approves every such payment); the agent's amount, the only authority here; a
|
|
2633
2711
|
* session the API and the device would take (payment-core
|
|
2634
2712
|
* merchantCardEndpointSessionRefusal: the profile's URL, headers, cookies, Referer and
|
|
2635
|
-
* body, paused on its checkout page);
|
|
2636
|
-
*
|
|
2637
|
-
*
|
|
2713
|
+
* body, paused on its checkout page); for a merchant that keeps its own record of the
|
|
2714
|
+
* order, that record, held to the agent's amount and currency, and refused for an order
|
|
2715
|
+
* that renews or a checkout in test mode (admitCardEndpointFacts); and the profile's
|
|
2716
|
+
* status last, so that refusal means every other check passed. Nothing it throws names
|
|
2717
|
+
* the URL, the body or a session value.
|
|
2638
2718
|
*/
|
|
2639
2719
|
function admitCardEndpoint(input) {
|
|
2640
2720
|
const named = input.request.merchant_card_endpoint;
|
|
@@ -2657,9 +2737,45 @@ function admitCardEndpoint(input) {
|
|
|
2657
2737
|
const refusal = cardEndpointSessionRefusal(profile, session);
|
|
2658
2738
|
if (refusal)
|
|
2659
2739
|
throw new CardEndpointRefusedError(profile.id, 'request_unsupported', refusal);
|
|
2740
|
+
const facts = admitCardEndpointFacts(profile, session, named.facts, input);
|
|
2660
2741
|
if (!cardEndpointLive(profile))
|
|
2661
2742
|
throw new CardEndpointRefusedError(profile.id, 'not_enabled');
|
|
2662
|
-
return { profile: profile.id, session };
|
|
2743
|
+
return { profile: profile.id, session, ...(facts ? { facts } : {}) };
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* The merchant's own record of the order, for a profile whose merchant keeps one
|
|
2747
|
+
* (profile.facts), read with the rules the API reads it with (payment-core
|
|
2748
|
+
* readMerchantCardEndpointFacts and merchantCardEndpointFactsRefusal): required, bound
|
|
2749
|
+
* to this checkout's session, in the reviewed layout; a total or currency that differs
|
|
2750
|
+
* from the agent's is an AmountMismatchError against the merchant's total, and a
|
|
2751
|
+
* renewing order or a checkout in test mode is refused. Card endpoint payments are live
|
|
2752
|
+
* only, so the record must say the checkout is live. Undefined for any other profile,
|
|
2753
|
+
* which sends none.
|
|
2754
|
+
*/
|
|
2755
|
+
function admitCardEndpointFacts(profile, session, sent, input) {
|
|
2756
|
+
if (!profile.facts) {
|
|
2757
|
+
if (sent !== undefined)
|
|
2758
|
+
throw new CardEndpointRefusedError(profile.id, 'request_unsupported', 'the request carries an order record this merchant has none of');
|
|
2759
|
+
return undefined;
|
|
2760
|
+
}
|
|
2761
|
+
if (sent === undefined)
|
|
2762
|
+
throw new CardEndpointRefusedError(profile.id, 'facts_unavailable', `${profile.merchant}'s own record of this order was not read`);
|
|
2763
|
+
const read = sharedReadFacts(profile.id, session.url, sent);
|
|
2764
|
+
if (!read.ok)
|
|
2765
|
+
throw new CardEndpointRefusedError(profile.id, 'facts_unavailable', read.refusal);
|
|
2766
|
+
const currency = String(input.currency).toLowerCase();
|
|
2767
|
+
const refusal = sharedFactsRefusal(profile.id, read.facts, { amountCents: typeof input.amount === 'number' ? input.amount : Number.NaN, currency, live: true });
|
|
2768
|
+
if (refusal?.code === 'amount' || refusal?.code === 'currency') {
|
|
2769
|
+
// An integer amount is compared here; a decimal string is left to the API, which
|
|
2770
|
+
// reads it in the currency's units and holds it to the same total.
|
|
2771
|
+
if (typeof input.amount === 'number') {
|
|
2772
|
+
throw new AmountMismatchError(null, input.amount, read.facts.amount.amountCents, currency, read.facts.amount.currency, 'create', 'merchant_total');
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
else if (refusal) {
|
|
2776
|
+
throw new CardEndpointRefusedError(profile.id, 'facts_refused', refusal.reason);
|
|
2777
|
+
}
|
|
2778
|
+
return sent;
|
|
2663
2779
|
}
|
|
2664
2780
|
/**
|
|
2665
2781
|
* The merchant's answer to the device's card request, as the API serves it once
|
|
@@ -2682,8 +2798,9 @@ function cardEndpointReplay(s, authorizationId, profile, metadata) {
|
|
|
2682
2798
|
throw unknown('merchant_answer_malformed');
|
|
2683
2799
|
const base = { mode: CARD_ENDPOINT_MODE, authorizationId, profile, status: answer.status, ...metadata };
|
|
2684
2800
|
if (answer.withheld !== undefined) {
|
|
2685
|
-
if (answer.withheld !== 'card_in_response' && answer.withheld !== 'unreadable_in_response')
|
|
2801
|
+
if (answer.withheld !== 'card_in_response' && answer.withheld !== 'unreadable_in_response' && answer.withheld !== 'refused_step_in_response') {
|
|
2686
2802
|
throw unknown('merchant_answer_malformed');
|
|
2803
|
+
}
|
|
2687
2804
|
return { ...base, headers: {}, setCookies: [], body: '', withheld: answer.withheld };
|
|
2688
2805
|
}
|
|
2689
2806
|
const headers = answer.headers;
|
|
@@ -54,7 +54,7 @@ declare function parseCreditguardTransaction(body: any, txId: any): {
|
|
|
54
54
|
personalIdAlwaysEnabled: boolean;
|
|
55
55
|
inputs: any;
|
|
56
56
|
} | null;
|
|
57
|
-
declare function creditguardTransactionRefusal(txn: any, expectation: any): "otp" | "keep_on_file" | "dcc" | "installments" | "personal_id" | "jwe_unsupported" | "amount_mismatch" | "currency_mismatch" | "transaction_unreadable" | "three_d_secure" | "transaction_type" | "form_inputs" | "card_field_hidden" | "no_retries_left" | "
|
|
57
|
+
declare function creditguardTransactionRefusal(txn: any, expectation: any): "otp" | "keep_on_file" | "dcc" | "installments" | "personal_id" | "jwe_unsupported" | "amount_mismatch" | "environment_mismatch" | "currency_mismatch" | "transaction_unreadable" | "three_d_secure" | "transaction_type" | "form_inputs" | "card_field_hidden" | "no_retries_left" | "jwe_public_key_malformed" | null;
|
|
58
58
|
declare function creditguardSendWindowRefusal(timing: any): "timing_unknown" | "page_expired" | "recaptcha_expired" | null;
|
|
59
59
|
declare var CREDITGUARD_REFUSAL_CODES: readonly string[];
|
|
60
60
|
declare function creditguardResponseOutcome(status: any, body: any, options: any): {
|
|
@@ -158,6 +158,8 @@ export declare class MicroformFrames {
|
|
|
158
158
|
/** A frame's committed document URL, fragment included. A URL that is not a field iframe's forgets the frame. */
|
|
159
159
|
observe(frameId: unknown, url: unknown): void;
|
|
160
160
|
forget(frameId: unknown): void;
|
|
161
|
+
/** The card Microform whose field documents this attachment recorded, for prepare({ tokenizeBeforePay: true }). */
|
|
162
|
+
loadedCard(): LoadedCardMicroform;
|
|
161
163
|
/**
|
|
162
164
|
* Judge a POST the frame `frameId` sent. When that frame is not on record, `reread` reads the
|
|
163
165
|
* pausing session's frame tree once (recording what it finds) and resolves to the frame's
|
|
@@ -173,6 +175,34 @@ export declare class MicroformFrames {
|
|
|
173
175
|
* request's frame could not be read at all.
|
|
174
176
|
*/
|
|
175
177
|
export declare function judgeMicroformFrameUrls(request: FlexRequest, issuingUrl: string | undefined, frameUrls: Iterable<string | undefined>, contexts: CaptureContexts): CybersourceFlexDecision | Promise<CybersourceFlexDecision>;
|
|
178
|
+
/**
|
|
179
|
+
* What prepare({ tokenizeBeforePay: true }) reads from the page before Pay: the one card Microform
|
|
180
|
+
* whose fields loaded, its capture context and its fields, or why there is none to read.
|
|
181
|
+
*/
|
|
182
|
+
export type LoadedCardMicroform = {
|
|
183
|
+
ok: true;
|
|
184
|
+
captureContext: string;
|
|
185
|
+
environment: string;
|
|
186
|
+
microform: {
|
|
187
|
+
version: string;
|
|
188
|
+
fields: MicroformField[];
|
|
189
|
+
};
|
|
190
|
+
} | {
|
|
191
|
+
ok: false;
|
|
192
|
+
reason: 'microform_not_loaded' | 'microform_ambiguous' | 'microform_unsupported';
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The card Microform a page loaded, for prepare({ tokenizeBeforePay: true }): every committed card
|
|
196
|
+
* field-iframe document, grouped by capture context, microform id, version and host. One group of a
|
|
197
|
+
* reviewed version, with a card number field and at most a security code field, is the checkout the
|
|
198
|
+
* cardholder's device tokenizes before Pay. No group is `microform_not_loaded` (prepare once the card
|
|
199
|
+
* fields show), two or more are `microform_ambiguous`, and any other group is `microform_unsupported`.
|
|
200
|
+
* A check (eCheck) Microform carries no card and is not counted. Reads the frames only; the capture
|
|
201
|
+
* context is verified by prepareCheckout, the API and the device.
|
|
202
|
+
*/
|
|
203
|
+
export declare function loadedCardMicroform(frames: Iterable<MicroformFrame>): LoadedCardMicroform;
|
|
204
|
+
/** The Playwright adapter's read: every frame URL on the page (Playwright keeps the fragment in frame.url()). */
|
|
205
|
+
export declare function loadedCardMicroformFromUrls(urls: Iterable<string | undefined>): LoadedCardMicroform;
|
|
176
206
|
/**
|
|
177
207
|
* Whether a create's sidecar has the shape the API admits (routes/api/v2/checkout.ts
|
|
178
208
|
* createSchema): a capture context of 1 to 16384 characters, and a Microform version with a
|
|
@@ -89,4 +89,30 @@ declare function transientTokenFacts(status: any, body: any, verified: any, card
|
|
|
89
89
|
}>;
|
|
90
90
|
declare function cybersourceFlexPreparationUrl(environment: any): any;
|
|
91
91
|
declare function isPreparedCybersourceFlexRequest(url: any, method: any, body: any, environment: any, headers: any): boolean;
|
|
92
|
-
|
|
92
|
+
declare var CYBERSOURCE_FLEX_PRESEAL_TRIGGER: string;
|
|
93
|
+
declare function admitCybersourceFlexPreparation(prepared: any, { crypto, now }?: {}): Promise<Readonly<{
|
|
94
|
+
context: Readonly<{
|
|
95
|
+
raw: any;
|
|
96
|
+
signingKid: any;
|
|
97
|
+
environment: any;
|
|
98
|
+
origin: string;
|
|
99
|
+
path: string;
|
|
100
|
+
url: string;
|
|
101
|
+
jwk: Readonly<{
|
|
102
|
+
kty: "RSA";
|
|
103
|
+
n: any;
|
|
104
|
+
e: any;
|
|
105
|
+
kid: any;
|
|
106
|
+
}>;
|
|
107
|
+
modulusBytes: number;
|
|
108
|
+
index: any;
|
|
109
|
+
contextType: any;
|
|
110
|
+
allowedCardNetworks: readonly any[] | null;
|
|
111
|
+
targetOrigins: readonly any[];
|
|
112
|
+
iat: any;
|
|
113
|
+
exp: any;
|
|
114
|
+
}>;
|
|
115
|
+
fields: readonly string[];
|
|
116
|
+
}>>;
|
|
117
|
+
declare function sameCybersourceFlexPreparation(prepared: any, request: any): boolean;
|
|
118
|
+
export { CYBERSOURCE_FLEX_CAPTURE_CONTEXT_MAX_LENGTH, CYBERSOURCE_FLEX_CONTENT_TYPE, CYBERSOURCE_FLEX_PRESEAL_TRIGGER, CYBERSOURCE_FLEX_PSP, CYBERSOURCE_FLEX_REFUSAL_REASONS, CYBERSOURCE_FLEX_REVIEWED_IFRAME_VERSIONS, CYBERSOURCE_FLEX_RULES_SOURCE, CYBERSOURCE_FLEX_SIGNING_KEYS, CYBERSOURCE_FLEX_TEST_PANS, CYBERSOURCE_FLEX_TOKENS_URLS, CybersourceFlexError, admitCybersourceFlexPreparation, assertCybersourceFlexTemplate, buildCardPlaintext, cybersourceFlexEnvironment, flexHostEnvironment as cybersourceFlexHostEnvironment, cybersourceFlexPreparationUrl, cybersourceFlexRefusalCode, cybersourceFlexTemplateKid, detectCardTypes, isCybersourceFlexMediaType, isCybersourceFlexRequest, isCybersourceFlexTemplateBody, isCybersourceFlexTokenShape, isPreparedCybersourceFlexRequest, isVerifiedCaptureContext, parseMicroformFragment, sameCybersourceFlexPreparation, sealForContext, transientTokenFacts, transientTokenSigned, verifyCaptureContext };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: a2baaac410b5aaa629b3d5031136f88645c7b45c67c3bbc154cad72de216a04e
|
|
4
4
|
// src/cybersource-flex-rules.js
|
|
5
5
|
var CYBERSOURCE_FLEX_RULES_SOURCE = Object.freeze({ version: "2.12.1", sha256: "8429ebae83e9745cd53ad8ff0d9ba69bee0087c1435ed0695a25444bed418327" });
|
|
6
6
|
var CYBERSOURCE_FLEX_RULES = [
|
|
@@ -686,4 +686,44 @@ function iframeHeaders(headers) {
|
|
|
686
686
|
function isPreparedCybersourceFlexRequest(url, method, body, environment, headers) {
|
|
687
687
|
return (environment === "production" || environment === "sandbox") && method === "POST" && cybersourceFlexEnvironment(url) === environment && iframeHeaders(headers) && isCybersourceFlexTemplateBody(body);
|
|
688
688
|
}
|
|
689
|
-
|
|
689
|
+
var CYBERSOURCE_FLEX_PRESEAL_TRIGGER = "number";
|
|
690
|
+
var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
691
|
+
function preparedLayout(microform) {
|
|
692
|
+
if (!isRecord(microform) || typeof microform.version !== "string" || !CYBERSOURCE_FLEX_REVIEWED_IFRAME_VERSIONS.includes(microform.version)) {
|
|
693
|
+
refuse("microform_version", "this payment's card field is not a Microform version this device can rebuild.");
|
|
694
|
+
}
|
|
695
|
+
if (Object.keys(microform).some((key) => key !== "version" && key !== "fields")) {
|
|
696
|
+
refuse("fields", "the Microform fields this payment loaded are not a card number field and an optional security code field.");
|
|
697
|
+
}
|
|
698
|
+
fieldSet(microform.fields, CYBERSOURCE_FLEX_PRESEAL_TRIGGER);
|
|
699
|
+
return Object.freeze(FIELD_TYPES.filter((field) => microform.fields.includes(field)));
|
|
700
|
+
}
|
|
701
|
+
async function admitCybersourceFlexPreparation(prepared, { crypto, now } = {}) {
|
|
702
|
+
if (!isRecord(prepared))
|
|
703
|
+
refuse("request", "malformed checkout preparation.");
|
|
704
|
+
const fields = preparedLayout(prepared.microform);
|
|
705
|
+
const { environment } = prepared;
|
|
706
|
+
if (environment !== "production" && environment !== "sandbox") {
|
|
707
|
+
refuse("environment_mismatch", "the preparation names no CyberSource environment.");
|
|
708
|
+
}
|
|
709
|
+
const context = await verifyCaptureContext(prepared.capture_context, { crypto, now });
|
|
710
|
+
if (context.environment !== environment || context.url !== CYBERSOURCE_FLEX_TOKENS_URLS[environment]) {
|
|
711
|
+
refuse("environment_mismatch", "the CyberSource test and live environments are mixed in this payment.");
|
|
712
|
+
}
|
|
713
|
+
return Object.freeze({ context, fields });
|
|
714
|
+
}
|
|
715
|
+
function sameCybersourceFlexPreparation(prepared, request) {
|
|
716
|
+
if (!isRecord(prepared) || !isRecord(request) || !isRecord(prepared.microform) || !isRecord(request.microform))
|
|
717
|
+
return false;
|
|
718
|
+
const context = prepared.capture_context;
|
|
719
|
+
if (typeof context !== "string" || context.length < 1 || context.length > CAPTURE_CONTEXT_MAX_LENGTH || request.capture_context !== context)
|
|
720
|
+
return false;
|
|
721
|
+
const one = prepared.microform;
|
|
722
|
+
const two = request.microform;
|
|
723
|
+
if (typeof one.version !== "string" || one.version !== two.version || !Array.isArray(one.fields) || !Array.isArray(two.fields))
|
|
724
|
+
return false;
|
|
725
|
+
const mine = new Set(one.fields);
|
|
726
|
+
const theirs = new Set(two.fields);
|
|
727
|
+
return mine.size === one.fields.length && theirs.size === two.fields.length && mine.size === theirs.size && [...mine].every((field) => theirs.has(field));
|
|
728
|
+
}
|
|
729
|
+
export { CYBERSOURCE_FLEX_CAPTURE_CONTEXT_MAX_LENGTH, CYBERSOURCE_FLEX_CONTENT_TYPE, CYBERSOURCE_FLEX_PRESEAL_TRIGGER, CYBERSOURCE_FLEX_PSP, CYBERSOURCE_FLEX_REFUSAL_REASONS, CYBERSOURCE_FLEX_REVIEWED_IFRAME_VERSIONS, CYBERSOURCE_FLEX_RULES_SOURCE, CYBERSOURCE_FLEX_SIGNING_KEYS, CYBERSOURCE_FLEX_TEST_PANS, CYBERSOURCE_FLEX_TOKENS_URLS, CybersourceFlexError, admitCybersourceFlexPreparation, assertCybersourceFlexTemplate, buildCardPlaintext, cybersourceFlexEnvironment, flexHostEnvironment as cybersourceFlexHostEnvironment, cybersourceFlexPreparationUrl, cybersourceFlexRefusalCode, cybersourceFlexTemplateKid, detectCardTypes, isCybersourceFlexMediaType, isCybersourceFlexRequest, isCybersourceFlexTemplateBody, isCybersourceFlexTokenShape, isPreparedCybersourceFlexRequest, isVerifiedCaptureContext, parseMicroformFragment, sameCybersourceFlexPreparation, sealForContext, transientTokenFacts, transientTokenSigned, verifyCaptureContext };
|
package/dist/cybersource-flex.js
CHANGED
|
@@ -222,6 +222,10 @@ export class MicroformFrames {
|
|
|
222
222
|
if (typeof frameId === 'string')
|
|
223
223
|
this.frames.delete(frameId);
|
|
224
224
|
}
|
|
225
|
+
/** The card Microform whose field documents this attachment recorded, for prepare({ tokenizeBeforePay: true }). */
|
|
226
|
+
loadedCard() {
|
|
227
|
+
return loadedCardMicroform(this.frames.values());
|
|
228
|
+
}
|
|
225
229
|
/**
|
|
226
230
|
* Judge a POST the frame `frameId` sent. When that frame is not on record, `reread` reads the
|
|
227
231
|
* pausing session's frame tree once (recording what it finds) and resolves to the frame's
|
|
@@ -251,6 +255,48 @@ export function judgeMicroformFrameUrls(request, issuingUrl, frameUrls, contexts
|
|
|
251
255
|
}
|
|
252
256
|
return judgeMicroformRequest(request, issuingUrl === undefined ? undefined : frameOf(issuingUrl), frames, contexts);
|
|
253
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* The card Microform a page loaded, for prepare({ tokenizeBeforePay: true }): every committed card
|
|
260
|
+
* field-iframe document, grouped by capture context, microform id, version and host. One group of a
|
|
261
|
+
* reviewed version, with a card number field and at most a security code field, is the checkout the
|
|
262
|
+
* cardholder's device tokenizes before Pay. No group is `microform_not_loaded` (prepare once the card
|
|
263
|
+
* fields show), two or more are `microform_ambiguous`, and any other group is `microform_unsupported`.
|
|
264
|
+
* A check (eCheck) Microform carries no card and is not counted. Reads the frames only; the capture
|
|
265
|
+
* context is verified by prepareCheckout, the API and the device.
|
|
266
|
+
*/
|
|
267
|
+
export function loadedCardMicroform(frames) {
|
|
268
|
+
const groups = new Map();
|
|
269
|
+
for (const frame of frames) {
|
|
270
|
+
if (frame.microformType !== 'card')
|
|
271
|
+
continue;
|
|
272
|
+
const key = JSON.stringify([frame.captureContext, frame.microformId, frame.version, frame.environment]);
|
|
273
|
+
const group = groups.get(key);
|
|
274
|
+
if (group)
|
|
275
|
+
group.types.add(frame.fieldType);
|
|
276
|
+
else
|
|
277
|
+
groups.set(key, { frame, types: new Set([frame.fieldType]) });
|
|
278
|
+
}
|
|
279
|
+
if (groups.size === 0)
|
|
280
|
+
return { ok: false, reason: 'microform_not_loaded' };
|
|
281
|
+
if (groups.size > 1)
|
|
282
|
+
return { ok: false, reason: 'microform_ambiguous' };
|
|
283
|
+
const [{ frame, types }] = [...groups.values()];
|
|
284
|
+
if (!frame.reviewed || !types.has('number') || [...types].some((type) => !MICROFORM_FIELDS.includes(type))) {
|
|
285
|
+
return { ok: false, reason: 'microform_unsupported' };
|
|
286
|
+
}
|
|
287
|
+
return { ok: true, captureContext: frame.captureContext, environment: frame.environment,
|
|
288
|
+
microform: { version: frame.version, fields: MICROFORM_FIELDS.filter((field) => types.has(field)) } };
|
|
289
|
+
}
|
|
290
|
+
/** The Playwright adapter's read: every frame URL on the page (Playwright keeps the fragment in frame.url()). */
|
|
291
|
+
export function loadedCardMicroformFromUrls(urls) {
|
|
292
|
+
const frames = [];
|
|
293
|
+
for (const url of urls) {
|
|
294
|
+
const frame = frameOf(url);
|
|
295
|
+
if (frame)
|
|
296
|
+
frames.push(frame);
|
|
297
|
+
}
|
|
298
|
+
return loadedCardMicroform(frames);
|
|
299
|
+
}
|
|
254
300
|
/**
|
|
255
301
|
* Whether a create's sidecar has the shape the API admits (routes/api/v2/checkout.ts
|
|
256
302
|
* createSchema): a capture context of 1 to 16384 characters, and a Microform version with a
|