@agent-cards/sdk 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/PREFLIGHT.md +34 -0
  3. package/README.md +1217 -15
  4. package/dist/aci.d.ts +173 -0
  5. package/dist/aci.generated.d.ts +87 -0
  6. package/dist/aci.generated.js +306 -0
  7. package/dist/aci.js +371 -0
  8. package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
  9. package/dist/adyen-merchant-hosted.generated.js +283 -49
  10. package/dist/agentcard.d.ts +9 -2
  11. package/dist/agentcard.js +4 -2
  12. package/dist/amadeus-checkout.generated.d.ts +57 -0
  13. package/dist/amadeus-checkout.generated.js +549 -0
  14. package/dist/amadeus.d.ts +41 -0
  15. package/dist/amadeus.js +81 -0
  16. package/dist/builtin-registry.generated.js +1 -1
  17. package/dist/card-endpoint.d.ts +177 -0
  18. package/dist/card-endpoint.js +286 -0
  19. package/dist/card-fields.generated.d.ts +5 -3
  20. package/dist/card-fields.generated.js +284 -7
  21. package/dist/cdp.d.ts +6 -1
  22. package/dist/cdp.js +983 -147
  23. package/dist/client.d.ts +493 -13
  24. package/dist/client.js +1100 -82
  25. package/dist/creditguard.generated.d.ts +79 -0
  26. package/dist/creditguard.generated.js +662 -0
  27. package/dist/cse-body.js +11 -1
  28. package/dist/cybersource-flex.d.ts +183 -0
  29. package/dist/cybersource-flex.generated.d.ts +92 -0
  30. package/dist/cybersource-flex.generated.js +689 -0
  31. package/dist/cybersource-flex.js +275 -0
  32. package/dist/datatrans.d.ts +96 -0
  33. package/dist/datatrans.generated.d.ts +64 -0
  34. package/dist/datatrans.generated.js +426 -0
  35. package/dist/datatrans.js +178 -0
  36. package/dist/elavon-converge.d.ts +37 -0
  37. package/dist/elavon-converge.generated.d.ts +49 -0
  38. package/dist/elavon-converge.generated.js +439 -0
  39. package/dist/elavon-converge.js +84 -0
  40. package/dist/eprotect.d.ts +152 -0
  41. package/dist/eprotect.generated.d.ts +77 -0
  42. package/dist/eprotect.generated.js +498 -0
  43. package/dist/eprotect.js +222 -0
  44. package/dist/gmo.d.ts +63 -0
  45. package/dist/gmo.generated.d.ts +60 -0
  46. package/dist/gmo.generated.js +586 -0
  47. package/dist/gmo.js +119 -0
  48. package/dist/hosted-form.d.ts +3 -2
  49. package/dist/hosted-form.js +3 -2
  50. package/dist/index.d.ts +19 -3
  51. package/dist/index.js +6 -1
  52. package/dist/juspay-gate.d.ts +82 -0
  53. package/dist/juspay-gate.js +126 -0
  54. package/dist/juspay.generated.d.ts +89 -0
  55. package/dist/juspay.generated.js +705 -0
  56. package/dist/lifecycle.d.ts +62 -6
  57. package/dist/lifecycle.js +123 -8
  58. package/dist/merchant-card-endpoints.generated.d.ts +63 -0
  59. package/dist/merchant-card-endpoints.generated.js +1103 -0
  60. package/dist/merchant-hosted.d.ts +4 -1
  61. package/dist/merchant-hosted.js +5 -2
  62. package/dist/merchant-session-probe.d.ts +48 -0
  63. package/dist/merchant-session-probe.js +53 -0
  64. package/dist/merchant-session.generated.d.ts +69 -0
  65. package/dist/merchant-session.generated.js +919 -0
  66. package/dist/payrails.d.ts +133 -0
  67. package/dist/payrails.generated.d.ts +73 -0
  68. package/dist/payrails.generated.js +596 -0
  69. package/dist/payrails.js +305 -0
  70. package/dist/pelecard.d.ts +32 -0
  71. package/dist/pelecard.generated.d.ts +32 -0
  72. package/dist/pelecard.generated.js +283 -0
  73. package/dist/pelecard.js +42 -0
  74. package/dist/pre-claim.d.ts +84 -3
  75. package/dist/pre-claim.js +987 -9
  76. package/dist/preflight-catalog.json +2310 -263
  77. package/dist/preflight-schemas.json +187 -6
  78. package/dist/preflight.generated.d.ts +1 -1
  79. package/dist/preflight.generated.js +247 -4
  80. package/dist/preparation.d.ts +5 -2
  81. package/dist/preparation.js +36 -10
  82. package/dist/prepared-processor.d.ts +86 -5
  83. package/dist/prepared-processor.js +157 -6
  84. package/dist/primer.d.ts +53 -0
  85. package/dist/primer.generated.d.ts +38 -0
  86. package/dist/primer.generated.js +624 -0
  87. package/dist/primer.js +82 -0
  88. package/dist/registry.d.ts +114 -12
  89. package/dist/registry.js +85 -11
  90. package/dist/shiji-sts.d.ts +144 -0
  91. package/dist/shiji-sts.generated.d.ts +72 -0
  92. package/dist/shiji-sts.generated.js +913 -0
  93. package/dist/shiji-sts.js +222 -0
  94. package/dist/stripe-checkout.generated.d.ts +1 -1
  95. package/dist/stripe-checkout.generated.js +735 -10
  96. package/dist/stripe-setup.d.ts +16 -0
  97. package/dist/stripe-setup.js +61 -0
  98. package/dist/substitutions.generated.js +187 -29
  99. package/dist/verifone.d.ts +68 -0
  100. package/dist/verifone.generated.d.ts +51 -0
  101. package/dist/verifone.generated.js +1401 -0
  102. package/dist/verifone.js +119 -0
  103. package/dist/voice-types.d.ts +27 -0
  104. package/dist/voice.d.ts +6 -1
  105. package/dist/voice.js +6 -1
  106. package/dist/wspay.d.ts +41 -0
  107. package/dist/wspay.generated.d.ts +41 -0
  108. package/dist/wspay.generated.js +370 -0
  109. package/dist/wspay.js +65 -0
  110. package/examples/preflight/kernel-native/inventory.json +1 -1
  111. package/package.json +3 -3
package/dist/primer.js ADDED
@@ -0,0 +1,82 @@
1
+ // Primer Web SDK v2 (Headless Checkout and hosted fields), as the SDK sees it.
2
+ //
3
+ // Printful (printful.com) and GetYourGuide (getyourguide.com) take cards through Primer's hosted
4
+ // fields. The merchant page never holds the card: Primer's hosted inputs hand it to a hidden
5
+ // api-controller iframe on https://sdk.primer.io, which sends one fetch POST to
6
+ // https://sdk.api.primer.io/payment-instruments (sdk.api.sandbox.primer.io in sandbox) as
7
+ // application/json with the session's primer-client-token, and gets back a card token the SDK or
8
+ // the merchant's server charges later. The request names no amount. The cardholder's device fills
9
+ // the real card into the paused body and sends it to Primer itself; the page receives only Primer's
10
+ // single-use token (token mode). The request comes from a cross-origin iframe, so the CDP adapter
11
+ // pauses it in the iframe's own target, as it does for Spreedly's card frame.
12
+ //
13
+ // The registry entry ships dark (capability primer_payment_instruments). This build declares the
14
+ // capability on syncRegistry, and the API serves the entry only to an org Agentcard enabled for
15
+ // Primer, so for every other org nothing here fires: isCardRequest never recognizes a Primer URL and
16
+ // Primer's requests go through untouched, exactly as before.
17
+ //
18
+ // For an org in the dark launch, a POST the registry recognizes on a Primer pci host is judged by
19
+ // payment-core's own pause decision (primer.generated.ts classifyPrimerRequest), the rule the API
20
+ // runs at create and the device runs before it fills anything:
21
+ // - a one-time new card from the legacy v2 controller pauses for approval;
22
+ // - a body with no card (PayPal, Apple Pay, Google Pay, Klarna) continues untouched;
23
+ // - a body that asks Primer to keep the card (tokenType MULTI_USE, paymentFlow VAULT) is aborted
24
+ // alone: a save-card choice on the page decides it, so the next request is judged again;
25
+ // - Primer Checkout (primer-js), whose controller gives up after 30 s, and any other shape the
26
+ // legacy controller would not send are aborted as an unsupported checkout before any create or
27
+ // prompt, and the attachment holds: the merchant's integration decides those.
28
+ import { PRIMER_PCI_HOSTS, classifyPrimerRequest, primerResponseOutcome, sanitizePrimerResponse } from './primer.generated.js';
29
+ export const PRIMER_PSP = 'primer';
30
+ /** The dark-launch capability a build declares when it judges every Primer request with classifyPrimerRequest. */
31
+ export const PRIMER_CAPABILITY = 'primer_payment_instruments';
32
+ /** True for a URL on one of Primer's two pci hosts, whatever its path. */
33
+ export function isPrimerPciHost(rawUrl) {
34
+ try {
35
+ return PRIMER_PCI_HOSTS.includes(new URL(rawUrl).hostname.toLowerCase());
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * The gate's verdict for a POST the registry recognized on a Primer pci host: defer (a new card,
43
+ * which the arbiter pauses, or a body with no card, which the card-field rule continues), block it
44
+ * alone, or abort it as an unsupported checkout. A decision that throws refuses.
45
+ */
46
+ export function primerRequestVerdict(request) {
47
+ if (request.body === null)
48
+ return { kind: 'block', reason: 'unreadable_primer_request' };
49
+ let decision;
50
+ try {
51
+ decision = classifyPrimerRequest(request.url, request.method.toUpperCase(), request.body, request.headers ?? {});
52
+ }
53
+ catch {
54
+ return { kind: 'unsupported', reason: 'unsupported_primer_request' };
55
+ }
56
+ if (decision === 'card' || decision === null)
57
+ return { kind: 'defer' };
58
+ if (decision === 'keep_on_file')
59
+ return { kind: 'block', reason: 'primer_keep_on_file' };
60
+ if (decision === 'unsupported_sdk')
61
+ return { kind: 'unsupported', reason: 'unsupported_primer_sdk' };
62
+ return { kind: 'unsupported', reason: 'unsupported_primer_request' };
63
+ }
64
+ /**
65
+ * The only answer the page may receive for an approved Primer request: status 200 or 201 and
66
+ * Primer's single-use card token rebuilt from payment-core's allow-list (sanitizePrimerResponse),
67
+ * under a JSON content type. Null for anything else: another status, a multi-use token or vaultData
68
+ * (the card was saved), a field out of bounds, a body that does not parse.
69
+ */
70
+ export function primerTokenAnswer(status, body) {
71
+ if (status !== 200 && status !== 201)
72
+ return null;
73
+ try {
74
+ // No card here: the device already matched the token to the card it sent.
75
+ if (primerResponseOutcome(status, body, null).outcome !== 'approved')
76
+ return null;
77
+ return { status, headers: { 'content-type': 'application/json' }, body: sanitizePrimerResponse(body) };
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
@@ -6,7 +6,12 @@
6
6
  * site shares another. The list is served from the Agentcard API at runtime so
7
7
  * integrators pick up new processors without shipping a release.
8
8
  */
9
- export type Encoding = 'json' | 'form';
9
+ /**
10
+ * The shape of the card-bearing body. `jwe`: a compact JWE the processor's own card field
11
+ * sealed (CyberSource Flex), opaque to everyone but the processor; served only with the
12
+ * capability that reads its capture context.
13
+ */
14
+ export type Encoding = 'json' | 'form' | 'jwe';
10
15
  /**
11
16
  * How a paused request gets its card. `token` (the default when absent): the
12
17
  * cardholder's device calls the processor and the runtime replays the
@@ -15,8 +20,13 @@ export type Encoding = 'json' | 'form';
15
20
  * the paused body and lets the request continue. `hosted_form`: the device
16
21
  * submits the processor's own form and the paused request resolves with no
17
22
  * processor answer (served only to SDKs that ask for it on sync).
23
+ * `device_encrypt`: the card is encrypted in-page under a per-session key the
24
+ * paused request does not carry (Payrails); the runtime supplies the key the
25
+ * page's init payload named, the device encrypts the real card and sends the
26
+ * request itself, and the runtime replays the processor's answer as in
27
+ * `token` (served only to SDKs that ask for it on sync).
18
28
  */
19
- export type CheckoutMode = 'token' | 'cse' | 'hosted_form';
29
+ export type CheckoutMode = 'token' | 'cse' | 'hosted_form' | 'device_encrypt';
20
30
  export interface Recognizer {
21
31
  /** Stable id, e.g. "shopify" */
22
32
  psp: string;
@@ -31,7 +41,8 @@ export interface Recognizer {
31
41
  * here matches with them: they exist so a transport that can only pre-filter
32
42
  * by URL (CDP's `Fetch.enable`, which takes globs) can be armed from the
33
43
  * registry instead of a hardcoded list. Optional so a hand-built registry
34
- * (tests, pinning) still type-checks.
44
+ * (tests, pinning) still type-checks. An empty list names no host, as on the
45
+ * server: the entry arms no pattern and claims no URL.
35
46
  */
36
47
  hosts?: string[];
37
48
  encoding: Encoding;
@@ -58,6 +69,24 @@ export interface Recognizer {
58
69
  * is not. Carried through syncRegistry() verbatim.
59
70
  */
60
71
  jsonCardFields?: (string | number)[][];
72
+ /**
73
+ * True when the processor reads form field names without regard to case
74
+ * (Authorize.Net: live forms post x_Background_URL and x_cust_ID), so
75
+ * cardFields match a key in any case and with surrounding spaces trimmed:
76
+ * a form that spells X_CARD_NUM is a card request like one that spells
77
+ * x_card_num. Carried verbatim by syncRegistry and read by card-fields.js.
78
+ */
79
+ caseInsensitiveFormKeys?: boolean;
80
+ /**
81
+ * True when the endpoint is the bare URL: a request whose URL carries a
82
+ * query string or a fragment, even an empty `?` or `#`, is not this
83
+ * processor's endpoint, so findRecognizer does not match it and the request
84
+ * continues untouched, exactly as the API refuses it
85
+ * (resolveRecognizerForUrl). Authorize.Net's transact.dll reads a query
86
+ * parameter as a form field, so a key the template policy never saw would
87
+ * ride with the card. Carried verbatim by syncRegistry.
88
+ */
89
+ bareEndpoint?: boolean;
61
90
  /**
62
91
  * With cardFields or jsonCardFields: the endpoints (regex source over
63
92
  * hostname + pathname, anchored) where a request without a card is routine
@@ -69,19 +98,39 @@ export interface Recognizer {
69
98
  * True when the card is encrypted inside the page before the request leaves.
70
99
  * A digit swap is useless here; the vault must re-run the PSP's client-side
71
100
  * crypto instead. Refused locally unless the entry also carries
72
- * `mode: 'cse'`, which says the vault does exactly that.
101
+ * `mode: 'cse'` or `mode: 'device_encrypt'`, which say the vault does
102
+ * exactly that.
73
103
  */
74
104
  clientSideEncrypted?: boolean;
75
105
  /** How the card reaches the processor; absent means 'token'. Carried verbatim by syncRegistry. */
76
106
  mode?: CheckoutMode;
77
107
  /**
78
108
  * Dark launch: a capability name the API attaches to an entry it serves only
79
- * to a client that declared it (?capabilities=). Absent on every entry in
80
- * BUILTIN_REGISTRY, because a gated entry is left out of the built-in
81
- * fallback. Carried verbatim by syncRegistry for a client that does declare
82
- * it, so the SDK never has to act on it.
109
+ * to a client that declared it (?capabilities=), from an org Agentcard
110
+ * enabled for the processor. Absent on every entry in BUILTIN_REGISTRY,
111
+ * because a gated entry is left out of the built-in fallback. Carried
112
+ * verbatim by syncRegistry for a client that does declare it
113
+ * (SUPPORTED_CAPABILITIES). authorize() refuses an entry whose capability
114
+ * this build does not list in SUPPORTED_CAPABILITIES
115
+ * (UnsupportedCapabilityError), which only a hand-built registry can hold.
116
+ * GMO names `gmo_shared_pk`, CyberSource Flex names `capture_context`,
117
+ * Datatrans names `datatrans_secure_fields_tokenize`, Authorize.Net DPM
118
+ * names `authorize_net_dpm_form`, Pelecard names `pelecard_transaction`,
119
+ * Primer names `primer_payment_instruments`, Payrails names
120
+ * `payrails_device_encrypt`, Elavon Converge names `converge_hpp_process`,
121
+ * WSPay names `wspay_confirm` and Shiji STS names `shiji_sts_pinned_key`.
83
122
  */
84
123
  capability?: string;
124
+ /**
125
+ * The card-on-file policy the API applies to this processor's card post,
126
+ * served with the entry: `refuse`, `disclose` or `agentcard_single_use`
127
+ * (Datatrans: a post with usage=REGISTRATION). The SDK refuses such a post in
128
+ * the browser only while this is `refuse`, absent or a value it does not
129
+ * know; otherwise the post pauses and the API and the cardholder's device
130
+ * apply the policy. Read at each sync, so a policy change needs no SDK
131
+ * release.
132
+ */
133
+ cardOnFile?: string;
85
134
  /**
86
135
  * True when this processor cannot be taken over on an ordinary authorization:
87
136
  * the cardholder must have approved a prepare() first. authorize() refuses
@@ -102,11 +151,39 @@ export interface Recognizer {
102
151
  /**
103
152
  * True when a processor's host environment is enforced: the API refuses a
104
153
  * create whose host environment disagrees with the row's test/live nature, and
105
- * the device sends only documented test cards to a sandbox host. Absent on
106
- * every entry today; carried verbatim by syncRegistry, and the SDK never acts
154
+ * the device sends only documented test cards to a sandbox host. GMO sets it
155
+ * (stg.token.mul-pay.jp is its test host), and so does Datatrans
156
+ * (pay.sandbox.datatrans.com is its sandbox host), and so does Primer
157
+ * (sdk.api.sandbox.primer.io is its sandbox host), and so does Payrails (its
158
+ * two staging hosts are test hosts), and so does WSPay (proxy-test.wspay.biz
159
+ * is its test host), and so does Shiji STS (iframe.vault.eu.uat.shijipayment.com
160
+ * is its sandbox host); as dark entries none is ever in
161
+ * BUILTIN_REGISTRY. Carried verbatim by syncRegistry, and the SDK never acts
107
162
  * on it.
108
163
  */
109
164
  hostEnvironmentPinned?: boolean;
165
+ /**
166
+ * The card request's shape when it is not a POST body. Worldpay eProtect
167
+ * declares `{ method: 'GET', cardIn: 'query', response: 'jsonp' }`: its card
168
+ * rides in the query of one JSONP <script> GET. Absent means a POST. Carried
169
+ * verbatim by syncRegistry; isCardRequest reads `method` through
170
+ * recognizerRequestMethod, and pauses a GET only for a processor whose exact
171
+ * query this build can check.
172
+ */
173
+ request?: {
174
+ method: 'GET' | 'POST';
175
+ cardIn?: string;
176
+ response?: string;
177
+ };
178
+ /**
179
+ * Anchored regex SOURCES of hostnames the SDK pauses only to ABORT: Worldpay
180
+ * eProtect's failover host, where the vendor script re-sends the page's
181
+ * registration 5 seconds after an unanswered primary. Never a card
182
+ * destination: not in `hosts`, never recognized. cardUrlPatterns arms them so
183
+ * a raw CDP connection can refuse the re-send. Carried verbatim by
184
+ * syncRegistry.
185
+ */
186
+ abortHosts?: string[];
110
187
  }
111
188
  import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
112
189
  export { BUILTIN_REGISTRY };
@@ -124,7 +201,31 @@ export interface RecognizerOptions {
124
201
  export declare function isLoopbackUrl(parsed: URL): boolean;
125
202
  /** The glob patterns that pause every loopback request under a sandbox client; `isCardRequest` is the exact check. */
126
203
  export declare const LOOPBACK_URL_PATTERNS: readonly string[];
204
+ /**
205
+ * The URLs a recognizer's own rules read for `url`. With sandbox credentials a loopback fixture's
206
+ * request is read on each of the processor's hosts whose endpoint its path matches (decision 178, the
207
+ * way findRecognizer recognises it), so a rule written against the processor's host, such as the
208
+ * card-free endpoints a catalog entry names, answers on the fixture as it would on the processor.
209
+ * Every other URL is read as it is.
210
+ */
211
+ export declare function urlsOnProcessorHosts(url: string, rec: Recognizer, opts?: RecognizerOptions): string[];
127
212
  export declare function findRecognizer(url: string, registry?: Recognizer[], opts?: RecognizerOptions): Recognizer | null;
213
+ /**
214
+ * The HTTP method of a recognizer's card request, the rule payment-core's
215
+ * recognizerRequestMethod applies: GET only when the entry declares
216
+ * `request.method: 'GET'` (Worldpay eProtect), POST for everything else, so a
217
+ * value that rode in over the wire as anything else never widens a POST
218
+ * recognizer.
219
+ */
220
+ export declare function recognizerRequestMethod(rec: Pick<Recognizer, 'request'>): 'GET' | 'POST';
221
+ /**
222
+ * Whether `method` is this recognizer's card request method for `url`. A GET
223
+ * carries the card in its URL, so a GET recognizer is a card request only for a
224
+ * processor whose exact query this build checks (Worldpay eProtect, through
225
+ * payment-core's grammar); any other GET recognizer is nothing this build can
226
+ * complete and is never paused.
227
+ */
228
+ export declare function isRecognizerRequest(rec: Recognizer, url: string, method: string): boolean;
128
229
  /**
129
230
  * True when this processor cannot be taken over on an ordinary authorization:
130
231
  * the cardholder must have approved a prepare() first (the `preparationRequired`
@@ -133,8 +234,9 @@ export declare function findRecognizer(url: string, registry?: Recognizer[], opt
133
234
  * preparation_required for the same case. This is the SAME rule payment-core's
134
235
  * recognizerRequiresPreparation applies on the server, a strict `=== true`, so a
135
236
  * value that rode in over the wire (syncRegistry spreads the served JSON) cannot
136
- * turn a truthy string into a gate. Absent on every BUILTIN_REGISTRY entry, so
137
- * this is false for all 24 today.
237
+ * turn a truthy string into a gate. Absent on every BUILTIN_REGISTRY entry; only
238
+ * aci sets it, and aci reaches a client only through syncRegistry once the
239
+ * client declares its capability.
138
240
  */
139
241
  export declare function recognizerRequiresPreparation(rec: Pick<Recognizer, 'preparationRequired'>): boolean;
140
242
  /**
package/dist/registry.js CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
  // Pinned fallback generated from the single shared processor catalog.
10
10
  import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
11
+ import { isEprotectTokenRequest } from './eprotect.generated.js';
12
+ import { EPROTECT_PSP } from './eprotect.js';
11
13
  export { BUILTIN_REGISTRY };
12
14
  /** True for a plain-http loopback address (`127.0.0.1` or `localhost`, any port); the only http a sandbox recogniser admits. */
13
15
  export function isLoopbackUrl(parsed) {
@@ -29,6 +31,27 @@ function literalHosts(sources) {
29
31
  }
30
32
  return out;
31
33
  }
34
+ /**
35
+ * The URLs a recognizer's own rules read for `url`. With sandbox credentials a loopback fixture's
36
+ * request is read on each of the processor's hosts whose endpoint its path matches (decision 178, the
37
+ * way findRecognizer recognises it), so a rule written against the processor's host, such as the
38
+ * card-free endpoints a catalog entry names, answers on the fixture as it would on the processor.
39
+ * Every other URL is read as it is.
40
+ */
41
+ export function urlsOnProcessorHosts(url, rec, opts = {}) {
42
+ let parsed;
43
+ try {
44
+ parsed = new URL(url);
45
+ }
46
+ catch {
47
+ return [url];
48
+ }
49
+ if (!opts.sandbox || !isLoopbackUrl(parsed))
50
+ return [url];
51
+ const anchored = new RegExp(`^(?:${rec.match.source})$`, 'i');
52
+ const hosts = literalHosts(rec.hosts).filter((host) => anchored.test(`${host}${parsed.pathname}`));
53
+ return hosts.length > 0 ? hosts.map((host) => `https://${host}${parsed.pathname}${parsed.search}`) : [url];
54
+ }
32
55
  export function findRecognizer(url, registry = BUILTIN_REGISTRY, opts = {}) {
33
56
  // Match the way the SERVER does, on the PARSED url — never as a substring of
34
57
  // the raw string. `match` is unanchored, so a raw-string test classifies any
@@ -52,8 +75,11 @@ export function findRecognizer(url, registry = BUILTIN_REGISTRY, opts = {}) {
52
75
  }
53
76
  // Decision 178: with sandbox credentials a loopback fixture store is judged by its path, as if it were
54
77
  // on each processor's own host; nothing else about the rule changes, and production never gets here.
78
+ // A bare endpoint keeps its rule below: no query string or fragment, as the API refuses one.
55
79
  if (opts.sandbox && isLoopbackUrl(parsed)) {
56
80
  return (registry.find((r) => {
81
+ if (r.bareEndpoint === true && /[?#]/.test(parsed.href))
82
+ return false;
57
83
  const anchored = new RegExp(`^(?:${r.match.source})$`, 'i');
58
84
  return literalHosts(r.hosts).some((host) => anchored.test(`${host}${parsed.pathname}`));
59
85
  }) ?? null);
@@ -62,18 +88,54 @@ export function findRecognizer(url, registry = BUILTIN_REGISTRY, opts = {}) {
62
88
  return null;
63
89
  const hostPath = `${parsed.hostname.toLowerCase()}${parsed.pathname}`;
64
90
  return (registry.find((r) => {
65
- if (r.hosts && r.hosts.length > 0) {
66
- const hostAllowed = r.hosts.some((h) => new RegExp(h, 'i').test(parsed.hostname));
67
- if (!hostAllowed)
68
- return false;
69
- }
91
+ // hosts[], when the entry carries it, is the server's own allowlist, which admits no host it
92
+ // does not name: an entry served with none (verifone, found only through a reviewed merchant
93
+ // profile, verifone.ts) claims no URL here either.
94
+ if (Array.isArray(r.hosts) && !r.hosts.some((h) => new RegExp(h, 'i').test(parsed.hostname)))
95
+ return false;
96
+ // A bare endpoint (bareEndpoint, Authorize.Net's transact.dll) is not
97
+ // matched with any query string or fragment, even an empty `?` or `#`,
98
+ // exactly as the server refuses it: the parsed href keeps both, and a `?`
99
+ // or `#` inside the path is already percent-encoded there. A strict
100
+ // `=== true`, so a truthy value that rode in over the wire is not a rule.
101
+ if (r.bareEndpoint === true && /[?#]/.test(parsed.href))
102
+ return false;
70
103
  // FULLY ANCHORED, exactly as the server does it. Unanchored, any EXTENSION
71
104
  // of a valid path is admitted on a legitimate processor host
72
105
  // (api.stripe.com/v1/tokens/anything), which the service then refuses —
73
106
  // and a pause the SDK cannot complete becomes an abort.
74
- return new RegExp(`^(?:${r.match.source})$`, 'i').test(hostPath);
107
+ if (!new RegExp(`^(?:${r.match.source})$`, 'i').test(hostPath))
108
+ return false;
109
+ // Worldpay eProtect's card rides in the query of a GET, so the query is
110
+ // the request: as on the server (payment-core resolveRecognizerForUrl),
111
+ // only the vendor's exact registration is recognized, never another
112
+ // /eProtect/paypage call.
113
+ return r.psp !== EPROTECT_PSP || isEprotectTokenRequest(url, 'GET');
75
114
  }) ?? null);
76
115
  }
116
+ /**
117
+ * The HTTP method of a recognizer's card request, the rule payment-core's
118
+ * recognizerRequestMethod applies: GET only when the entry declares
119
+ * `request.method: 'GET'` (Worldpay eProtect), POST for everything else, so a
120
+ * value that rode in over the wire as anything else never widens a POST
121
+ * recognizer.
122
+ */
123
+ export function recognizerRequestMethod(rec) {
124
+ return rec?.request?.method === 'GET' ? 'GET' : 'POST';
125
+ }
126
+ /**
127
+ * Whether `method` is this recognizer's card request method for `url`. A GET
128
+ * carries the card in its URL, so a GET recognizer is a card request only for a
129
+ * processor whose exact query this build checks (Worldpay eProtect, through
130
+ * payment-core's grammar); any other GET recognizer is nothing this build can
131
+ * complete and is never paused.
132
+ */
133
+ export function isRecognizerRequest(rec, url, method) {
134
+ const expected = recognizerRequestMethod(rec);
135
+ if (typeof method !== 'string' || method.toUpperCase() !== expected)
136
+ return false;
137
+ return expected === 'POST' || (rec.psp === EPROTECT_PSP && isEprotectTokenRequest(url, 'GET'));
138
+ }
77
139
  /**
78
140
  * True when this processor cannot be taken over on an ordinary authorization:
79
141
  * the cardholder must have approved a prepare() first (the `preparationRequired`
@@ -82,8 +144,9 @@ export function findRecognizer(url, registry = BUILTIN_REGISTRY, opts = {}) {
82
144
  * preparation_required for the same case. This is the SAME rule payment-core's
83
145
  * recognizerRequiresPreparation applies on the server, a strict `=== true`, so a
84
146
  * value that rode in over the wire (syncRegistry spreads the served JSON) cannot
85
- * turn a truthy string into a gate. Absent on every BUILTIN_REGISTRY entry, so
86
- * this is false for all 24 today.
147
+ * turn a truthy string into a gate. Absent on every BUILTIN_REGISTRY entry; only
148
+ * aci sets it, and aci reaches a client only through syncRegistry once the
149
+ * client declares its capability.
87
150
  */
88
151
  export function recognizerRequiresPreparation(rec) {
89
152
  return rec.preparationRequired === true;
@@ -118,8 +181,19 @@ export function cardUrlPatterns(registry) {
118
181
  return [...out];
119
182
  }
120
183
  function recognizerUrlPatterns(rec) {
121
- const hosts = Array.isArray(rec?.hosts) ? rec.hosts.filter((h) => typeof h === 'string') : [];
122
- if (hosts.length > 0) {
184
+ // Abort-only hosts are armed like card hosts: a request there has to be
185
+ // paused before the adapters can refuse it (Worldpay eProtect's failover).
186
+ const aborts = Array.isArray(rec?.abortHosts) ? rec.abortHosts.filter((h) => typeof h === 'string') : [];
187
+ return [...cardHostPatterns(rec), ...aborts.flatMap((h) => regexSourceToGlobs(h)).map((g) => wrap(g).toLowerCase())];
188
+ }
189
+ function cardHostPatterns(rec) {
190
+ if (Array.isArray(rec?.hosts)) {
191
+ // An entry that carries hosts[] carries the server's own allowlist, and the server admits no
192
+ // host it does not name (resolveRecognizerForUrl), so an entry served with none arms nothing.
193
+ // Verifone is one: its card request goes to the merchant's own server and is found only through
194
+ // a reviewed merchant profile (verifone.ts). Widening its never-matching `match` instead would
195
+ // arm `*` and pause every request on every page.
196
+ //
123
197
  // Host text ANYWHERE in the URL, not `*://host/*`. Two reasons: a URL with
124
198
  // userinfo (`https://u:p@host/…`) still has `host` as its hostname so the
125
199
  // registry matches it, and isCardRequest tests `match` unanchored against
@@ -133,7 +207,7 @@ function recognizerUrlPatterns(rec) {
133
207
  // normalization resolveRecognizerForUrl does with url.hostname. Do not
134
208
  // "fix" this by wildcarding the letters: `*???.??????.???*` matches most of
135
209
  // the web and would pause every request on the page.
136
- return hosts.flatMap((h) => regexSourceToGlobs(h)).map((g) => wrap(g).toLowerCase());
210
+ return rec.hosts.filter((h) => typeof h === 'string').flatMap((h) => regexSourceToGlobs(h)).map((g) => wrap(g).toLowerCase());
137
211
  }
138
212
  // No hosts[] — a registry pinned before the field existed, or hand-built.
139
213
  // Fall back to widening `match`, which is the predicate isCardRequest
@@ -0,0 +1,144 @@
1
+ import { SHIJI_STS_COMPANION_WINDOW_MS, SHIJI_STS_PSP, ShijiStsError, isShijiStsHost } from './shiji-sts.generated.js';
2
+ export { SHIJI_STS_COMPANION_WINDOW_MS, SHIJI_STS_PSP, ShijiStsError, isShijiStsHost };
3
+ /** The dark-launch capability a build declares when it swaps Shiji's ciphertexts and binds its getCvvId companion. */
4
+ export declare const SHIJI_STS_CAPABILITY = "shiji_sts_pinned_key";
5
+ /**
6
+ * Why a Shiji STS request was refused, reported as the `reason` of the `unsupported_checkout` event
7
+ * and of the attachment's `unsupported` state:
8
+ * shiji_sts_keep_on_file a getTokenId in the proxy's shape while Agentcard cannot yet ask the
9
+ * cardholder to let a hotel keep the card to guarantee the stay
10
+ * unsupported_shiji_sts_request any other request to getTokenId or getCvvId: not the proxy's shape,
11
+ * a query, a port, or a body the classifier cannot read
12
+ * shiji_sts_foreign_key a card field encrypted under a key other than the pinned Shiji key
13
+ * shiji_sts_companion_unbound a getCvvId that follows no getTokenId this attachment continued,
14
+ * or another page's or another accessToken's (a saved card's CVV)
15
+ * shiji_sts_companion_expired a getCvvId more than 120 s after the getTokenId it follows
16
+ * shiji_sts_companion_used a second getCvvId after the one the device's CVV went into
17
+ */
18
+ export type ShijiStsUnsupportedReason = 'shiji_sts_keep_on_file' | 'unsupported_shiji_sts_request' | 'shiji_sts_foreign_key' | 'shiji_sts_companion_unbound' | 'shiji_sts_companion_expired' | 'shiji_sts_companion_used';
19
+ /** What the adapters do with a POST the registry recognized on an STS host. */
20
+ export type ShijiStsVerdict = {
21
+ kind: 'pause';
22
+ } | {
23
+ kind: 'companion';
24
+ } | {
25
+ kind: 'refuse';
26
+ reason: ShijiStsUnsupportedReason;
27
+ };
28
+ interface ShijiStsRequest {
29
+ url: string;
30
+ method: string;
31
+ body: string;
32
+ }
33
+ /**
34
+ * The verdict on a request to an STS host that the registry recognized: 'pause' for the one prompt
35
+ * (a getTokenId in the proxy's shape once the card-on-file decision allows it), 'companion' for a
36
+ * getCvvId in the proxy's shape, and 'refuse' with a reason for everything else. A classifier that
37
+ * throws refuses.
38
+ */
39
+ export declare function shijiStsVerdict(request: ShijiStsRequest): ShijiStsVerdict;
40
+ /** A cse substitution as the API serves it (substitute.ts Substitutions). */
41
+ interface ServedSubstitutions {
42
+ encoding: string;
43
+ at: string;
44
+ fields: Record<string, string>;
45
+ remove?: string[];
46
+ }
47
+ /**
48
+ * Whether an approved Shiji STS authorization's served substitution is one this SDK can continue:
49
+ * JSON at the body root (`at` is ''), no sibling to remove, and exactly pan, expires and cvv, each a
50
+ * 128-byte ciphertext as JSEncrypt writes one. Anything else is not acted on.
51
+ */
52
+ export declare function isShijiStsServedSubstitution(sub: unknown): sub is ServedSubstitutions;
53
+ /**
54
+ * The page's reviewed getCvvId keys, as the API serves them beside an approved Shiji STS set
55
+ * (checkout.ts approvedShijiStsFields, `shiji_sts_companion`): the keySequence values Agentcard
56
+ * reviewed as the booking page's own getCvvId key (its SPS merchant's settings.client_key).
57
+ */
58
+ export interface ShijiStsServedCompanion {
59
+ key_sequences: string[];
60
+ }
61
+ /**
62
+ * Whether the API's `shiji_sts_companion` beside an approved Shiji STS set is one this SDK can bind
63
+ * the companion getCvvId to: exactly `{ key_sequences }`, 1 to 16 non-empty keySequences of printable
64
+ * ASCII. Anything else, an API that serves none included, is not acted on: the approval reads as
65
+ * malformed and the getTokenId is not continued, so no card reaches Shiji while its CVV has no
66
+ * reviewed key to go to.
67
+ */
68
+ export declare function isShijiStsServedCompanion(value: unknown): value is ShijiStsServedCompanion;
69
+ /** The paused request an approval was created from: the URL and body the API reviewed at create. */
70
+ export interface ShijiStsApprovedRequest {
71
+ url: string;
72
+ body: string;
73
+ }
74
+ /**
75
+ * The continuation of an approved getTokenId: the paused body with the device's pan and expires
76
+ * written over the page's own at the body root (payment-core substituteShijiSts, which refuses a
77
+ * value that is not a ciphertext under the pinned key), the device's cvv, which this body does not
78
+ * carry and the runtime keeps in memory for the one companion getCvvId, and the reviewed getCvvId
79
+ * keys that companion may carry it under (`companion`, the replay's shijiStsCompanion, which the
80
+ * client read from the API's shiji_sts_companion).
81
+ *
82
+ * Only into `approved`, the paused getTokenId the approval was created from: `url` and `body` must
83
+ * equal it byte for byte, so the accessToken and keySequence are the ones the API reviewed at create
84
+ * (its merchant binding). Every Shiji merchant's card is encrypted under the one pinned key, so the
85
+ * device's ciphertext would tokenize the card in any STS session a later request of the same shape
86
+ * names. The adapters never hand a Shiji approval to a retry either (cdp.ts survivesAbandonment).
87
+ *
88
+ * Null when neither URL is on an STS host, so every other processor keeps the generic swap. Throws
89
+ * for any other body or URL, an STS URL that is not getTokenId, a substitution
90
+ * isShijiStsServedSubstitution refuses (`sub` is whatever the approval served, of any processor's
91
+ * shape), no reviewed getCvvId key to bind the companion to, and while the card-on-file decision is
92
+ * open: in each case before anything continues.
93
+ */
94
+ export declare function shijiStsTokenContinuation(url: string, body: string, sub: unknown, approved: ShijiStsApprovedRequest, companion?: {
95
+ keySequences: readonly string[];
96
+ }): {
97
+ body: string;
98
+ cvv: string;
99
+ cvvKeySequences: string[];
100
+ } | null;
101
+ /**
102
+ * One attachment's getCvvId companion: at most one binding, to the getTokenId it last continued with
103
+ * the Vault's ciphertexts. `bind` runs right before that continue, `release` when the continue
104
+ * fails or the attachment is cancelled or its page goes away, `releasePage` when the frame or target
105
+ * the getTokenId came from goes away or commits a new document, and `claim` for every getCvvId in the
106
+ * proxy's shape. The binding is payment-core's (createShijiStsCompanion: same host, same page, same
107
+ * accessToken, a reviewed getCvvId key, within 120 s, once). The CVV ciphertext leaves memory with the
108
+ * one body it goes into or with the teardown that releases it, and the binding itself is dropped a
109
+ * little after its window, so a later getCvvId reads as unbound.
110
+ */
111
+ export declare class ShijiStsCompanions {
112
+ private bound;
113
+ /**
114
+ * Bind the companion of `tokenRequest` (the paused getTokenId: its URL, method and body) for the
115
+ * page `page` (the adapter's opaque id for the frame that sent it) and keep `cvv`, the device's
116
+ * CVV ciphertext, for it, to go only into a getCvvId whose keySequence is one of `cvvKeySequences`
117
+ * (the reviewed getCvvId keys the approval served). Replaces any earlier binding. Throws
118
+ * ShijiStsError for a request that is not a getTokenId in the proxy's shape, a key list
119
+ * payment-core refuses (none, empty or malformed), and while the card-on-file decision is open.
120
+ */
121
+ bind(tokenRequest: ShijiStsRequest, page: string, cvv: string, cvvKeySequences: readonly string[], now?: number): void;
122
+ /** Drop the binding and the CVV ciphertext it holds. */
123
+ release(): void;
124
+ /**
125
+ * Drop the binding only when its page is one `gone` names: the adapter calls it with a test for the
126
+ * page ids of a frame or target that went away, so the CVV ciphertext never outlives the frame its
127
+ * getCvvId would have to come from, and a binding on another frame stays.
128
+ */
129
+ releasePage(gone: (page: string) => boolean): void;
130
+ /** Whether a binding is held (its window may have closed; claim decides). */
131
+ isBound(): boolean;
132
+ /** The page id the binding is held for, or null with none: an adapter compares its own ids to it. */
133
+ boundPage(): string | null;
134
+ /**
135
+ * The body to continue a getCvvId with, the device's CVV written over the page's own at the body
136
+ * root, or why the request is refused. Only the first getCvvId the binding answers 'swap' for gets
137
+ * a body; the CVV ciphertext is dropped as it is written, so a second one reads as used.
138
+ */
139
+ claim(request: ShijiStsRequest, page: string, now?: number): {
140
+ body: string;
141
+ } | {
142
+ reason: ShijiStsUnsupportedReason;
143
+ };
144
+ }
@@ -0,0 +1,72 @@
1
+ declare var SHIJI_STS_PSP: string;
2
+ declare var SHIJI_STS_HOSTS: readonly string[];
3
+ declare var SHIJI_STS_SANDBOX_HOST: string;
4
+ declare var SHIJI_STS_TOKEN_PATH: string;
5
+ declare var SHIJI_STS_CVV_PATH: string;
6
+ declare var SHIJI_STS_KEY: Readonly<{
7
+ spki: "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDF68IfqgqwmtmAKHNDlEyO9dYQiJHLd9ZixQ32PmzQtBH/mK+KB0q9K2kT7IjL1KdfSPSh8zXlXJOzZ8HQPewmxZgDSnKBVoeVb8HENiaMcIioddz9GJSY1z5ZvEE2DZawiSRxOiOorXQ8HjyVF+1McoWbIShrbeLcT/lC/FS5UQIDAQAB";
8
+ sha256: "c7846a0f1736b11b7549a364122d43b6517b87989f873ad805748b1f5a469ff7";
9
+ }>;
10
+ declare var SHIJI_STS_CIPHERTEXT_BYTES: number;
11
+ declare var SHIJI_STS_COMPANION_WINDOW_MS: number;
12
+ declare var SHIJI_STS_FIELDS: Readonly<{
13
+ token: readonly string[];
14
+ cvv: readonly string[];
15
+ }>;
16
+ declare var SHIJI_STS_KEEP_ON_FILE: string;
17
+ declare var SHIJI_STS_REFUSAL_REASONS: readonly string[];
18
+ declare var ShijiStsError: {
19
+ new (reason: any): {
20
+ name: string;
21
+ message: string;
22
+ stack?: string;
23
+ cause?: unknown;
24
+ };
25
+ };
26
+ declare function shijiStsRefusalMessage(reason: any): any;
27
+ declare function shijiStsKeepOnFileRefusal(policy?: string): "keep_on_file" | null;
28
+ declare function shijiStsEndpoint(url: any, method?: string): "token" | "cvv" | null;
29
+ declare function shijiStsEnvironment(url: any, method?: string): "sandbox" | "live" | null;
30
+ declare function isShijiStsHost(url: any): boolean;
31
+ declare function isShijiStsCiphertext(value: any): boolean;
32
+ declare function isShijiStsCiphertextUnderKey(value: any): boolean;
33
+ declare function readShijiStsBody(kind: any, body: any): Readonly<{
34
+ ok: false;
35
+ reason: "unsupported";
36
+ }> | Readonly<{
37
+ ok: false;
38
+ reason: "foreign_key";
39
+ }> | Readonly<{
40
+ ok: boolean;
41
+ accessToken: any;
42
+ keySequence: any;
43
+ fields: Readonly<{}>;
44
+ }>;
45
+ declare function shijiStsBodyRefusal(url: any, body: any): any;
46
+ declare function classifyShijiStsRequest(url: any, method: any, body: any): any;
47
+ declare function isShijiStsCardRequest(request: any): boolean;
48
+ declare function shijiStsPlaintexts(card: any): {
49
+ pan: string;
50
+ expires: string;
51
+ cvv: string;
52
+ };
53
+ declare function encryptShijiStsCard(card: any, facilities?: {}): {
54
+ pan: string;
55
+ expires: string;
56
+ cvv: string;
57
+ };
58
+ declare function shijiStsKeyUsable(authorization: any): boolean;
59
+ declare function substituteShijiSts(url: any, body: any, fields: any): string;
60
+ declare function shijiStsSubstitutionsRefusal(template: any, substitutions: any): {
61
+ code: string;
62
+ message: any;
63
+ } | null;
64
+ declare function createShijiStsCompanion(tokenRequest: any, { page, now, cvvKeySequences }?: {}): Readonly<{
65
+ claim: (request: any, context?: {}) => any;
66
+ }>;
67
+ declare function shijiStsResponseFacts(status: any, text: any): {
68
+ outcome: string;
69
+ code: any;
70
+ };
71
+ declare function sanitizeShijiStsResponse(text: any, status?: number): string;
72
+ export { SHIJI_STS_CIPHERTEXT_BYTES, SHIJI_STS_COMPANION_WINDOW_MS, SHIJI_STS_CVV_PATH, SHIJI_STS_FIELDS, SHIJI_STS_HOSTS, SHIJI_STS_KEEP_ON_FILE, SHIJI_STS_KEY, SHIJI_STS_PSP, SHIJI_STS_REFUSAL_REASONS, SHIJI_STS_SANDBOX_HOST, SHIJI_STS_TOKEN_PATH, ShijiStsError, classifyShijiStsRequest, createShijiStsCompanion, encryptShijiStsCard, isShijiStsCardRequest, isShijiStsCiphertext, isShijiStsCiphertextUnderKey, isShijiStsHost, readShijiStsBody, sanitizeShijiStsResponse, shijiStsBodyRefusal, shijiStsEndpoint, shijiStsEnvironment, shijiStsKeepOnFileRefusal, shijiStsKeyUsable, shijiStsPlaintexts, shijiStsRefusalMessage, shijiStsResponseFacts, shijiStsSubstitutionsRefusal, substituteShijiSts };