@agent-cards/sdk 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/PREFLIGHT.md +4 -0
- package/README.md +153 -0
- package/dist/cdp.d.ts +2 -1
- package/dist/cdp.js +161 -31
- package/dist/client.d.ts +97 -1
- package/dist/client.js +172 -5
- package/dist/cybersource-flex.d.ts +183 -0
- package/dist/cybersource-flex.generated.d.ts +92 -0
- package/dist/cybersource-flex.generated.js +689 -0
- package/dist/cybersource-flex.js +275 -0
- package/dist/gmo.d.ts +63 -0
- package/dist/gmo.generated.d.ts +60 -0
- package/dist/gmo.generated.js +586 -0
- package/dist/gmo.js +119 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/lifecycle.d.ts +8 -3
- package/dist/lifecycle.js +6 -2
- package/dist/pre-claim.d.ts +12 -1
- package/dist/pre-claim.js +135 -9
- package/dist/preflight-catalog.json +116 -0
- package/dist/preflight-schemas.json +8 -0
- package/dist/preflight.generated.js +15 -1
- package/dist/prepared-processor.d.ts +4 -2
- package/dist/prepared-processor.js +12 -2
- package/dist/registry.d.ts +17 -7
- package/dist/stripe-checkout.generated.js +37 -2
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -12,6 +12,9 @@ import { BuyModule } from './buy.js';
|
|
|
12
12
|
import { Languages } from './languages.js';
|
|
13
13
|
import { RequestRegistry } from './requests.js';
|
|
14
14
|
import { AgentcardError } from './voice-types.js';
|
|
15
|
+
import { GMO_CAPABILITY, GMO_PSP, gmoTokenAnswer } from './gmo.js';
|
|
16
|
+
import { isCybersourceFlexTokenShape } from './cybersource-flex.generated.js';
|
|
17
|
+
import { CYBERSOURCE_FLEX_CAPABILITY, CYBERSOURCE_FLEX_PSP, validCybersourceFlexSidecar } from './cybersource-flex.js';
|
|
15
18
|
/**
|
|
16
19
|
* The modes this SDK can finish. Asked for on syncRegistry (the API serves
|
|
17
20
|
* only recognizers in these modes, so a request this build cannot complete
|
|
@@ -31,14 +34,34 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
31
34
|
/**
|
|
32
35
|
* Dark-launch capabilities this build can finish. Sent on syncRegistry as
|
|
33
36
|
* ?capabilities= so the API also serves the recognizers gated behind one of
|
|
34
|
-
* these
|
|
37
|
+
* these, and only to an org Agentcard enabled for the processor.
|
|
38
|
+
* `fiserv_card_capture`: Fiserv Commerce Hub's Secure Data Capture card
|
|
35
39
|
* capture, which this build pauses and pays only through a prepared checkout
|
|
36
40
|
* (pre-claim.ts, fiserv.ts). The API serves Fiserv's recognizer only to a build
|
|
37
41
|
* that lists it and only for a company Agentcard turned Fiserv on for, so an older
|
|
38
42
|
* SDK never pauses a capture it cannot finish and no SDK pauses one for a company
|
|
39
43
|
* Fiserv is off for; the API also refuses a Fiserv payment for every such company.
|
|
44
|
+
* `gmo_shared_pk`: GMO token v2's getToken on the shared-key path, which this
|
|
45
|
+
* build judges before it pauses (gmo.ts) and pays only for the amount the
|
|
46
|
+
* agent passes. A gated recognizer never sits in BUILTIN_REGISTRY, so it is
|
|
47
|
+
* paused only after a sync.
|
|
48
|
+
* `capture_context`: CyberSource Flex Microform, whose create carries the capture
|
|
49
|
+
* context this build reads from the card field iframe (cybersource-flex.ts); the API
|
|
50
|
+
* serves Flex's recognizer the same way, only for a company Agentcard turned it on for.
|
|
51
|
+
* A gated recognizer is never in BUILTIN_REGISTRY, so a build reaches it only through a
|
|
52
|
+
* sync that declares its capability.
|
|
40
53
|
*/
|
|
41
|
-
export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture'];
|
|
54
|
+
export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY];
|
|
55
|
+
/**
|
|
56
|
+
* Processors whose paused request names no amount at all, so the pair the
|
|
57
|
+
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
58
|
+
* without it (AmountRequiredError), and the API answers 400 amount_required
|
|
59
|
+
* for the same case. GMO's card token request carries none: the merchant's
|
|
60
|
+
* server charges the token later, for the amount it decides.
|
|
61
|
+
*/
|
|
62
|
+
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP];
|
|
63
|
+
/** How AmountRequiredError names a processor. */
|
|
64
|
+
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO' };
|
|
42
65
|
/** An integer in the smallest unit, or a decimal string in normal units with a point; nothing else. */
|
|
43
66
|
export function validAmountInput(amount) {
|
|
44
67
|
if (typeof amount === 'number')
|
|
@@ -78,6 +101,50 @@ export class UnsupportedModeError extends Error {
|
|
|
78
101
|
this.name = 'UnsupportedModeError';
|
|
79
102
|
}
|
|
80
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The recognizer is gated behind a dark-launch capability this SDK build does not
|
|
106
|
+
* declare (SUPPORTED_CAPABILITIES), so it cannot send what that processor's create
|
|
107
|
+
* needs. Thrown locally before any create or prompt, and terminal. A registry the
|
|
108
|
+
* API served never holds such an entry (it serves a gated one only to a build that
|
|
109
|
+
* declares it); a hand-built one can.
|
|
110
|
+
*/
|
|
111
|
+
export class UnsupportedCapabilityError extends Error {
|
|
112
|
+
capability;
|
|
113
|
+
constructor(capability) {
|
|
114
|
+
super(`the checkout needs the "${capability}" capability, which this version of @agent-cards/checkout cannot complete; upgrade the SDK.`);
|
|
115
|
+
this.capability = capability;
|
|
116
|
+
this.name = 'UnsupportedCapabilityError';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* placeholderCard() found no stored card to describe (`card_not_found`), the stored card's
|
|
121
|
+
* network has no documented placeholder number (`brand_unsupported`), or the stored card
|
|
122
|
+
* carries no usable expiry (`expiry_unknown`). Nothing was created and nothing was charged.
|
|
123
|
+
*/
|
|
124
|
+
export class PlaceholderCardError extends Error {
|
|
125
|
+
reason;
|
|
126
|
+
constructor(reason) {
|
|
127
|
+
super(`no placeholder card: ${reason}`);
|
|
128
|
+
this.reason = reason;
|
|
129
|
+
this.name = 'PlaceholderCardError';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The placeholder number per network: a documented test card that the pinned Microform
|
|
134
|
+
* card-type table detects as that network and nothing else. Visa, Mastercard, Amex and JCB
|
|
135
|
+
* are CyberSource's own test cards (developer.cybersource.com hello-world testing guide).
|
|
136
|
+
* Discover is not: CyberSource's 6011111111111117 falls in the table's Maestro range, so a
|
|
137
|
+
* merchant that allows Discover but not Maestro refuses it in the card field itself; Stripe's
|
|
138
|
+
* documented Discover test card 6011000990139424 is detected as Discover alone. A network
|
|
139
|
+
* outside this list has no placeholder and placeholderCard() refuses it rather than guess.
|
|
140
|
+
*/
|
|
141
|
+
const PLACEHOLDER_NUMBERS = Object.freeze({
|
|
142
|
+
visa: '4111111111111111',
|
|
143
|
+
mastercard: '5555555555554444',
|
|
144
|
+
amex: '378282246310005',
|
|
145
|
+
discover: '6011000990139424',
|
|
146
|
+
jcb: '3566111111111113',
|
|
147
|
+
});
|
|
81
148
|
/**
|
|
82
149
|
* The recognizer says this processor is preparation-required, and no preparation
|
|
83
150
|
* was passed. Its card may be sent only after the cardholder approved a
|
|
@@ -94,6 +161,24 @@ export class PreparationRequiredError extends Error {
|
|
|
94
161
|
this.name = 'PreparationRequiredError';
|
|
95
162
|
}
|
|
96
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* The paused request names no amount and none was passed. A GMO card token
|
|
166
|
+
* request carries no amount (the merchant's server charges the token later),
|
|
167
|
+
* so the `amount` and `currency` you pass are what the cardholder approves.
|
|
168
|
+
* Thrown locally before any create or prompt, and terminal: the same request
|
|
169
|
+
* without an amount fails the same way. The API answers 400 amount_required
|
|
170
|
+
* for the same case. A page total (`pageAmount`) does not count.
|
|
171
|
+
*/
|
|
172
|
+
export class AmountRequiredError extends Error {
|
|
173
|
+
psp;
|
|
174
|
+
code = 'amount_required';
|
|
175
|
+
constructor(psp) {
|
|
176
|
+
const processor = PROCESSOR_NAMES[psp] ?? psp;
|
|
177
|
+
super(`A ${processor} payment request names no amount, so pass amount and currency: that is the amount the cardholder approves. Nothing was created and nothing was charged.`);
|
|
178
|
+
this.psp = psp;
|
|
179
|
+
this.name = 'AmountRequiredError';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
97
182
|
export class ApprovalTimeoutError extends Error {
|
|
98
183
|
constructor(ms) { super(`user did not approve within ${ms}ms`); this.name = 'ApprovalTimeoutError'; }
|
|
99
184
|
}
|
|
@@ -688,9 +773,14 @@ export class VaultClient {
|
|
|
688
773
|
let raw;
|
|
689
774
|
// ?capabilities= is the dark-launch half of the same negotiation: a
|
|
690
775
|
// processor gated behind a capability is served only when this build lists
|
|
691
|
-
// it
|
|
692
|
-
//
|
|
693
|
-
//
|
|
776
|
+
// it (SUPPORTED_CAPABILITIES: fiserv_card_capture, gmo_shared_pk,
|
|
777
|
+
// capture_context) and the caller's company is one Agentcard turned that
|
|
778
|
+
// processor on for, so this build arms Fiserv's card capture, GMO's getToken
|
|
779
|
+
// and CyberSource Flex's card fields only for such a company, and an older
|
|
780
|
+
// build never receives a recognizer it cannot judge. Every other org's
|
|
781
|
+
// registry never recognizes a Flex host, so its Microform requests go
|
|
782
|
+
// through untouched; the create checks the org again (403
|
|
783
|
+
// processor_unavailable).
|
|
694
784
|
const capabilities = SUPPORTED_CAPABILITIES.length ? `&capabilities=${SUPPORTED_CAPABILITIES.join(',')}` : '';
|
|
695
785
|
// merchant_profiles=1 asks for the reviewed Adyen merchant profiles too, each
|
|
696
786
|
// as a `{ merchant_profile }` entry after the recognizers; an API that
|
|
@@ -786,6 +876,39 @@ export class VaultClient {
|
|
|
786
876
|
const registry = this.mergedRegistry();
|
|
787
877
|
return this.sandbox ? [...deriveCardUrlPatterns(registry), ...LOOPBACK_URL_PATTERNS] : deriveCardUrlPatterns(registry);
|
|
788
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* What to type into a checkout so the page's own card facts match the stored card that
|
|
881
|
+
* pays (see PlaceholderCard): read from GET /api/v2/vault_cards, which lists each stored card's
|
|
882
|
+
* network and expiry. `cardId` names the stored card; omitted, the user's most recently added
|
|
883
|
+
* card. Pin the returned `cardId` on the attachment or authorize(), so the approval page
|
|
884
|
+
* preselects that card: without a pin the page preselects its own choice (its recommended
|
|
885
|
+
* card, or the first card the cardholder's device can open), which can be another card. After
|
|
886
|
+
* prepare(), pass the prepared checkout's `cardId`, the card the cardholder already approved.
|
|
887
|
+
* The facts hold only while the cardholder pays with that card. Throws PlaceholderCardError
|
|
888
|
+
* when there is no such card, its network has no placeholder, or it carries no expiry.
|
|
889
|
+
*/
|
|
890
|
+
async placeholderCard(input) {
|
|
891
|
+
if (!input || typeof input.user !== 'string' || !input.user)
|
|
892
|
+
throw new Error('placeholderCard needs the user whose stored card will pay.');
|
|
893
|
+
// The card list lives under /api/v2 only: the API aliases /v2/checkout alone without the prefix.
|
|
894
|
+
const list = await this.get(`/api/v2/vault_cards?user_id=${encodeURIComponent(input.user)}`, input.signal);
|
|
895
|
+
const cards = Array.isArray(list?.data) ? list.data : [];
|
|
896
|
+
const card = (input.cardId === undefined ? cards[0] : cards.find((entry) => entry?.id === input.cardId));
|
|
897
|
+
if (!card || typeof card !== 'object' || typeof card.id !== 'string' || !card.id)
|
|
898
|
+
throw new PlaceholderCardError('card_not_found');
|
|
899
|
+
const brand = typeof card.brand === 'string' ? card.brand.toLowerCase() : '';
|
|
900
|
+
const number = Object.prototype.hasOwnProperty.call(PLACEHOLDER_NUMBERS, brand) ? PLACEHOLDER_NUMBERS[brand] : undefined;
|
|
901
|
+
if (!number)
|
|
902
|
+
throw new PlaceholderCardError('brand_unsupported');
|
|
903
|
+
const month = card.expiry_month, year = card.expiry_year;
|
|
904
|
+
if (!Number.isSafeInteger(month) || month < 1 || month > 12
|
|
905
|
+
|| !Number.isSafeInteger(year) || year < 2000 || year > 2099)
|
|
906
|
+
throw new PlaceholderCardError('expiry_unknown');
|
|
907
|
+
return Object.freeze({
|
|
908
|
+
cardId: card.id, brand, number, securityCode: brand === 'amex' ? '1234' : '123',
|
|
909
|
+
expiryMonth: String(month).padStart(2, '0'), expiryYear: String(year),
|
|
910
|
+
});
|
|
911
|
+
}
|
|
789
912
|
/** Wait for real device approval before the caller starts native tokenization. */
|
|
790
913
|
async prepareCheckout(input) {
|
|
791
914
|
input = { ...input };
|
|
@@ -1069,6 +1192,22 @@ export class VaultClient {
|
|
|
1069
1192
|
throw new CardEncryptedError(rec.psp);
|
|
1070
1193
|
if (!SUPPORTED_MODES.includes(mode))
|
|
1071
1194
|
throw new UnsupportedModeError(mode);
|
|
1195
|
+
// A dark-launch recognizer names the capability its create needs. A build that does not
|
|
1196
|
+
// declare it cannot send what that create carries, so it refuses here, before any create;
|
|
1197
|
+
// a registry the API served never holds such an entry.
|
|
1198
|
+
if (rec.capability !== undefined && !SUPPORTED_CAPABILITIES.includes(rec.capability))
|
|
1199
|
+
throw new UnsupportedCapabilityError(rec.capability);
|
|
1200
|
+
// CyberSource Flex: the capture context and the Microform fields ride on the request (the
|
|
1201
|
+
// adapters read both from the card field iframe) and the API verifies them before anything
|
|
1202
|
+
// exists. They belong to a Flex request and nothing else, as the API holds them.
|
|
1203
|
+
const flex = rec.psp === CYBERSOURCE_FLEX_PSP;
|
|
1204
|
+
if (!flex && (input.request.capture_context !== undefined || input.request.microform !== undefined)) {
|
|
1205
|
+
throw new Error('request.capture_context and request.microform belong to a CyberSource Flex card request.');
|
|
1206
|
+
}
|
|
1207
|
+
if (flex && !validCybersourceFlexSidecar(input.request.capture_context, input.request.microform)) {
|
|
1208
|
+
throw new Error('A CyberSource Flex card request carries the capture context its card field iframe was loaded with (request.capture_context) '
|
|
1209
|
+
+ 'and the Microform fields that loaded (request.microform); attachToCdp and attachToPlaywright read both.');
|
|
1210
|
+
}
|
|
1072
1211
|
// Say no rather than coerce. isCardRequest only ever pauses a POST, and the
|
|
1073
1212
|
// service stores the replay template with method 'POST' hardcoded — so a
|
|
1074
1213
|
// caller handing in a PUT or GET would get it silently rewritten and the
|
|
@@ -1090,6 +1229,12 @@ export class VaultClient {
|
|
|
1090
1229
|
if (hasAmount && !validAmountInput(input.amount)) {
|
|
1091
1230
|
throw new Error('amount is an integer in the currency\'s smallest unit (2306 for $23.06), or a decimal string in normal units ("23.06").');
|
|
1092
1231
|
}
|
|
1232
|
+
// A processor whose request names no amount (AGENT_AMOUNT_PROCESSORS) is
|
|
1233
|
+
// paid only for the pair passed here: it is all the approval can show.
|
|
1234
|
+
// Refused before any create or prompt; the API refuses the same create as
|
|
1235
|
+
// amount_required.
|
|
1236
|
+
if (!hasAmount && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
|
|
1237
|
+
throw new AmountRequiredError(rec.psp);
|
|
1093
1238
|
const timeoutMs = input.timeoutMs ?? 15 * 60_000;
|
|
1094
1239
|
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
|
|
1095
1240
|
throw new Error('timeoutMs must be a positive integer no larger than 2147483647.');
|
|
@@ -1135,6 +1280,11 @@ export class VaultClient {
|
|
|
1135
1280
|
...(checkoutContext ? { [STRIPE_CHECKOUT_CONTEXT_HEADER]: encodeStripeCheckoutContext(checkoutContext) } : {}) },
|
|
1136
1281
|
body: input.request.body,
|
|
1137
1282
|
...(mercadoContext ? { mercado_checkout: mercadoContext } : {}),
|
|
1283
|
+
...(flex ? {
|
|
1284
|
+
capture_context: input.request.capture_context,
|
|
1285
|
+
microform: { version: input.request.microform.version, fields: [...input.request.microform.fields],
|
|
1286
|
+
...(input.request.microform.trigger ? { trigger: input.request.microform.trigger } : {}) },
|
|
1287
|
+
} : {}),
|
|
1138
1288
|
},
|
|
1139
1289
|
};
|
|
1140
1290
|
try {
|
|
@@ -1334,6 +1484,12 @@ export class VaultClient {
|
|
|
1334
1484
|
|| typeof response.body !== 'string' || !response.headers || typeof response.headers !== 'object' || Array.isArray(response.headers)) {
|
|
1335
1485
|
throw new PaymentOutcomeUnknownError(authorizationId, 'approved_response_malformed');
|
|
1336
1486
|
}
|
|
1487
|
+
// A Flex approval answers the card field with the transient token CyberSource signed for
|
|
1488
|
+
// the device's sealed card, which the device checked before it approved. Anything else
|
|
1489
|
+
// is not an answer to hand the field iframe: the device may already have sent the card.
|
|
1490
|
+
if (flex && (response.status < 200 || response.status > 299 || !isCybersourceFlexTokenShape(response.body))) {
|
|
1491
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'cybersource_flex_token_unconfirmed');
|
|
1492
|
+
}
|
|
1337
1493
|
if (shopOrder) {
|
|
1338
1494
|
let receipt;
|
|
1339
1495
|
try {
|
|
@@ -1358,6 +1514,17 @@ export class VaultClient {
|
|
|
1358
1514
|
response.body = JSON.stringify(receipt);
|
|
1359
1515
|
response.headers = { 'content-type': 'application/json' };
|
|
1360
1516
|
}
|
|
1517
|
+
if (rec.psp === GMO_PSP) {
|
|
1518
|
+
// GMO's answer enters the page only as one clean single-use token under a JSON
|
|
1519
|
+
// content type (gmo.ts gmoTokenAnswer). Anything else is never handed over; the
|
|
1520
|
+
// card may have left the device, so the outcome is unknown rather than declined.
|
|
1521
|
+
const answer = gmoTokenAnswer(response.status, response.body);
|
|
1522
|
+
if (!answer)
|
|
1523
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'gmo_token_unconfirmed');
|
|
1524
|
+
response.status = answer.status;
|
|
1525
|
+
response.headers = answer.headers;
|
|
1526
|
+
response.body = answer.body;
|
|
1527
|
+
}
|
|
1361
1528
|
let processorContext;
|
|
1362
1529
|
if (mercadoContext) {
|
|
1363
1530
|
try {
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/** The processor id, as the registry and the API name it. */
|
|
2
|
+
export declare const CYBERSOURCE_FLEX_PSP = "cybersource_flex";
|
|
3
|
+
/**
|
|
4
|
+
* The dark-launch capability the Flex recognizer names: the capture-context sidecar a create
|
|
5
|
+
* must carry. syncRegistry declares it (SUPPORTED_CAPABILITIES), because this build reads the
|
|
6
|
+
* context and sends it; the API serves the recognizer only to a client that does.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CYBERSOURCE_FLEX_CAPABILITY = "capture_context";
|
|
9
|
+
/** The Microform v2 card fields the cardholder's device can rebuild. */
|
|
10
|
+
export type MicroformField = 'number' | 'securityCode';
|
|
11
|
+
/**
|
|
12
|
+
* The Microform fields of one payment, as the create carries them: the field iframe's version,
|
|
13
|
+
* the fields that loaded (the number field always, the security code field when the merchant
|
|
14
|
+
* created it), and the field whose iframe sent this request. The device rebuilds the iframe's
|
|
15
|
+
* exact plaintext from them, and the triggering field's data comes first in it.
|
|
16
|
+
*/
|
|
17
|
+
export interface MicroformSidecar {
|
|
18
|
+
version: string;
|
|
19
|
+
fields: MicroformField[];
|
|
20
|
+
trigger: MicroformField;
|
|
21
|
+
}
|
|
22
|
+
/** request.capture_context and request.microform on the create. */
|
|
23
|
+
export interface CybersourceFlexSidecar {
|
|
24
|
+
/** The capture context, byte for byte as the field iframe's URL fragment carries it. */
|
|
25
|
+
capture_context: string;
|
|
26
|
+
microform: MicroformSidecar;
|
|
27
|
+
}
|
|
28
|
+
/** A Flex card request the claim flow may take over. */
|
|
29
|
+
export interface CybersourceFlexPause {
|
|
30
|
+
sidecar: CybersourceFlexSidecar;
|
|
31
|
+
/**
|
|
32
|
+
* The request's shape for the retry rule (cdp.ts sameCheckoutRequest): microform type, fields,
|
|
33
|
+
* the context's key id and the verified capture context itself. Every createToken seals a fresh
|
|
34
|
+
* JWE, so the body itself never repeats; a retry under the same context and fields carries this
|
|
35
|
+
* same shape, and a field reloaded under any other context never reuses the approval, even one
|
|
36
|
+
* whose key id is the same.
|
|
37
|
+
*/
|
|
38
|
+
shape: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Why a Flex card request is not one this build can pay; reported on the unsupported_checkout
|
|
42
|
+
* event. Each is a fact about the page that its next request repeats: a request that is not a
|
|
43
|
+
* Microform card field's (including one from a frame this attachment read and found to be no
|
|
44
|
+
* Microform field), an unreviewed field version, or fields the device cannot rebuild.
|
|
45
|
+
*/
|
|
46
|
+
export type MicroformUnsupportedReason = 'microform_request_unrecognized' | 'microform_version_unreviewed' | 'microform_fields_unsupported';
|
|
47
|
+
/**
|
|
48
|
+
* Why this one request is refused while the next may pass; reported on the blocked event.
|
|
49
|
+
* `microform_frame_unread`: the frame that sent it could not be read (a missed event, a frame
|
|
50
|
+
* tree read that failed or timed out), which the page's next request may not repeat.
|
|
51
|
+
*/
|
|
52
|
+
export type CybersourceFlexRefusal = 'capture_context_invalid' | 'capture_context_mismatch' | 'request_body_unreadable' | 'microform_frame_unread';
|
|
53
|
+
export type CybersourceFlexDecision = {
|
|
54
|
+
kind: 'pause';
|
|
55
|
+
flex: CybersourceFlexPause;
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'continue';
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'unsupported';
|
|
60
|
+
reason: MicroformUnsupportedReason;
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'refuse';
|
|
63
|
+
reason: CybersourceFlexRefusal;
|
|
64
|
+
cause?: string;
|
|
65
|
+
};
|
|
66
|
+
/** The paused request, as the pre-claim gates see it. */
|
|
67
|
+
export interface FlexRequest {
|
|
68
|
+
url: string;
|
|
69
|
+
method: string;
|
|
70
|
+
headers: Record<string, string>;
|
|
71
|
+
body: string | null;
|
|
72
|
+
}
|
|
73
|
+
/** An https URL on flex.cybersource.com or testflex.cybersource.com, with no port or credentials. */
|
|
74
|
+
export declare function isCybersourceFlexHost(url: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* How an adapter fails a card request it will not let through: a CyberSource Flex request as a
|
|
77
|
+
* network error (Playwright 'failed', CDP 'Failed', net::ERR_FAILED), every other request as an
|
|
78
|
+
* abort (net::ERR_ABORTED). Microform 2.12.1's field reports a failed request to the merchant's page
|
|
79
|
+
* at once (createToken's callback gets CREATE_TOKEN_XHR_ERROR), so the page runs its own error path:
|
|
80
|
+
* a message, a retry under the same capture context, or GetPay's reload under a new one. It never
|
|
81
|
+
* reports an aborted request, so after a refusal or a decline the page's createToken would wait with
|
|
82
|
+
* no answer. Both were observed with the real field on CyberSource's sandbox.
|
|
83
|
+
*/
|
|
84
|
+
export declare function refusedRequestFailure(url: string): 'failed' | 'aborted';
|
|
85
|
+
/** A Microform field iframe's URL, read: payment-core's parseMicroformFragment. */
|
|
86
|
+
type MicroformFrame = {
|
|
87
|
+
environment: string;
|
|
88
|
+
version: string;
|
|
89
|
+
reviewed: boolean;
|
|
90
|
+
microformType: string;
|
|
91
|
+
microformId: string | null;
|
|
92
|
+
fieldType: string;
|
|
93
|
+
captureContext: string;
|
|
94
|
+
};
|
|
95
|
+
type VerifiedContext = {
|
|
96
|
+
url: string;
|
|
97
|
+
environment: string;
|
|
98
|
+
exp: number;
|
|
99
|
+
jwk: {
|
|
100
|
+
kid: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
type Verification = {
|
|
104
|
+
ok: true;
|
|
105
|
+
context: VerifiedContext;
|
|
106
|
+
} | {
|
|
107
|
+
ok: false;
|
|
108
|
+
cause: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Each capture context verified once, offline, against the keys payment-core pins, and
|
|
112
|
+
* remembered for the attachment. The adapters start a context's check when its field iframe
|
|
113
|
+
* loads, so by the time the page sends its card the answer is usually settled and the pause
|
|
114
|
+
* stays synchronous. Bounded: the oldest context is dropped past 32.
|
|
115
|
+
*/
|
|
116
|
+
export declare class CaptureContexts {
|
|
117
|
+
private readonly entries;
|
|
118
|
+
verify(jwt: string): Verification | Promise<Verification>;
|
|
119
|
+
/** Start the check of a card field iframe's capture context from the frame's URL, when it is one. */
|
|
120
|
+
warm(url: string | undefined): void;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Judge one POST to a Flex host, given the frame that sent it and every Microform frame this
|
|
124
|
+
* attachment knows. `issuing` is that frame read as a Microform field: `undefined` when its URL
|
|
125
|
+
* could not be read at all, `null` when it was read and is no Microform v2 field (the merchant's
|
|
126
|
+
* own page or frame, which is where Flex API direct traffic comes from). In order:
|
|
127
|
+
* 1. a check (eCheck) Microform: continue, it carries no card;
|
|
128
|
+
* 2. a body the adapter could not read: refuse this request;
|
|
129
|
+
* 3. not a field iframe's sealed card (application/jwt, a Microform JWE, the exact tokens
|
|
130
|
+
* URL): unsupported, Microform v0.x and Flex API direct post here too;
|
|
131
|
+
* 4. a frame that could not be read: refuse this request, since the read can fail for a
|
|
132
|
+
* reason the page's next request does not repeat (a missed event, a slow frame tree read);
|
|
133
|
+
* 5. a frame read and found to be no Microform field, an unreviewed version, or fields the
|
|
134
|
+
* device cannot rebuild: unsupported, as the page's next request repeats each of them;
|
|
135
|
+
* 6. a capture context that does not verify, has expired, names another URL or host, or did
|
|
136
|
+
* not seal this body: refuse this request;
|
|
137
|
+
* 7. otherwise pause, with the sidecar the create carries.
|
|
138
|
+
* Returns synchronously when the context's verification already settled.
|
|
139
|
+
*/
|
|
140
|
+
export declare function judgeMicroformRequest(request: FlexRequest, issuing: MicroformFrame | null | undefined, frames: Iterable<MicroformFrame>, contexts: CaptureContexts): CybersourceFlexDecision | Promise<CybersourceFlexDecision>;
|
|
141
|
+
/**
|
|
142
|
+
* The CDP adapter's record of Microform field iframes, keyed by frame id (an out-of-process
|
|
143
|
+
* iframe's target id is its frame id). Fed only from CDP sources that report a document the
|
|
144
|
+
* frame COMMITTED, with its fragment: Page.frameNavigated (the child's own session for an
|
|
145
|
+
* out-of-process iframe, the parent's for an in-process one), the iframe target's url on
|
|
146
|
+
* Target.attachedToTarget when the SDK attaches to a form that already rendered, and the
|
|
147
|
+
* pausing session's Page.getFrameTree as a last read. A document request (Network.
|
|
148
|
+
* requestWillBeSent, Fetch.requestPaused) is only a hint that warms the context's check: a page
|
|
149
|
+
* can start a navigation to a genuine field URL in a frame it scripts and stop it before it
|
|
150
|
+
* commits, and that frame then still runs the page's own document. A frame that navigates
|
|
151
|
+
* elsewhere or detaches is forgotten. Bounded to the 64 most recent frames.
|
|
152
|
+
*/
|
|
153
|
+
export declare class MicroformFrames {
|
|
154
|
+
private readonly frames;
|
|
155
|
+
readonly contexts: CaptureContexts;
|
|
156
|
+
/** Start the capture context's check from a navigation that has not committed; records nothing. */
|
|
157
|
+
warm(url: string | undefined): void;
|
|
158
|
+
/** A frame's committed document URL, fragment included. A URL that is not a field iframe's forgets the frame. */
|
|
159
|
+
observe(frameId: unknown, url: unknown): void;
|
|
160
|
+
forget(frameId: unknown): void;
|
|
161
|
+
/**
|
|
162
|
+
* Judge a POST the frame `frameId` sent. When that frame is not on record, `reread` reads the
|
|
163
|
+
* pausing session's frame tree once (recording what it finds) and resolves to the frame's
|
|
164
|
+
* committed URL, or undefined when the frame was not found or the read failed: a frame read and
|
|
165
|
+
* found to be no field is judged as such, one never read is refused for this request alone.
|
|
166
|
+
*/
|
|
167
|
+
judge(request: FlexRequest, frameId: string | undefined, reread?: () => Promise<string | undefined>): CybersourceFlexDecision | Promise<CybersourceFlexDecision>;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The Playwright adapter's judge: Playwright keeps a frame's fragment in frame.url() (checked in
|
|
171
|
+
* cybersource-flex-browser.test.mjs) and moves frame.url() only when a navigation commits, so the
|
|
172
|
+
* issuing frame and its siblings are read at pause time. `issuingUrl` is undefined when the
|
|
173
|
+
* request's frame could not be read at all.
|
|
174
|
+
*/
|
|
175
|
+
export declare function judgeMicroformFrameUrls(request: FlexRequest, issuingUrl: string | undefined, frameUrls: Iterable<string | undefined>, contexts: CaptureContexts): CybersourceFlexDecision | Promise<CybersourceFlexDecision>;
|
|
176
|
+
/**
|
|
177
|
+
* Whether a create's sidecar has the shape the API admits (routes/api/v2/checkout.ts
|
|
178
|
+
* createSchema): a capture context of 1 to 16384 characters, and a Microform version with a
|
|
179
|
+
* number field, an optional security code field and a trigger among them. authorize() checks
|
|
180
|
+
* it before any create, so a hand-built request fails locally rather than at the API.
|
|
181
|
+
*/
|
|
182
|
+
export declare function validCybersourceFlexSidecar(captureContext: unknown, microform: unknown): boolean;
|
|
183
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
declare var CYBERSOURCE_FLEX_RULES_SOURCE: Readonly<{
|
|
2
|
+
version: "2.12.1";
|
|
3
|
+
sha256: "8429ebae83e9745cd53ad8ff0d9ba69bee0087c1435ed0695a25444bed418327";
|
|
4
|
+
}>;
|
|
5
|
+
declare var CybersourceFlexError: {
|
|
6
|
+
new (reason: any, message: any): {
|
|
7
|
+
name: string;
|
|
8
|
+
message: string;
|
|
9
|
+
stack?: string;
|
|
10
|
+
cause?: unknown;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
declare function isVerifiedCaptureContext(value: any): boolean;
|
|
14
|
+
declare function flexHostEnvironment(hostname: any): "production" | "sandbox" | null;
|
|
15
|
+
declare var CYBERSOURCE_FLEX_PSP: string;
|
|
16
|
+
declare var CYBERSOURCE_FLEX_CONTENT_TYPE: string;
|
|
17
|
+
declare var CYBERSOURCE_FLEX_TOKENS_URLS: Readonly<{
|
|
18
|
+
production: `https://flex.cybersource.com${string}`;
|
|
19
|
+
sandbox: `https://testflex.cybersource.com${string}`;
|
|
20
|
+
}>;
|
|
21
|
+
declare var CYBERSOURCE_FLEX_CAPTURE_CONTEXT_MAX_LENGTH: number;
|
|
22
|
+
declare var CYBERSOURCE_FLEX_REVIEWED_IFRAME_VERSIONS: readonly string[];
|
|
23
|
+
declare var CYBERSOURCE_FLEX_SIGNING_KEYS: any;
|
|
24
|
+
declare var CYBERSOURCE_FLEX_TEST_PANS: Readonly<Set<string>>;
|
|
25
|
+
declare function verifyCaptureContext(jwt: any, { crypto, now }?: {}): Promise<Readonly<{
|
|
26
|
+
raw: any;
|
|
27
|
+
signingKid: any;
|
|
28
|
+
environment: any;
|
|
29
|
+
origin: string;
|
|
30
|
+
path: string;
|
|
31
|
+
url: string;
|
|
32
|
+
jwk: Readonly<{
|
|
33
|
+
kty: "RSA";
|
|
34
|
+
n: any;
|
|
35
|
+
e: any;
|
|
36
|
+
kid: any;
|
|
37
|
+
}>;
|
|
38
|
+
modulusBytes: number;
|
|
39
|
+
index: any;
|
|
40
|
+
contextType: any;
|
|
41
|
+
allowedCardNetworks: readonly any[] | null;
|
|
42
|
+
targetOrigins: readonly any[];
|
|
43
|
+
iat: any;
|
|
44
|
+
exp: any;
|
|
45
|
+
}>>;
|
|
46
|
+
declare function cybersourceFlexEnvironment(url: any): "production" | "sandbox" | undefined;
|
|
47
|
+
declare function parseMicroformFragment(rawUrl: any): Readonly<{
|
|
48
|
+
environment: "production" | "sandbox";
|
|
49
|
+
version: string;
|
|
50
|
+
reviewed: boolean;
|
|
51
|
+
microformType: any;
|
|
52
|
+
microformId: any;
|
|
53
|
+
fieldType: any;
|
|
54
|
+
keyId: string | null;
|
|
55
|
+
captureContext: any;
|
|
56
|
+
}> | null;
|
|
57
|
+
declare function detectCardTypes(pan: any, allowedCardNetworks: any): {
|
|
58
|
+
codes: any[];
|
|
59
|
+
valid: boolean;
|
|
60
|
+
cards: {
|
|
61
|
+
code: any;
|
|
62
|
+
name: any;
|
|
63
|
+
valid: any;
|
|
64
|
+
}[];
|
|
65
|
+
securityCode: {
|
|
66
|
+
required: boolean;
|
|
67
|
+
length: any;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
declare function cybersourceFlexTemplateKid(body: any): any;
|
|
71
|
+
declare function isCybersourceFlexTemplateBody(body: any): boolean;
|
|
72
|
+
declare function isCybersourceFlexMediaType(value: any): boolean;
|
|
73
|
+
declare function isCybersourceFlexRequest(url: any, method: any, body: any, headers: any): boolean;
|
|
74
|
+
declare function assertCybersourceFlexTemplate(body: any, verified: any): void;
|
|
75
|
+
declare function buildCardPlaintext(verified: any, card: any, { fields, trigger, now }?: {
|
|
76
|
+
trigger?: string | undefined;
|
|
77
|
+
}): string;
|
|
78
|
+
declare function sealForContext(verified: any, plaintext: any, { crypto }?: {}): Promise<string>;
|
|
79
|
+
declare var CYBERSOURCE_FLEX_REFUSAL_REASONS: readonly string[];
|
|
80
|
+
declare function cybersourceFlexRefusalCode(text: any): string | null;
|
|
81
|
+
declare function isCybersourceFlexTokenShape(text: any): boolean;
|
|
82
|
+
declare function transientTokenSigned(status: any, body: any, verified: any, { crypto }?: {}): Promise<{
|
|
83
|
+
confirmed: boolean;
|
|
84
|
+
reason: any;
|
|
85
|
+
}>;
|
|
86
|
+
declare function transientTokenFacts(status: any, body: any, verified: any, card: any, { crypto }?: {}): Promise<{
|
|
87
|
+
confirmed: boolean;
|
|
88
|
+
reason: any;
|
|
89
|
+
}>;
|
|
90
|
+
declare function cybersourceFlexPreparationUrl(environment: any): any;
|
|
91
|
+
declare function isPreparedCybersourceFlexRequest(url: any, method: any, body: any, environment: any, headers: any): boolean;
|
|
92
|
+
export { CYBERSOURCE_FLEX_CAPTURE_CONTEXT_MAX_LENGTH, CYBERSOURCE_FLEX_CONTENT_TYPE, 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, assertCybersourceFlexTemplate, buildCardPlaintext, cybersourceFlexEnvironment, flexHostEnvironment as cybersourceFlexHostEnvironment, cybersourceFlexPreparationUrl, cybersourceFlexRefusalCode, cybersourceFlexTemplateKid, detectCardTypes, isCybersourceFlexMediaType, isCybersourceFlexRequest, isCybersourceFlexTemplateBody, isCybersourceFlexTokenShape, isPreparedCybersourceFlexRequest, isVerifiedCaptureContext, parseMicroformFragment, sealForContext, transientTokenFacts, transientTokenSigned, verifyCaptureContext };
|