@agent-cards/sdk 0.21.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.
- package/CHANGELOG.md +47 -0
- package/PREFLIGHT.md +30 -0
- package/README.md +1070 -21
- package/dist/aci.d.ts +173 -0
- package/dist/aci.generated.d.ts +87 -0
- package/dist/aci.generated.js +306 -0
- package/dist/aci.js +371 -0
- package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
- package/dist/adyen-merchant-hosted.generated.js +283 -49
- package/dist/agentcard.d.ts +9 -2
- package/dist/agentcard.js +4 -2
- package/dist/amadeus-checkout.generated.d.ts +57 -0
- package/dist/amadeus-checkout.generated.js +549 -0
- package/dist/amadeus.d.ts +41 -0
- package/dist/amadeus.js +81 -0
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-endpoint.d.ts +177 -0
- package/dist/card-endpoint.js +286 -0
- package/dist/card-fields.generated.d.ts +5 -3
- package/dist/card-fields.generated.js +284 -7
- package/dist/cdp.d.ts +5 -1
- package/dist/cdp.js +841 -135
- package/dist/client.d.ts +403 -19
- package/dist/client.js +949 -98
- package/dist/creditguard.generated.d.ts +79 -0
- package/dist/creditguard.generated.js +662 -0
- package/dist/cse-body.js +11 -1
- package/dist/datatrans.d.ts +96 -0
- package/dist/datatrans.generated.d.ts +64 -0
- package/dist/datatrans.generated.js +426 -0
- package/dist/datatrans.js +178 -0
- package/dist/elavon-converge.d.ts +37 -0
- package/dist/elavon-converge.generated.d.ts +49 -0
- package/dist/elavon-converge.generated.js +439 -0
- package/dist/elavon-converge.js +84 -0
- package/dist/eprotect.d.ts +152 -0
- package/dist/eprotect.generated.d.ts +77 -0
- package/dist/eprotect.generated.js +498 -0
- package/dist/eprotect.js +222 -0
- package/dist/hosted-form.d.ts +3 -2
- package/dist/hosted-form.js +3 -2
- package/dist/index.d.ts +17 -3
- package/dist/index.js +6 -1
- package/dist/juspay-gate.d.ts +82 -0
- package/dist/juspay-gate.js +126 -0
- package/dist/juspay.generated.d.ts +89 -0
- package/dist/juspay.generated.js +705 -0
- package/dist/lifecycle.d.ts +57 -6
- package/dist/lifecycle.js +119 -8
- package/dist/merchant-card-endpoints.generated.d.ts +63 -0
- package/dist/merchant-card-endpoints.generated.js +1103 -0
- package/dist/merchant-hosted.d.ts +4 -1
- package/dist/merchant-hosted.js +5 -2
- package/dist/merchant-session-probe.d.ts +48 -0
- package/dist/merchant-session-probe.js +53 -0
- package/dist/merchant-session.generated.d.ts +69 -0
- package/dist/merchant-session.generated.js +919 -0
- package/dist/payrails.d.ts +133 -0
- package/dist/payrails.generated.d.ts +73 -0
- package/dist/payrails.generated.js +596 -0
- package/dist/payrails.js +305 -0
- package/dist/pelecard.d.ts +32 -0
- package/dist/pelecard.generated.d.ts +32 -0
- package/dist/pelecard.generated.js +283 -0
- package/dist/pelecard.js +42 -0
- package/dist/pre-claim.d.ts +72 -2
- package/dist/pre-claim.js +857 -5
- package/dist/preflight-catalog.json +2218 -287
- package/dist/preflight-schemas.json +179 -6
- package/dist/preflight.generated.d.ts +1 -1
- package/dist/preflight.generated.js +233 -4
- package/dist/preparation.d.ts +5 -2
- package/dist/preparation.js +36 -10
- package/dist/prepared-processor.d.ts +84 -5
- package/dist/prepared-processor.js +147 -6
- package/dist/primer.d.ts +53 -0
- package/dist/primer.generated.d.ts +38 -0
- package/dist/primer.generated.js +624 -0
- package/dist/primer.js +82 -0
- package/dist/registry.d.ts +100 -8
- package/dist/registry.js +85 -11
- package/dist/shiji-sts.d.ts +144 -0
- package/dist/shiji-sts.generated.d.ts +72 -0
- package/dist/shiji-sts.generated.js +913 -0
- package/dist/shiji-sts.js +222 -0
- package/dist/stripe-checkout.generated.d.ts +1 -1
- package/dist/stripe-checkout.generated.js +699 -9
- package/dist/stripe-setup.d.ts +16 -0
- package/dist/stripe-setup.js +61 -0
- package/dist/substitutions.generated.js +187 -29
- package/dist/verifone.d.ts +68 -0
- package/dist/verifone.generated.d.ts +51 -0
- package/dist/verifone.generated.js +1401 -0
- package/dist/verifone.js +119 -0
- package/dist/voice-types.d.ts +27 -0
- package/dist/voice.d.ts +6 -1
- package/dist/voice.js +6 -1
- package/dist/wspay.d.ts +41 -0
- package/dist/wspay.generated.d.ts +41 -0
- package/dist/wspay.generated.js +370 -0
- package/dist/wspay.js +65 -0
- package/examples/preflight/kernel-native/inventory.json +1 -1
- 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 };
|