@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/aci.d.ts ADDED
@@ -0,0 +1,173 @@
1
+ type AciAt = {
2
+ checkoutId: string;
3
+ host: string;
4
+ environment: 'production' | 'sandbox';
5
+ };
6
+ /** What one request on an ACI checkout is (payment-core aci.js classifyAciRequest). */
7
+ export type AciLeg = ({
8
+ leg: 'card' | 'absorb' | 'pass' | 'continuation';
9
+ } & AciAt) | ({
10
+ leg: 'refuse';
11
+ code: string;
12
+ } & AciAt);
13
+ /** The processor id, as the registry and the API name it. */
14
+ export declare const ACI_PSP = "aci";
15
+ /** The dark-launch capability the API serves the ACI recognizer behind (payment-core definitions.js). */
16
+ export declare const ACI_CAPABILITY = "aci_split_card.v1";
17
+ /**
18
+ * How long a sibling leg (the page's CVC or expiry request) waits for its checkout's card-number
19
+ * request to begin an approval. The widget fires every leg on one Pay click, milliseconds apart,
20
+ * so a sibling that still has no card-number request after this is one this SDK will never answer
21
+ * (a one-click stored-card CVC form, or a card-number request refused before it paused).
22
+ */
23
+ export declare const ACI_SIBLING_WAIT_MS = 5000;
24
+ /** What the page's requests on one checkout are answered with: the card-number request's answer. */
25
+ export interface AciAnswer {
26
+ status: number;
27
+ headers: Record<string, string>;
28
+ body: string;
29
+ }
30
+ /** The SDK's verdict on the page's /payment submission. */
31
+ export type AciPaymentVerdict = {
32
+ outcome: 'continue';
33
+ body: string;
34
+ stripped: boolean;
35
+ authorizationId: string | null;
36
+ } | {
37
+ outcome: 'refuse';
38
+ code: string;
39
+ };
40
+ /** The /payment submission of a checkout whose card-number request was never answered here. */
41
+ export declare const ACI_PAYMENT_UNAPPROVED = "aci_payment_unapproved";
42
+ /** A second /payment submission on a checkout that already continued one. */
43
+ export declare const ACI_PAYMENT_REPEATED = "aci_payment_repeated";
44
+ /** A card-number request on a checkout whose card-number request is running or was answered here. */
45
+ export declare const ACI_CHECKOUT_REUSED = "aci_checkout_reused";
46
+ /**
47
+ * How many failed checkouts one attachment keeps. A failed checkout only makes its later CVC and
48
+ * expiry requests fail at once instead of after ACI_SIBLING_WAIT_MS, so the oldest are dropped past
49
+ * this; a running or answered checkout is never dropped, since it is what refuses a second
50
+ * card-number request or /payment on that checkout.
51
+ */
52
+ export declare const ACI_FAILED_CHECKOUTS_KEPT = 16;
53
+ /**
54
+ * ACI's paymentBrand for the card number a card-number request carried, or null. That number is
55
+ * the agent's placeholder, typed in the approved card's network (a Mastercard-range placeholder for
56
+ * an Agentcard-issued card), and ACI's widget names the brand of the /payment submission from the
57
+ * same digits. The SDK is never told the approved card's own network: the widget's brand for the
58
+ * approved request is the one /payment may carry. The cardholder's device judges the placeholder
59
+ * with this same payment-core rule and refuses to send a card of another brand, which ACI would
60
+ * otherwise hold on the checkout before refusing /payment (100.100.700).
61
+ */
62
+ export declare function aciBrandOfCardLeg(body: string | null | undefined): string | null;
63
+ /**
64
+ * The answer a paused card-number request gets, or null when the stored answer is not one to hand
65
+ * the page: a 2xx carrying exactly ACI's rebuilt success answer (payment-core sanitizeAciResponse,
66
+ * the allow-list the API stored it through), under JSON. The widget only waits for every leg to
67
+ * resolve, so nothing else ACI said is ever needed, and the same answer serves the sibling legs.
68
+ */
69
+ export declare function aciCardLegAnswer(response: {
70
+ status: number;
71
+ body: string;
72
+ }): AciAnswer | null;
73
+ /**
74
+ * The URL with an ACI checkout id taken out of its path, or null for a URL that is not on one of
75
+ * ACI's hosts under /v1/checkouts/. The id is the checkout's only capability: once the card-number
76
+ * request carried the real card, anyone holding it can post /payment against that card for the
77
+ * rest of the checkout's 30 minutes, so it never rides on an event.
78
+ */
79
+ export declare function redactAciCheckoutId(raw: string): string | null;
80
+ /** How long the persistent-context engine probe (a CDP session opening) may take before it counts as no. */
81
+ export declare const ACI_ENGINE_PROBE_MS = 5000;
82
+ /**
83
+ * Whether a Playwright page's browser sends the body the SDK replaced on a navigation. Removing the
84
+ * placeholder expiry from a classic-mode /payment needs it, because the widget's form posts
85
+ * /payment as its hidden iframe's navigation. The SDK's ACI browser test shows it on Chromium in
86
+ * both adapters (design gate G3), and CI installs only Chromium; a local WebKit 26.5 run sent the
87
+ * replaced body too, but nothing keeps that true, so every engine but Chromium is refused rather
88
+ * than trusted with the placeholder expiry. The engine is the page's browser type, or, for a
89
+ * persistent context that names no browser, whether a CDP session opens on the page, which only
90
+ * Chromium allows.
91
+ */
92
+ export declare function playwrightReplacesNavigationBody(page: any, probeMs?: number): Promise<boolean>;
93
+ /**
94
+ * One attachment's ACI state, keyed by checkout (host and checkout id). Every sibling and every
95
+ * /payment submission is matched against a card-number request this attachment itself paused and
96
+ * answered, never taken on the page's word. Inert (classify returns null) until the registry serves
97
+ * ACI, which a client reaches only by syncing with the capability, so a build or an API that never
98
+ * served it leaves every ACI request exactly as before.
99
+ */
100
+ export declare class AciGate {
101
+ private readonly vault;
102
+ private readonly siblingWaitMs;
103
+ private readonly groups;
104
+ /** Siblings that arrived before their checkout's card-number request began, by checkout. */
105
+ private readonly early;
106
+ /** The checkout whose card-number request's approval is running. */
107
+ private current;
108
+ constructor(vault: {
109
+ recognizesProcessor?: (psp: string) => boolean;
110
+ } | undefined, siblingWaitMs?: number);
111
+ private served;
112
+ /** What this request is, when the registry serves ACI and it is a POST to an ACI checkout; null leaves it to the other gates. */
113
+ classify(url: string, method: string, body: string | null | undefined): AciLeg | null;
114
+ /**
115
+ * Playwright's route predicate sees only the URL. The card-number, CVC, expiry and holder
116
+ * requests share the recognizer's URL; /payment and /registration do not, so they are routed
117
+ * here, and the rest of ACI's traffic (the widget script, /bins, /v1/collect) never is.
118
+ */
119
+ matches(url: string): boolean;
120
+ /**
121
+ * The card-number request of a checkout began its approval; its siblings wait for the outcome.
122
+ * False, with nothing changed, for a checkout whose card-number request is still running or was
123
+ * answered here: its group keeps its answer and its one /payment, so a second approval can never
124
+ * reopen that checkout for another /payment. The pre-claim gate refuses such a request first
125
+ * (reused, aci_checkout_reused), before it claims a preparation; the adapters fail it here too.
126
+ * A refused begin leaves the running approval it found running. A checkout whose card-number
127
+ * request failed begins again.
128
+ */
129
+ begin(url: string, method: string, body: string | null | undefined, merchantOrigin: string | undefined): boolean;
130
+ /** Whether this checkout's card-number request is running or was answered here: another one is refused. */
131
+ reused(leg: {
132
+ host: string;
133
+ checkoutId: string;
134
+ }): boolean;
135
+ private live;
136
+ /** Drops the oldest failed checkouts past ACI_FAILED_CHECKOUTS_KEPT; running and answered ones stay. */
137
+ private prune;
138
+ /** The card-number request's answer reached the page; its siblings get the same answer. */
139
+ answered(url: string, answer: AciAnswer, authorizationId: string | null): void;
140
+ /** The card-number request's approval ended, answered or not; unanswered siblings fail. */
141
+ end(): void;
142
+ /**
143
+ * A request on this checkout was refused before any card-number request began (a card-number
144
+ * request that asks ACI to keep the card, a leg the widget never sends): the siblings waiting for
145
+ * one fail now, and so does every later sibling until a card-number request begins. A running or
146
+ * answered card-number request keeps its siblings.
147
+ */
148
+ refused(leg: {
149
+ host: string;
150
+ checkoutId: string;
151
+ }): void;
152
+ /**
153
+ * A sibling leg (the page's CVC or expiry request): the answer to hand it, once its checkout's
154
+ * card-number request was answered, or null when that request failed, never began within
155
+ * siblingWaitMs, or cannot be answered here. Never resolves to anything the page did not get.
156
+ */
157
+ hold(leg: {
158
+ host: string;
159
+ checkoutId: string;
160
+ }): Promise<AciAnswer | null>;
161
+ /**
162
+ * The page's /payment submission: continue it with the body to send, or refuse it. It continues
163
+ * only as a frame navigation (the widget's form posting into its hidden iframe; a script's fetch
164
+ * of the same URL is not the widget's and is refused before it can take the reservation), only
165
+ * for a checkout whose card-number request this attachment answered, only once, and only as
166
+ * payment-core's judge admits it for that card's brand and the merchant the cardholder approved.
167
+ * The reservation is taken before the answer, so two submissions can never both continue.
168
+ */
169
+ payment(url: string, method: string, body: string | null | undefined, request: {
170
+ navigation: boolean;
171
+ }): AciPaymentVerdict;
172
+ }
173
+ export {};
@@ -0,0 +1,87 @@
1
+ declare var ACI_HOSTS: readonly string[];
2
+ declare var ACI_TEST_HOSTS: readonly string[];
3
+ declare var ACI_TEST_PANS: readonly string[];
4
+ declare var ACI_CARD_ON_FILE_REFUSED: string;
5
+ declare var ACI_UNSUPPORTED_REQUEST: string;
6
+ declare var ACI_BRAND_MISMATCH: string;
7
+ declare var ACI_RESULT_URL_REFUSED: string;
8
+ declare var ACI_EXPIRY_UNSTRIPPABLE: string;
9
+ declare var ACI_CARD_FIELD: string;
10
+ declare var ACI_ABSORBED_FIELDS: readonly string[];
11
+ declare var ACI_PASSED_FIELDS: readonly string[];
12
+ declare var ACI_SIBLING_KEYS: readonly string[];
13
+ declare var ACI_REMOVED_ON_CONTINUATION: readonly string[];
14
+ declare function aciEndpoint(url: any): {
15
+ kind: string;
16
+ host: string;
17
+ checkoutId: string;
18
+ environment: string | undefined;
19
+ } | null;
20
+ declare function aciHostEnvironment(hostname: any): "production" | "sandbox" | undefined;
21
+ declare function aciEnvironment(url: any): string | undefined;
22
+ declare function readAciForm(body: any, limit?: number): {
23
+ key: string;
24
+ value: string;
25
+ raw: string;
26
+ }[] | null;
27
+ declare function isAciCardOnFileKey(key: any): boolean;
28
+ declare function classifyAciRequest(request: any): {
29
+ checkoutId: string;
30
+ host: string;
31
+ environment: string | undefined;
32
+ leg: string;
33
+ code: any;
34
+ } | {
35
+ checkoutId: string;
36
+ host: string;
37
+ environment: string | undefined;
38
+ leg: string;
39
+ } | null;
40
+ declare function isAciCardRequest(request: any): boolean;
41
+ declare function isPreparedAciRequest(request: any, environment: any): boolean;
42
+ declare function injectAciCard(templateBody: any, card: any): {
43
+ body: string;
44
+ where: string[];
45
+ };
46
+ declare function aciBrandForNetwork(network: any): any;
47
+ declare function aciCardBrand(number: any): "VISA" | "MASTER" | "AMEX" | null;
48
+ declare function aciBrandOfCardLeg(body: any): "VISA" | "MASTER" | "AMEX" | null;
49
+ declare function aciPlaceholderBrandRefusal(templateBody: any, card: any): string | null;
50
+ declare function prepareAciContinuation(request: any, context: any): {
51
+ checkoutId: string;
52
+ host: string;
53
+ environment: string | undefined;
54
+ outcome: string;
55
+ code: any;
56
+ } | {
57
+ outcome: string;
58
+ code: string;
59
+ } | {
60
+ checkoutId: string;
61
+ host: string;
62
+ environment: string | undefined;
63
+ outcome: string;
64
+ body: string;
65
+ stripped: boolean;
66
+ code?: undefined;
67
+ };
68
+ declare var ACI_CHECKOUT_UPDATED: string;
69
+ declare function aciFacts(text: any): {
70
+ resultCode: any;
71
+ refusalCode: any;
72
+ };
73
+ declare function aciResponseOutcome(status: any, text: any): {
74
+ outcome: string;
75
+ reason: string;
76
+ code?: undefined;
77
+ } | {
78
+ outcome: string;
79
+ reason?: undefined;
80
+ code?: undefined;
81
+ } | {
82
+ outcome: string;
83
+ code: any;
84
+ reason?: undefined;
85
+ };
86
+ declare function sanitizeAciResponse(text: any): string;
87
+ export { ACI_ABSORBED_FIELDS, ACI_BRAND_MISMATCH, ACI_CARD_FIELD, ACI_CARD_ON_FILE_REFUSED, ACI_CHECKOUT_UPDATED, ACI_EXPIRY_UNSTRIPPABLE, ACI_HOSTS, ACI_PASSED_FIELDS, ACI_REMOVED_ON_CONTINUATION, ACI_RESULT_URL_REFUSED, ACI_SIBLING_KEYS, ACI_TEST_HOSTS, ACI_TEST_PANS, ACI_UNSUPPORTED_REQUEST, aciBrandForNetwork, aciBrandOfCardLeg, aciCardBrand, aciEndpoint, aciEnvironment, aciFacts, aciHostEnvironment, aciPlaceholderBrandRefusal, aciResponseOutcome, classifyAciRequest, injectAciCard, isAciCardOnFileKey, isAciCardRequest, isPreparedAciRequest, prepareAciContinuation, readAciForm, sanitizeAciResponse };
@@ -0,0 +1,306 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: b5b6d696111cb2906b73f99dc9ed21cbf51580bd0799183694ed5147c8a10e66
4
+ // src/aci.js
5
+ var ACI_HOSTS = Object.freeze(["eu-prod.oppwa.com", "oppwa.com", "eu-test.oppwa.com", "test.oppwa.com"]);
6
+ var ACI_TEST_HOSTS = Object.freeze(["eu-test.oppwa.com", "test.oppwa.com"]);
7
+ var ACI_TEST_PANS = Object.freeze([
8
+ "4200000000000000",
9
+ "4711100000000000",
10
+ "5454545454545454",
11
+ "5212345678901234",
12
+ "377777777777770",
13
+ "375987000000005"
14
+ ]);
15
+ var ACI_CARD_ON_FILE_REFUSED = "aci_card_on_file_refused";
16
+ var ACI_UNSUPPORTED_REQUEST = "aci_unsupported_request";
17
+ var ACI_BRAND_MISMATCH = "aci_brand_mismatch";
18
+ var ACI_RESULT_URL_REFUSED = "aci_result_url_refused";
19
+ var ACI_EXPIRY_UNSTRIPPABLE = "aci_expiry_unstrippable";
20
+ var ACI_CARD_FIELD = "card.number";
21
+ var ACI_ABSORBED_FIELDS = Object.freeze(["card.cvv", "card.expiryMonth", "card.expiryYear"]);
22
+ var ACI_PASSED_FIELDS = Object.freeze(["card.holder"]);
23
+ var ACI_SIBLING_KEYS = Object.freeze(["customParameters[SHOPPER_EndToEndIdentity]"]);
24
+ var ACI_REMOVED_ON_CONTINUATION = Object.freeze(["card.expiryMonth", "card.expiryYear"]);
25
+ var END_TO_END = ACI_SIBLING_KEYS[0];
26
+ var CHECKOUT_ID = /^[0-9A-F]{32}\.[a-z0-9]{1,16}-vm-tx[0-9]{1,3}$/i;
27
+ var ENDPOINT_PATH = /^\/v1\/checkouts\/([^/]+?)(\/payment|\/registration)?$/;
28
+ var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
29
+ var PAN = /^[0-9]{12,19}$/;
30
+ var CVV = /^[0-9]{3,4}$/;
31
+ var MONTH = /^(?:0[1-9]|1[0-2])$/;
32
+ var YEAR = /^(?:19|20)[0-9]{2}$/;
33
+ var HEX64 = /^[0-9a-f]{64}$/i;
34
+ var CONTROL = /[\u0000-\u001f\u007f]/;
35
+ function aciEndpoint(url) {
36
+ if (typeof url !== "string" || url.length > 512 || url.includes("?") || url.includes("#"))
37
+ return null;
38
+ let parsed;
39
+ try {
40
+ parsed = new URL(url);
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password || !ACI_HOSTS.includes(parsed.hostname))
46
+ return null;
47
+ const path = ENDPOINT_PATH.exec(parsed.pathname);
48
+ if (!path || !exact(CHECKOUT_ID, path[1]))
49
+ return null;
50
+ const kind = path[2] === "/payment" ? "payment" : path[2] === "/registration" ? "registration" : "leg";
51
+ return { kind, host: parsed.hostname, checkoutId: path[1], environment: aciHostEnvironment(parsed.hostname) };
52
+ }
53
+ function aciHostEnvironment(hostname) {
54
+ if (ACI_TEST_HOSTS.includes(hostname))
55
+ return "sandbox";
56
+ return ACI_HOSTS.includes(hostname) ? "production" : void 0;
57
+ }
58
+ function aciEnvironment(url) {
59
+ const endpoint = aciEndpoint(url);
60
+ return endpoint && endpoint.kind === "leg" ? endpoint.environment : void 0;
61
+ }
62
+ function readAciForm(body, limit = 8192) {
63
+ if (typeof body !== "string" || body === "" || body.length > limit)
64
+ return null;
65
+ const pairs = [];
66
+ const seen = /* @__PURE__ */ new Set();
67
+ for (const raw of body.split("&")) {
68
+ const eq = raw.indexOf("=");
69
+ if (eq <= 0 || pairs.length >= 64)
70
+ return null;
71
+ let key, value;
72
+ try {
73
+ key = decodeURIComponent(raw.slice(0, eq).replace(/\+/g, " "));
74
+ value = decodeURIComponent(raw.slice(eq + 1).replace(/\+/g, " "));
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ if (!key || key.length > 128 || CONTROL.test(key) || CONTROL.test(value) || value.length > 2048 || seen.has(key))
80
+ return null;
81
+ seen.add(key);
82
+ pairs.push({ key, value, raw });
83
+ }
84
+ return pairs;
85
+ }
86
+ function isAciCardOnFileKey(key) {
87
+ return typeof key === "string" && (key === "createRegistration" || key === "registrationId" || key === "recurringType" || key.startsWith("registrations[") || key.startsWith("registrations.") || key.startsWith("standingInstruction."));
88
+ }
89
+ function validField(key, value) {
90
+ switch (key) {
91
+ case "card.number":
92
+ return exact(PAN, value);
93
+ case "card.cvv":
94
+ return exact(CVV, value);
95
+ case "card.expiryMonth":
96
+ return exact(MONTH, value);
97
+ case "card.expiryYear":
98
+ return exact(YEAR, value);
99
+ // ACI takes 3 to 128 characters; the merchant may allow an empty holder.
100
+ case "card.holder":
101
+ return typeof value === "string" && value.length <= 128;
102
+ case END_TO_END:
103
+ return exact(HEX64, value);
104
+ default:
105
+ return false;
106
+ }
107
+ }
108
+ function classifyAciRequest(request) {
109
+ if (!request || typeof request !== "object" || Array.isArray(request))
110
+ return null;
111
+ const endpoint = aciEndpoint(request.url);
112
+ if (!endpoint || request.method !== "POST")
113
+ return null;
114
+ const at = { checkoutId: endpoint.checkoutId, host: endpoint.host, environment: endpoint.environment };
115
+ const refuse = (code) => ({ leg: "refuse", code, ...at });
116
+ if (endpoint.kind === "registration")
117
+ return refuse(ACI_CARD_ON_FILE_REFUSED);
118
+ if (endpoint.kind === "payment")
119
+ return { leg: "continuation", ...at };
120
+ const pairs = readAciForm(request.body);
121
+ if (!pairs)
122
+ return refuse(ACI_UNSUPPORTED_REQUEST);
123
+ if (pairs.some(({ key }) => isAciCardOnFileKey(key)))
124
+ return refuse(ACI_CARD_ON_FILE_REFUSED);
125
+ if (!pairs.every(({ key, value }) => validField(key, value)))
126
+ return refuse(ACI_UNSUPPORTED_REQUEST);
127
+ const own = pairs.map(({ key }) => key).filter((key) => key !== END_TO_END);
128
+ const has = (key) => own.includes(key);
129
+ if (has("card.expiryMonth") !== has("card.expiryYear"))
130
+ return refuse(ACI_UNSUPPORTED_REQUEST);
131
+ if (has(ACI_CARD_FIELD)) {
132
+ return own.every((key) => key === ACI_CARD_FIELD || key === "card.cvv") ? { leg: "card", ...at } : refuse(ACI_UNSUPPORTED_REQUEST);
133
+ }
134
+ if (own.length && own.every((key) => ACI_ABSORBED_FIELDS.includes(key)))
135
+ return { leg: "absorb", ...at };
136
+ if (own.length && own.every((key) => ACI_PASSED_FIELDS.includes(key)))
137
+ return { leg: "pass", ...at };
138
+ return refuse(ACI_UNSUPPORTED_REQUEST);
139
+ }
140
+ function isAciCardRequest(request) {
141
+ return classifyAciRequest(request)?.leg === "card";
142
+ }
143
+ function isPreparedAciRequest(request, environment) {
144
+ const leg = classifyAciRequest(request);
145
+ return !!leg && leg.leg === "card" && (environment === "production" || environment === "sandbox") && leg.environment === environment;
146
+ }
147
+ function injectAciCard(templateBody, card) {
148
+ const pairs = readAciForm(templateBody);
149
+ const keys = pairs ? pairs.map(({ key }) => key) : [];
150
+ if (!pairs || !keys.includes(ACI_CARD_FIELD) || pairs.some(({ key }) => isAciCardOnFileKey(key)) || !keys.every((key) => key === ACI_CARD_FIELD || key === "card.cvv" || key === END_TO_END) || !pairs.every(({ key, value }) => validField(key, value))) {
151
+ throw new Error("Refusing to pay: unsupported ACI card request.");
152
+ }
153
+ const number = String(card && card.number);
154
+ const cvc = String(card && card.cvc);
155
+ const month = String(card && card.month).padStart(2, "0");
156
+ const rawYear = String(card && card.year);
157
+ const year = /^[0-9]{2}$/.test(rawYear) ? `20${rawYear}` : rawYear;
158
+ if (!exact(PAN, number) || !exact(CVV, cvc) || !exact(MONTH, month) || !exact(YEAR, year)) {
159
+ throw new Error("Could not fill the card fields for ACI. Nothing was sent.");
160
+ }
161
+ if (aciPlaceholderBrandRefusal(templateBody, { number })) {
162
+ throw new Error("Refusing to pay: this ACI payment names a card network that is not your card's. Nothing was sent.");
163
+ }
164
+ const written = { "card.number": number, "card.cvv": cvc };
165
+ const out = pairs.map(({ key, raw }) => Object.prototype.hasOwnProperty.call(written, key) ? `${raw.slice(0, raw.indexOf("="))}=${written[key]}` : raw);
166
+ const where = ["card.number"];
167
+ if (keys.includes("card.cvv"))
168
+ where.push("card.cvv");
169
+ else {
170
+ out.push(`card.cvv=${cvc}`);
171
+ where.push("card.cvv");
172
+ }
173
+ out.push(`card.expiryMonth=${month}`, `card.expiryYear=${year}`);
174
+ where.push("card.expiryMonth", "card.expiryYear");
175
+ return { body: out.join("&"), where };
176
+ }
177
+ var ACI_BRANDS = Object.freeze({ visa: "VISA", mastercard: "MASTER", master: "MASTER", amex: "AMEX", american_express: "AMEX" });
178
+ function aciBrandForNetwork(network) {
179
+ const key = typeof network === "string" ? network.trim().toLowerCase().replace(/[\s-]+/g, "_") : "";
180
+ return Object.prototype.hasOwnProperty.call(ACI_BRANDS, key) ? ACI_BRANDS[key] : null;
181
+ }
182
+ function aciCardBrand(number) {
183
+ if (!exact(PAN, number))
184
+ return null;
185
+ if (/^4/.test(number))
186
+ return "VISA";
187
+ if (/^(?:5[1-5]|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)/.test(number))
188
+ return "MASTER";
189
+ return /^3[47]/.test(number) ? "AMEX" : null;
190
+ }
191
+ function aciBrandOfCardLeg(body) {
192
+ const pairs = readAciForm(body);
193
+ return aciCardBrand(pairs?.find(({ key }) => key === ACI_CARD_FIELD)?.value);
194
+ }
195
+ function aciPlaceholderBrandRefusal(templateBody, card) {
196
+ const placeholder = aciBrandOfCardLeg(templateBody);
197
+ return placeholder && placeholder === aciCardBrand(String(card?.number ?? "")) ? null : ACI_BRAND_MISMATCH;
198
+ }
199
+ var CONTINUATION_KEYS = /* @__PURE__ */ new Set([
200
+ "paymentBrand",
201
+ "shopperResultUrl",
202
+ "shopOrigin",
203
+ "forceUtf8",
204
+ "card.holder",
205
+ "card.expiryMonth",
206
+ "card.expiryYear",
207
+ ...[
208
+ "acceptLanguage",
209
+ "challengeWindow",
210
+ "deviceId",
211
+ "javaEnabled",
212
+ "javascriptEnabled",
213
+ "language",
214
+ "screenColorDepth",
215
+ "screenHeight",
216
+ "screenWidth",
217
+ "timezone",
218
+ "userAgent"
219
+ ].map((name) => `customer.browser.${name}`),
220
+ "customer.browserFingerprint.id",
221
+ "risk.parameters[CS_SESSION_ID]"
222
+ ]);
223
+ var SHOPPER_PARAMETER = /^customParameters\[SHOPPER_[A-Za-z0-9._]{1,56}\]$/;
224
+ var continuationKey = (key) => CONTINUATION_KEYS.has(key) || exact(SHOPPER_PARAMETER, key);
225
+ function onOrigin(value, origin) {
226
+ if (typeof value !== "string" || typeof origin !== "string")
227
+ return false;
228
+ let url, expected;
229
+ try {
230
+ url = new URL(value);
231
+ expected = new URL(origin);
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ return url.protocol === "https:" && !url.username && !url.password && expected.origin === origin && url.origin === origin;
237
+ }
238
+ function prepareAciContinuation(request, context) {
239
+ const { brand, merchantOrigin } = context || {};
240
+ const endpoint = request && typeof request === "object" ? aciEndpoint(request.url) : null;
241
+ if (!endpoint || endpoint.kind !== "payment" || request.method !== "POST")
242
+ return { outcome: "refuse", code: ACI_UNSUPPORTED_REQUEST };
243
+ const at = { checkoutId: endpoint.checkoutId, host: endpoint.host, environment: endpoint.environment };
244
+ const refuse = (code) => ({ outcome: "refuse", code, ...at });
245
+ const pairs = readAciForm(request.body, 16384);
246
+ if (!pairs)
247
+ return refuse(ACI_UNSUPPORTED_REQUEST);
248
+ if (pairs.some(({ key }) => isAciCardOnFileKey(key)))
249
+ return refuse(ACI_CARD_ON_FILE_REFUSED);
250
+ if (!pairs.every(({ key }) => continuationKey(key)))
251
+ return refuse(ACI_UNSUPPORTED_REQUEST);
252
+ const value = (key) => pairs.find((pair) => pair.key === key)?.value;
253
+ if (typeof brand !== "string" || !/^[A-Z]{2,16}$/.test(brand) || value("paymentBrand") !== brand)
254
+ return refuse(ACI_BRAND_MISMATCH);
255
+ if (!onOrigin(value("shopperResultUrl"), merchantOrigin))
256
+ return refuse(ACI_RESULT_URL_REFUSED);
257
+ const shopOrigin = value("shopOrigin");
258
+ if (shopOrigin !== void 0 && shopOrigin !== merchantOrigin)
259
+ return refuse(ACI_RESULT_URL_REFUSED);
260
+ const month = value("card.expiryMonth");
261
+ const year = value("card.expiryYear");
262
+ if (month === void 0 !== (year === void 0))
263
+ return refuse(ACI_EXPIRY_UNSTRIPPABLE);
264
+ const stripped = month !== void 0;
265
+ if (stripped && (!validField("card.expiryMonth", month) || !validField("card.expiryYear", year)))
266
+ return refuse(ACI_EXPIRY_UNSTRIPPABLE);
267
+ const body = pairs.filter(({ key }) => !ACI_REMOVED_ON_CONTINUATION.includes(key)).map(({ raw }) => raw).join("&");
268
+ return { outcome: "continue", body, stripped, ...at };
269
+ }
270
+ var ACI_CHECKOUT_UPDATED = "000.200.101";
271
+ var RESULT_CODE = /^[0-9]{3}\.[0-9]{3}\.[0-9]{3}$/;
272
+ var REFUSAL_FAMILY = /^(?:100|200|600|800)\./;
273
+ function resultCode(text) {
274
+ if (typeof text !== "string" || text.length > 32768)
275
+ return null;
276
+ let parsed;
277
+ try {
278
+ parsed = JSON.parse(text);
279
+ }
280
+ catch {
281
+ return null;
282
+ }
283
+ const result = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed.result : null;
284
+ const code = result && typeof result === "object" && !Array.isArray(result) ? result.code : null;
285
+ return exact(RESULT_CODE, code) ? code : null;
286
+ }
287
+ function aciFacts(text) {
288
+ const code = resultCode(text);
289
+ return { resultCode: code, refusalCode: code && REFUSAL_FAMILY.test(code) ? code : null };
290
+ }
291
+ function aciResponseOutcome(status, text) {
292
+ const { resultCode: code, refusalCode } = aciFacts(text);
293
+ if (!(Number.isInteger(status) && status >= 200 && status < 300))
294
+ return { outcome: "unknown", reason: "aci_unconfirmed" };
295
+ if (code === ACI_CHECKOUT_UPDATED)
296
+ return { outcome: "approved" };
297
+ if (refusalCode)
298
+ return { outcome: "refused", code: refusalCode };
299
+ return { outcome: "unknown", reason: "aci_unconfirmed" };
300
+ }
301
+ function sanitizeAciResponse(text) {
302
+ if (resultCode(text) !== ACI_CHECKOUT_UPDATED)
303
+ throw new Error("Refusing to store an unconfirmed ACI answer.");
304
+ return JSON.stringify({ result: { code: ACI_CHECKOUT_UPDATED, description: "successfully updated checkout" } });
305
+ }
306
+ export { ACI_ABSORBED_FIELDS, ACI_BRAND_MISMATCH, ACI_CARD_FIELD, ACI_CARD_ON_FILE_REFUSED, ACI_CHECKOUT_UPDATED, ACI_EXPIRY_UNSTRIPPABLE, ACI_HOSTS, ACI_PASSED_FIELDS, ACI_REMOVED_ON_CONTINUATION, ACI_RESULT_URL_REFUSED, ACI_SIBLING_KEYS, ACI_TEST_HOSTS, ACI_TEST_PANS, ACI_UNSUPPORTED_REQUEST, aciBrandForNetwork, aciBrandOfCardLeg, aciCardBrand, aciEndpoint, aciEnvironment, aciFacts, aciHostEnvironment, aciPlaceholderBrandRefusal, aciResponseOutcome, classifyAciRequest, injectAciCard, isAciCardOnFileKey, isAciCardRequest, isPreparedAciRequest, prepareAciContinuation, readAciForm, sanitizeAciResponse };