@agent-cards/sdk 0.23.21 → 0.23.22
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 +14 -0
- package/PREFLIGHT.md +4 -0
- package/README.md +27 -1
- package/dist/amadeus-checkout.generated.d.ts +6 -1
- package/dist/amadeus-checkout.generated.js +26 -2
- package/dist/card-fields.generated.js +1391 -104
- package/dist/cdp.js +53 -18
- package/dist/client.d.ts +73 -6
- package/dist/client.js +164 -24
- package/dist/creditguard.generated.js +4 -2
- package/dist/cse-body.js +11 -0
- package/dist/ebanx.d.ts +1 -1
- package/dist/ebanx.js +1 -1
- package/dist/eximbay.d.ts +46 -0
- package/dist/eximbay.js +69 -0
- package/dist/lifecycle.d.ts +9 -4
- package/dist/lifecycle.js +16 -4
- package/dist/paygent.generated.d.ts +60 -0
- package/dist/paygent.generated.js +425 -0
- package/dist/payrexx.d.ts +91 -0
- package/dist/payrexx.generated.d.ts +91 -0
- package/dist/payrexx.generated.js +536 -0
- package/dist/payrexx.js +141 -0
- package/dist/pre-claim.d.ts +23 -0
- package/dist/pre-claim.js +131 -20
- package/dist/prepared-processor.d.ts +7 -2
- package/dist/prepared-processor.js +16 -2
- package/dist/primer.generated.js +1427 -140
- package/dist/stripe-checkout.generated.js +289 -21
- package/dist/toast-seal.generated.d.ts +37 -0
- package/dist/toast-seal.generated.js +625 -0
- package/dist/toast.generated.d.ts +41 -0
- package/dist/toast.generated.js +113 -0
- package/package.json +2 -2
package/dist/payrexx.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payrexx Pay's card frame after its card request (payment-core payrexx.js holds the rules; this SDK vendors them as
|
|
3
|
+
* payrexx.generated.ts).
|
|
4
|
+
*
|
|
5
|
+
* The frame, https://checkout.payrexx.com/?pspId=44&lang=<xx>, posts the card to its own URL. When Payrexx answers that
|
|
6
|
+
* card request by starting 3-D Secure (no token, a threeDSServerTransID), the merchant's page runs 3-D Secure and the
|
|
7
|
+
* frame posts the whole card again to the same URL for each step, with the step's own keys after the card request's
|
|
8
|
+
* fifteen:
|
|
9
|
+
* - the authentication, after the ACS method: threeDSServerTransID, threeDSVersion when the first answer named one,
|
|
10
|
+
* and ten authData values the page reads from the agent's browser;
|
|
11
|
+
* - the post-authentication, after the bank's challenge: threeDSServerTransID and postAuth=true.
|
|
12
|
+
* On a page this SDK is attached to, that card is the agent's placeholder. One approval covers at most one of each, in
|
|
13
|
+
* that order, for the 3-D Secure run its own answer opened (payment-core payrexxFollowUpVerdict), so a run sends the
|
|
14
|
+
* card at most three times. What this module keeps of the approved card request (PayrexxFollowUps) is what the
|
|
15
|
+
* pre-claim gate reads to decide whether the cardholder's device sends a step, or the step fails in the browser.
|
|
16
|
+
* The card request itself, and a body with no card value at all (a wallet's own encryptedCard), are not 3-D Secure
|
|
17
|
+
* sends: the card-request arbiter pauses the one and continues the other (card-fields.js).
|
|
18
|
+
*/
|
|
19
|
+
import { PAYREXX_FOLLOW_UP_KINDS, isPayrexxFrameUrl, payrexxAnswerOutcome, payrexxBodyVerdict, payrexxFollowUpVerdict, payrexxGateVerdict, } from './payrexx.generated.js';
|
|
20
|
+
export const PAYREXX_PSP = 'payrexx';
|
|
21
|
+
/** The capability a build declares to be served Payrexx's recognizer (payment-core PAYREXX_CAPABILITY). */
|
|
22
|
+
export const PAYREXX_CAPABILITY = 'payrexx_card_frame';
|
|
23
|
+
/** The reason a checkout reads when a 3-D Secure send of the placeholder card fails in the browser. */
|
|
24
|
+
export const PAYREXX_RESEND_REASON = 'unsupported_payrexx_card_resend';
|
|
25
|
+
/**
|
|
26
|
+
* Whether a request is a POST to Payrexx's card frame URL whose body sends the card again for a 3-D Secure step
|
|
27
|
+
* (payment-core payrexxBodyVerdict: payrexx_three_ds_resend). `readBody` is called only for a POST to the frame URL,
|
|
28
|
+
* and a reader that throws, or a body nobody could read, reads as no 3-D Secure send.
|
|
29
|
+
*/
|
|
30
|
+
export function isPayrexxResend(url, method, readBody) {
|
|
31
|
+
if (String(method).toUpperCase() !== 'POST' || !isPayrexxFrameUrl(url))
|
|
32
|
+
return false;
|
|
33
|
+
let body;
|
|
34
|
+
try {
|
|
35
|
+
body = readBody();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
body = null;
|
|
39
|
+
}
|
|
40
|
+
if (typeof body !== 'string')
|
|
41
|
+
return false;
|
|
42
|
+
const verdict = payrexxBodyVerdict(body);
|
|
43
|
+
return verdict.verdict === 'refuse' && verdict.code === 'payrexx_three_ds_resend';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A 3-D Secure send the cardholder's device did not answer with Payrexx's own answer. `reason` is the API's word for
|
|
47
|
+
* it: `not_picked_up` (no device started it in time: the Vault page was closed or asleep), `not_sent` (the device
|
|
48
|
+
* refused it before the card left), `no_answer`, `answer_withheld` (Payrexx's answer carried the card),
|
|
49
|
+
* `answer_refused`, `answer_late`, `response_expired`, or the API's refusal code when it did not take the send at all
|
|
50
|
+
* (`follow_up_refused`, `follow_up_not_covered`, `follow_up_window_closed`). `cardSent` says whether the card may
|
|
51
|
+
* have reached Payrexx: false only when the API confirmed it never left the device. Nothing in it names the request,
|
|
52
|
+
* the card or Payrexx's answer.
|
|
53
|
+
*/
|
|
54
|
+
export class PayrexxFollowUpError extends Error {
|
|
55
|
+
reason;
|
|
56
|
+
cardSent;
|
|
57
|
+
authorizationId;
|
|
58
|
+
constructor(reason, cardSent, authorizationId) {
|
|
59
|
+
super(cardSent
|
|
60
|
+
? `This Payrexx 3-D Secure request under ${authorizationId} has no answer, and the cardholder's device may have sent it: ${reason}.`
|
|
61
|
+
: `The cardholder's device did not send this Payrexx 3-D Secure request under ${authorizationId}: ${reason}.`);
|
|
62
|
+
this.reason = reason;
|
|
63
|
+
this.cardSent = cardSent;
|
|
64
|
+
this.authorizationId = authorizationId;
|
|
65
|
+
this.name = 'PayrexxFollowUpError';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What one attachment keeps of its approved Payrexx card request: the request and Payrexx's answer as the page received
|
|
70
|
+
* them, until when the API said the approval covers sends after it, and the sends it claimed since, in order. Only a
|
|
71
|
+
* card request whose answer started 3-D Secure (payment-core payrexxAnswerOutcome: pending three_ds) and whose read
|
|
72
|
+
* named that window (`followUpsUntil`) is kept. Any later Payrexx card request handed over replaces it, a send the
|
|
73
|
+
* window has passed is never claimed, and once a send is refused or fails the attachment stops (stop()): nothing after
|
|
74
|
+
* it is claimed, a later approval included.
|
|
75
|
+
*/
|
|
76
|
+
export class PayrexxFollowUps {
|
|
77
|
+
parent = null;
|
|
78
|
+
sent = [];
|
|
79
|
+
stopped = false;
|
|
80
|
+
/** After a refused or failed send: no later request on this attachment is claimed. */
|
|
81
|
+
stop() { this.stopped = true; }
|
|
82
|
+
/**
|
|
83
|
+
* The approval the remembered card request went under, or null when none is remembered. The gate sends nothing
|
|
84
|
+
* unless the checkout's own state names this same approval.
|
|
85
|
+
*/
|
|
86
|
+
get authorizationId() { return this.parent?.authorizationId ?? null; }
|
|
87
|
+
/**
|
|
88
|
+
* As the adapter hands an approved card request's answer to the page: before the answer lands, since the page may
|
|
89
|
+
* start 3-D Secure at once. Every Payrexx card request handed over clears what an earlier one left; only one whose
|
|
90
|
+
* answer started 3-D Secure, under a read that named the window, is kept.
|
|
91
|
+
*/
|
|
92
|
+
remember(request, replay) {
|
|
93
|
+
if (payrexxGateVerdict(request.url, request.body).verdict !== 'card')
|
|
94
|
+
return;
|
|
95
|
+
this.parent = null;
|
|
96
|
+
this.sent = [];
|
|
97
|
+
const until = typeof replay.followUpsUntil === 'string' ? Date.parse(replay.followUpsUntil) : NaN;
|
|
98
|
+
if ((replay.mode ?? 'token') !== 'token' || !Number.isFinite(until))
|
|
99
|
+
return;
|
|
100
|
+
// No card: this browser only ever held the placeholder.
|
|
101
|
+
const outcome = payrexxAnswerOutcome(replay.status, replay.body, null);
|
|
102
|
+
if (outcome.outcome !== 'approved' || outcome.pending !== 'three_ds')
|
|
103
|
+
return;
|
|
104
|
+
this.parent = { authorizationId: replay.authorizationId, url: request.url, body: request.body, answer: { status: replay.status, text: replay.body }, until };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The claim for a paused 3-D Secure send the remembered approval covers, or null: no approval covers sends here, its
|
|
108
|
+
* window has passed, or payment-core's rule refuses the send against the sends already claimed. Synchronous, and it
|
|
109
|
+
* reserves the send, so two requests can never both be claimed as the next one.
|
|
110
|
+
*/
|
|
111
|
+
claim(request, now = Date.now()) {
|
|
112
|
+
const parent = this.parent;
|
|
113
|
+
if (this.stopped || !parent || now >= parent.until || typeof request.body !== 'string')
|
|
114
|
+
return null;
|
|
115
|
+
const verdict = payrexxFollowUpVerdict({
|
|
116
|
+
parent: { url: parent.url, body: parent.body, answer: parent.answer }, sent: this.sent,
|
|
117
|
+
request: { url: request.url, method: request.method, body: request.body },
|
|
118
|
+
});
|
|
119
|
+
if (!verdict.ok || !PAYREXX_FOLLOW_UP_KINDS.includes(verdict.kind))
|
|
120
|
+
return null;
|
|
121
|
+
const entry = { kind: verdict.kind, state: 'pending' };
|
|
122
|
+
this.sent.push(entry);
|
|
123
|
+
const contentType = Object.entries(request.headers ?? {}).find(([name]) => /^content-type$/i.test(name))?.[1];
|
|
124
|
+
return {
|
|
125
|
+
authorizationId: parent.authorizationId, kind: verdict.kind, until: parent.until,
|
|
126
|
+
request: { url: request.url, method: 'POST', headers: { 'content-type': typeof contentType === 'string' ? contentType : 'application/x-www-form-urlencoded; charset=UTF-8' },
|
|
127
|
+
body: request.body },
|
|
128
|
+
settle(answer) {
|
|
129
|
+
if (entry.state !== 'pending')
|
|
130
|
+
return;
|
|
131
|
+
// A copy of the answer as it was settled: a caller that changes its own object later changes no claim.
|
|
132
|
+
if (answer) {
|
|
133
|
+
entry.state = 'answered';
|
|
134
|
+
entry.answer = { status: answer.status, text: answer.text };
|
|
135
|
+
}
|
|
136
|
+
else
|
|
137
|
+
entry.state = 'failed';
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
package/dist/pre-claim.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { type VerifoneProfile } from './verifone.js';
|
|
|
16
16
|
import { type WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
|
|
17
17
|
import { type PaddleVgsPayGate } from './paddle-vgs-pay.js';
|
|
18
18
|
import { type EbanxFollowUps } from './ebanx.js';
|
|
19
|
+
import { type PayrexxFollowUps } from './payrexx.js';
|
|
19
20
|
/** The native Stripe step the gate claimed, or null when none was: exactly what StripeCheckoutGate.claim returns. */
|
|
20
21
|
type StripeStep = ReturnType<StripeCheckoutGate['claim']>;
|
|
21
22
|
/** A synthetic response a gate fulfills locally. The effect applies CORS and encoding. */
|
|
@@ -116,6 +117,20 @@ export interface PreClaimContext {
|
|
|
116
117
|
headers: Record<string, string>;
|
|
117
118
|
body: string;
|
|
118
119
|
}>;
|
|
120
|
+
/** VaultClient.sendPayrexxFollowUp: a 3-D Secure send an approved Payrexx card request covers, from the cardholder's device. */
|
|
121
|
+
sendPayrexxFollowUp?(authorizationId: string, request: {
|
|
122
|
+
url: string;
|
|
123
|
+
method: 'POST';
|
|
124
|
+
headers: Record<string, string>;
|
|
125
|
+
body: string;
|
|
126
|
+
}, options?: {
|
|
127
|
+
signal?: AbortSignal;
|
|
128
|
+
until?: number;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
status: number;
|
|
131
|
+
headers: Record<string, string>;
|
|
132
|
+
body: string;
|
|
133
|
+
}>;
|
|
119
134
|
};
|
|
120
135
|
guards: {
|
|
121
136
|
matches(url: string, method?: string): boolean;
|
|
@@ -226,6 +241,12 @@ export interface PreClaimContext {
|
|
|
226
241
|
* is refused, as before the approval covered them.
|
|
227
242
|
*/
|
|
228
243
|
ebanx?: EbanxFollowUps;
|
|
244
|
+
/**
|
|
245
|
+
* The attachment's approved Payrexx card request and the 3-D Secure sends claimed under it (payrexx.ts
|
|
246
|
+
* PayrexxFollowUps). Absent, or holding no approval whose answer started 3-D Secure, and every 3-D Secure send of the
|
|
247
|
+
* card frame is refused.
|
|
248
|
+
*/
|
|
249
|
+
payrexx?: PayrexxFollowUps;
|
|
229
250
|
}
|
|
230
251
|
/**
|
|
231
252
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
@@ -260,6 +281,7 @@ export declare const transbankPreClaim: PreClaimGate;
|
|
|
260
281
|
export declare const redsysPreClaim: PreClaimGate;
|
|
261
282
|
export declare const quickpayPreClaim: PreClaimGate;
|
|
262
283
|
export declare const comgatePreClaim: PreClaimGate;
|
|
284
|
+
export declare const eximbayPreClaim: PreClaimGate;
|
|
263
285
|
export declare const paypalHostedFieldsPreClaim: PreClaimGate;
|
|
264
286
|
export declare const paypalCardFormsPreClaim: PreClaimGate;
|
|
265
287
|
export declare const cybersourceSecureAcceptancePreClaim: PreClaimGate;
|
|
@@ -277,6 +299,7 @@ export declare function routesHipayVault(vault: {
|
|
|
277
299
|
recognizesProcessor?: (psp: string) => boolean;
|
|
278
300
|
} | null | undefined, url: string): boolean;
|
|
279
301
|
export declare const ebanxPreClaim: PreClaimGate;
|
|
302
|
+
export declare const payrexxPreClaim: PreClaimGate;
|
|
280
303
|
/** The processor gates, in order. A new family adds one entry here; the terminal
|
|
281
304
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
282
305
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
package/dist/pre-claim.js
CHANGED
|
@@ -86,6 +86,7 @@ import { isRedsysEntry, isRedsysHost, redsysEntryUrl } from './redsys.js';
|
|
|
86
86
|
import { isArmedPrzelewy24Url, isPrzelewy24PayStep } from './przelewy24.js';
|
|
87
87
|
import { isQuickpayCardLookup, isQuickpayEntry, quickpayEntryUrl } from './quickpay.js';
|
|
88
88
|
import { isArmedComgateUrl } from './comgate.js';
|
|
89
|
+
import { isArmedEximbayUrl, isEximbayEntry } from './eximbay.js';
|
|
89
90
|
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
90
91
|
import { PAYPAL_CARD_FIELDS_REASON, PAYPAL_GUEST_CHECKOUT_REASON, isPaypalCardFieldsConfirm, isPaypalGraphql, paypalCardFieldsBody, paypalGraphqlCard, paypalGraphqlNamesCardOperation, } from './paypal-card-forms.js';
|
|
91
92
|
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
|
|
@@ -93,6 +94,7 @@ import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
|
|
|
93
94
|
import { paddleVgsPayEventUrl } from './paddle-vgs-pay.js';
|
|
94
95
|
import { HIPAY_PSP, HIPAY_TOKEN_URLS, HIPAY_UPDATE_PATH, HIPAY_VAULT_HOSTS, hipayNumberOnlyRequest } from './hipay.generated.js';
|
|
95
96
|
import { EBANX_RESEND_REASON, EbanxFollowUpError, ebanxResendRefusalOf, isEbanxResendUrl, } from './ebanx.js';
|
|
97
|
+
import { PAYREXX_RESEND_REASON, PayrexxFollowUpError, isPayrexxResend, } from './payrexx.js';
|
|
96
98
|
/**
|
|
97
99
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
98
100
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -1667,7 +1669,7 @@ export const ekashuPreClaim = (ctx) => {
|
|
|
1667
1669
|
return processorPageAbort(ctx);
|
|
1668
1670
|
};
|
|
1669
1671
|
// The stop for a card post into a processor's own page that only the cardholder's device may pay (NMI's, Redsys's,
|
|
1670
|
-
// QuickPay's).
|
|
1672
|
+
// QuickPay's, Eximbay's).
|
|
1671
1673
|
async function processorPageAbort(ctx) {
|
|
1672
1674
|
const { url, method } = ctx.request;
|
|
1673
1675
|
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
@@ -1762,6 +1764,25 @@ export const comgatePreClaim = (ctx) => {
|
|
|
1762
1764
|
return undefined;
|
|
1763
1765
|
return processorPageAbort(ctx);
|
|
1764
1766
|
};
|
|
1767
|
+
// Eximbay's payment window (eximbay.ts). Once this build's registry serves Eximbay's entry on a host (a capability the
|
|
1768
|
+
// API serves only to a company Agentcard turned Eximbay on for), every request to that host pauses, and this gate
|
|
1769
|
+
// judges the POSTs:
|
|
1770
|
+
// - the merchant's entry (the entry path exactly, no query) defers, and the terminal arbiter pauses it for the claim
|
|
1771
|
+
// flow like any card request: the cardholder's device pays it;
|
|
1772
|
+
// - every other POST to Eximbay (the step that opens the window, the window's amount query and its card lookup,
|
|
1773
|
+
// which carries the typed card's first eight digits, its card post, and its close and method posts) aborts as an
|
|
1774
|
+
// unsupported checkout, so Eximbay never sees the card the agent typed. The agent's browser only reaches Eximbay's
|
|
1775
|
+
// window when the entry was not paused (an attachment made too late, say), and then only the cardholder's device
|
|
1776
|
+
// can pay.
|
|
1777
|
+
// A GET (the window's scripts and images) is not this gate's and continues. With Eximbay not served, the entry is no
|
|
1778
|
+
// card request, the gate defers, and every request continues as before. Synchronous until it must abort, like every
|
|
1779
|
+
// gate. Exported for the gate registry's tests.
|
|
1780
|
+
export const eximbayPreClaim = (ctx) => {
|
|
1781
|
+
const { url, method } = ctx.request;
|
|
1782
|
+
if (String(method).toUpperCase() !== 'POST' || !isArmedEximbayUrl(ctx.vault, url) || isEximbayEntry(url, method))
|
|
1783
|
+
return undefined;
|
|
1784
|
+
return processorPageAbort(ctx);
|
|
1785
|
+
};
|
|
1765
1786
|
// PayPal's legacy Hosted Fields (paypal-hosted-fields.ts). Its tokenization frame posts the card the
|
|
1766
1787
|
// agent typed to the Orders v2 confirm-payment-source on cors.api.paypal.com (or its sandbox), which
|
|
1767
1788
|
// no Vault mode takes over, so every company's adapters hand each POST there to this gate:
|
|
@@ -2144,7 +2165,7 @@ async function hipayRefused(ctx, reason) {
|
|
|
2144
2165
|
// again. A checkout whose token request was already approved keeps that approval's state (lifecycle.unsupported),
|
|
2145
2166
|
// and its follow-ups stop (EbanxFollowUps.stop): a later request the approval would cover is refused too, so the
|
|
2146
2167
|
// page cannot finish 3-D Secure and pay through the confirm after a refusal. A failed send stops them the same way.
|
|
2147
|
-
// A send goes on only while the checkout itself names the remembered approval (
|
|
2168
|
+
// A send goes on only while the checkout itself names the remembered approval (followUpMaySend): it awaits the
|
|
2148
2169
|
// merchant, reports the bank's challenge, or holds that approval's answer for the page (awaiting_approval while
|
|
2149
2170
|
// the token's answer is on its way, since the adapters remember the approval before they hand the answer over, or
|
|
2150
2171
|
// ready_to_submit while the approval waits for the page's retry). A new token request awaiting the person, a new
|
|
@@ -2173,38 +2194,39 @@ export const ebanxPreClaim = (ctx) => {
|
|
|
2173
2194
|
catch {
|
|
2174
2195
|
body = null;
|
|
2175
2196
|
}
|
|
2176
|
-
const claim = ctx.ebanx && typeof ctx.vault.sendEbanxFollowUp === 'function' &&
|
|
2197
|
+
const claim = ctx.ebanx && typeof ctx.vault.sendEbanxFollowUp === 'function' && followUpMaySend(ctx, ctx.ebanx)
|
|
2177
2198
|
? ctx.ebanx.claim({ url, method: 'POST', headers: ctx.request.headers ?? {}, body }) : null;
|
|
2178
2199
|
return claim ? ebanxFollowUp(ctx, claim) : ebanxResendRefused(ctx, refusal);
|
|
2179
2200
|
};
|
|
2180
2201
|
/**
|
|
2181
2202
|
* The checkout states in which a send may go on under the remembered approval, an allowlist: the checkout awaits the
|
|
2182
|
-
* merchant, the application reported the bank's challenge (requestUserAction), the
|
|
2203
|
+
* merchant, the application reported the bank's challenge (requestUserAction), the approved answer is still on its way
|
|
2183
2204
|
* to the page (awaiting_approval: the page may send its first 3-D Secure call the moment the answer lands), or the
|
|
2184
2205
|
* approval waits for the page's retry (ready_to_submit). Every other state refuses, idle and the final ones among them.
|
|
2206
|
+
* The same for EBANX's sends and Payrexx's.
|
|
2185
2207
|
*/
|
|
2186
|
-
const
|
|
2208
|
+
const FOLLOW_UP_SENDING_STATES = new Set(['awaiting_merchant', 'requires_user_action', 'awaiting_approval', 'ready_to_submit']);
|
|
2187
2209
|
/**
|
|
2188
2210
|
* Whether a send may go on: nothing aborted the checkout, no reconciliation of the merchant's order is running (one
|
|
2189
2211
|
* that resolves failed while a confirm is out would let the application start another attempt), it is in one of
|
|
2190
|
-
*
|
|
2191
|
-
* person (awaiting_approval with no approval, or another one), a new
|
|
2192
|
-
* checkout and another approval's state all refuse.
|
|
2212
|
+
* FOLLOW_UP_SENDING_STATES, and its state names the approval the attachment remembered (`followUps`, EBANX's or
|
|
2213
|
+
* Payrexx's). A new card request awaiting the person (awaiting_approval with no approval, or another one), a new
|
|
2214
|
+
* attempt after a merchant failure, a prepared checkout and another approval's state all refuse.
|
|
2193
2215
|
*/
|
|
2194
|
-
function
|
|
2195
|
-
if (ctx.lifecycleAborted() || ctx.terminal() || !
|
|
2216
|
+
function followUpMaySend(ctx, followUps) {
|
|
2217
|
+
if (ctx.lifecycleAborted() || ctx.terminal() || !followUps || ctx.lifecycle.isReconciling())
|
|
2196
2218
|
return false;
|
|
2197
|
-
const approval =
|
|
2219
|
+
const approval = followUps.authorizationId;
|
|
2198
2220
|
const state = ctx.lifecycle.getState();
|
|
2199
|
-
return approval !== null &&
|
|
2221
|
+
return approval !== null && FOLLOW_UP_SENDING_STATES.has(state.status) && state.authorizationId === approval;
|
|
2200
2222
|
}
|
|
2201
2223
|
/**
|
|
2202
2224
|
* The checkout's final merchant results: completed, failed, declined, timed out, cancelled, an unknown outcome, or an
|
|
2203
2225
|
* unsupported request. A 3-D Secure call, which charges nothing, that fails in one of them leaves that outcome alone.
|
|
2204
2226
|
*/
|
|
2205
|
-
const
|
|
2206
|
-
/** Whether the checkout already ended (
|
|
2207
|
-
const
|
|
2227
|
+
const FOLLOW_UP_FINAL_STATES = new Set(['completed', 'failed', 'declined', 'timed_out', 'cancelled', 'outcome_unknown', 'unsupported']);
|
|
2228
|
+
/** Whether the checkout already ended (FOLLOW_UP_FINAL_STATES): its state and reason stand, whatever a later 3-D Secure call does. */
|
|
2229
|
+
const followUpCheckoutEnded = (ctx) => FOLLOW_UP_FINAL_STATES.has(ctx.lifecycle.getState().status);
|
|
2208
2230
|
/**
|
|
2209
2231
|
* A confirm whose card may have reached EBANX and whose answer the page never got: only a payment the merchant
|
|
2210
2232
|
* already confirmed (completed) stands, and every other state of this approval turns unknown, since EBANX may have
|
|
@@ -2245,7 +2267,7 @@ async function ebanxUnreadable(ctx) {
|
|
|
2245
2267
|
* (CheckoutLifecycle.followUpStarted): meanwhile reconcile() and retryAfterMerchantFailure() throw, so the application
|
|
2246
2268
|
* starts no other attempt while a confirm may pay. A confirm that may have left keeps only a payment the merchant
|
|
2247
2269
|
* confirmed (completed); any other state of its approval turns unknown (ebanxConfirmUnconfirmed). Any other send keeps
|
|
2248
|
-
* a final outcome (
|
|
2270
|
+
* a final outcome (FOLLOW_UP_FINAL_STATES) with its reason.
|
|
2249
2271
|
*/
|
|
2250
2272
|
async function ebanxFollowUp(ctx, claim) {
|
|
2251
2273
|
const settle = ctx.lifecycle.followUpStarted();
|
|
@@ -2271,7 +2293,7 @@ async function sendEbanxFollowUp(ctx, claim) {
|
|
|
2271
2293
|
ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason, cardSent } });
|
|
2272
2294
|
if (cardSent && claim.kind === 'confirm')
|
|
2273
2295
|
ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_confirm_unconfirmed');
|
|
2274
|
-
else if (!
|
|
2296
|
+
else if (!followUpCheckoutEnded(ctx))
|
|
2275
2297
|
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
2276
2298
|
await ctx.effects.abort();
|
|
2277
2299
|
return { kind: 'abort' };
|
|
@@ -2286,7 +2308,7 @@ async function sendEbanxFollowUp(ctx, claim) {
|
|
|
2286
2308
|
ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason: 'handoff_failed', cardSent: true } });
|
|
2287
2309
|
if (claim.kind === 'confirm')
|
|
2288
2310
|
ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_follow_up_handoff_failed');
|
|
2289
|
-
else if (!
|
|
2311
|
+
else if (!followUpCheckoutEnded(ctx))
|
|
2290
2312
|
ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
|
|
2291
2313
|
await ctx.effects.abort();
|
|
2292
2314
|
return { kind: 'abort' };
|
|
@@ -2294,6 +2316,95 @@ async function sendEbanxFollowUp(ctx, claim) {
|
|
|
2294
2316
|
ctx.onEvent?.({ type: 'ebanx_follow_up_answered', detail: { ...detail, status: answer.status } });
|
|
2295
2317
|
return { kind: 'answer' };
|
|
2296
2318
|
}
|
|
2319
|
+
// Payrexx Pay's card frame after its card request (payrexx.ts, payment-core payrexx.js). When Payrexx answers the card
|
|
2320
|
+
// request by starting 3-D Secure, the frame posts the card again to its own URL for the authentication after the ACS
|
|
2321
|
+
// method and the post-authentication after the bank's challenge. On a page this SDK is attached to that card is the
|
|
2322
|
+
// agent's placeholder, and the frame's URL is the card request's, so this gate judges each such post before the
|
|
2323
|
+
// card-request arbiter would pause it as a new card request:
|
|
2324
|
+
// - one the approved card request on this attachment covers (PayrexxFollowUps.claim) goes to the cardholder's device
|
|
2325
|
+
// under that approval (payrexxFollowUp below), and the frame receives Payrexx's answer to the real card;
|
|
2326
|
+
// - any other aborts as an unsupported checkout, reason `unsupported_payrexx_card_resend`, with no claim, create or
|
|
2327
|
+
// prompt, so Payrexx never receives the placeholder as a card, and the attachment holds. A checkout whose card
|
|
2328
|
+
// request was already approved keeps that approval's state, and its sends stop (PayrexxFollowUps.stop), as
|
|
2329
|
+
// EBANX's do. A send goes on only while the checkout itself names the remembered approval (followUpMaySend).
|
|
2330
|
+
// The card request itself, a wallet's body with no card value, a body the adapter could not read and every other
|
|
2331
|
+
// request defer: the arbiter pauses the card request for its recognizer and judges the rest (card-fields.js).
|
|
2332
|
+
// Synchronous until it must abort or send, like every gate. Exported for the gate registry's tests.
|
|
2333
|
+
export const payrexxPreClaim = (ctx) => {
|
|
2334
|
+
const { url, method } = ctx.request;
|
|
2335
|
+
if (!isPayrexxResend(url, method, () => ctx.request.body))
|
|
2336
|
+
return undefined;
|
|
2337
|
+
let body;
|
|
2338
|
+
try {
|
|
2339
|
+
body = ctx.request.body;
|
|
2340
|
+
}
|
|
2341
|
+
catch {
|
|
2342
|
+
body = null;
|
|
2343
|
+
}
|
|
2344
|
+
const claim = ctx.payrexx && typeof ctx.vault.sendPayrexxFollowUp === 'function' && followUpMaySend(ctx, ctx.payrexx)
|
|
2345
|
+
? ctx.payrexx.claim({ url, method: 'POST', headers: ctx.request.headers ?? {}, body }) : null;
|
|
2346
|
+
return claim ? payrexxFollowUp(ctx, claim) : payrexxResendRefused(ctx);
|
|
2347
|
+
};
|
|
2348
|
+
async function payrexxResendRefused(ctx) {
|
|
2349
|
+
const { url, method } = ctx.request;
|
|
2350
|
+
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
2351
|
+
ctx.payrexx?.stop();
|
|
2352
|
+
ctx.lifecycle.unsupported(PAYREXX_RESEND_REASON);
|
|
2353
|
+
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason: PAYREXX_RESEND_REASON, step: 'three_d_secure' } });
|
|
2354
|
+
await ctx.effects.abort();
|
|
2355
|
+
return { kind: 'abort' };
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* A 3-D Secure send the approved card request covers: the cardholder's device sends it with the real card
|
|
2359
|
+
* (VaultClient.sendPayrexxFollowUp), and the frame receives Payrexx's answer, with no second prompt. The events name the
|
|
2360
|
+
* approval and the step, never the request, the card or Payrexx's answer. A send charges nothing: the merchant's page
|
|
2361
|
+
* charges only the token a later answer names, at its own validateForm. So a send that gets no answer, or whose answer
|
|
2362
|
+
* the frame never receives, leaves no payment behind it: the frame's request fails, the attachment holds as the
|
|
2363
|
+
* refusal above does (a final outcome keeps its state and reason), and nothing more is sent. While a send is out it
|
|
2364
|
+
* counts as work in flight (CheckoutLifecycle.followUpStarted), so the application starts no other attempt over it.
|
|
2365
|
+
*/
|
|
2366
|
+
async function payrexxFollowUp(ctx, claim) {
|
|
2367
|
+
const settle = ctx.lifecycle.followUpStarted();
|
|
2368
|
+
try {
|
|
2369
|
+
return await sendPayrexxFollowUp(ctx, claim);
|
|
2370
|
+
}
|
|
2371
|
+
finally {
|
|
2372
|
+
settle();
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async function sendPayrexxFollowUp(ctx, claim) {
|
|
2376
|
+
const detail = { authorizationId: claim.authorizationId, step: claim.kind };
|
|
2377
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_requested', detail });
|
|
2378
|
+
let answer;
|
|
2379
|
+
try {
|
|
2380
|
+
answer = await ctx.vault.sendPayrexxFollowUp(claim.authorizationId, claim.request, { signal: ctx.lifecycle.abort.signal, until: claim.until });
|
|
2381
|
+
}
|
|
2382
|
+
catch (error) {
|
|
2383
|
+
claim.settle(null);
|
|
2384
|
+
ctx.payrexx?.stop();
|
|
2385
|
+
const reason = error instanceof PayrexxFollowUpError ? error.reason : 'follow_up_unavailable';
|
|
2386
|
+
const cardSent = error instanceof PayrexxFollowUpError ? error.cardSent : true;
|
|
2387
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_failed', detail: { ...detail, reason, cardSent } });
|
|
2388
|
+
if (!followUpCheckoutEnded(ctx))
|
|
2389
|
+
ctx.lifecycle.unsupported(PAYREXX_RESEND_REASON);
|
|
2390
|
+
await ctx.effects.abort();
|
|
2391
|
+
return { kind: 'abort' };
|
|
2392
|
+
}
|
|
2393
|
+
claim.settle({ status: answer.status, text: answer.body });
|
|
2394
|
+
try {
|
|
2395
|
+
await ctx.effects.answer({ status: answer.status, headers: answer.headers, body: answer.body });
|
|
2396
|
+
}
|
|
2397
|
+
catch {
|
|
2398
|
+
ctx.payrexx?.stop();
|
|
2399
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_failed', detail: { ...detail, reason: 'handoff_failed', cardSent: true } });
|
|
2400
|
+
if (!followUpCheckoutEnded(ctx))
|
|
2401
|
+
ctx.lifecycle.unsupported(PAYREXX_RESEND_REASON);
|
|
2402
|
+
await ctx.effects.abort();
|
|
2403
|
+
return { kind: 'abort' };
|
|
2404
|
+
}
|
|
2405
|
+
ctx.onEvent?.({ type: 'payrexx_follow_up_answered', detail: { ...detail, status: answer.status } });
|
|
2406
|
+
return { kind: 'answer' };
|
|
2407
|
+
}
|
|
2297
2408
|
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
2298
2409
|
// neither a native Stripe step nor a recognized card request continues, unless
|
|
2299
2410
|
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
@@ -2327,8 +2438,8 @@ async function unsupportedContinue(ctx) {
|
|
|
2327
2438
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
2328
2439
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
2329
2440
|
* pauses each one itself. */
|
|
2330
|
-
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, tokenexIframePreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, comgatePreClaim, przelewy24PreClaim, transbankPreClaim, paypalHostedFieldsPreClaim,
|
|
2331
|
-
paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, hipayPreClaim];
|
|
2441
|
+
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, tokenexIframePreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, comgatePreClaim, przelewy24PreClaim, transbankPreClaim, eximbayPreClaim, paypalHostedFieldsPreClaim,
|
|
2442
|
+
paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, payrexxPreClaim, hipayPreClaim];
|
|
2332
2443
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
2333
2444
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
2334
2445
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|
|
@@ -25,11 +25,15 @@
|
|
|
25
25
|
* Cashier's card frame for a token, too soon for an approval after Pay. Its one environment is
|
|
26
26
|
* production: Wix's tokenizer has no test host.
|
|
27
27
|
*
|
|
28
|
+
* paygent: prepared only. Paygent's PaygentToken.js abandons its token request 35 seconds after the
|
|
29
|
+
* merchant's press sends it, too soon for an approval after the press. Its environments are its two token
|
|
30
|
+
* hosts: token.paygent.co.jp (production) and sandbox.paygent.co.jp (sandbox).
|
|
31
|
+
*
|
|
28
32
|
* hipay: prepared only. The merchant's page gives HiPay's hosted fields 20 seconds after Pay, too soon for an
|
|
29
33
|
* approval after Pay. Its environments are its two vault hosts: secure2-vault (production) and
|
|
30
34
|
* stage-secure2-vault (sandbox).
|
|
31
35
|
*/
|
|
32
|
-
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv' | 'cybersource_flex' | 'aci' | 'creditguard' | 'worldpay_eprotect' | 'amadeus_checkout' | 'verifone' | 'cybersource_secure_acceptance' | 'paddle_vgs' | 'wix_payments' | 'hipay';
|
|
36
|
+
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv' | 'cybersource_flex' | 'aci' | 'creditguard' | 'worldpay_eprotect' | 'amadeus_checkout' | 'verifone' | 'cybersource_secure_acceptance' | 'paddle_vgs' | 'wix_payments' | 'paygent' | 'hipay';
|
|
33
37
|
export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
|
|
34
38
|
/**
|
|
35
39
|
* How the approved card reaches the processor: the device's own request (token), or
|
|
@@ -66,7 +70,8 @@ export declare function preparedApprovalAnswersRetry(psp: string): boolean;
|
|
|
66
70
|
* its own signing keys. A Verifone preparation pays in the environment of the key its reviewed
|
|
67
71
|
* profile pins. CyberSource Secure Acceptance's are separate hosts too: secureacceptance is
|
|
68
72
|
* live, testsecureacceptance is test. So are Paddle VGS's two card URLs: vault is live,
|
|
69
|
-
* sandbox-vault is test. So are
|
|
73
|
+
* sandbox-vault is test. So are Paygent's two token hosts: token is live, sandbox is test. So are
|
|
74
|
+
* HiPay's two vault hosts: secure2-vault is live, stage-secure2-vault is test.
|
|
70
75
|
*/
|
|
71
76
|
export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
|
|
72
77
|
/**
|
|
@@ -15,6 +15,7 @@ import { isPreparedVerifoneRequest } from './verifone.js';
|
|
|
15
15
|
import { isPreparedSecureAcceptanceRequest, secureAcceptanceEnvironment, secureAcceptancePreparationUrl, secureAcceptanceRequestRefusal, } from './cybersource-secure-acceptance.generated.js';
|
|
16
16
|
import { PADDLE_VGS_CARDS_URLS, isPreparedPaddleVgsRequest } from './paddle-vgs.generated.js';
|
|
17
17
|
import { WIX_PAYMENTS_TOKENIZE_URL, isPreparedWixPaymentsRequest } from './wix-payments.generated.js';
|
|
18
|
+
import { paygentEnvironment, paygentPreparationUrl, paygentRequestRefusal } from './paygent.generated.js';
|
|
18
19
|
import { HIPAY_TOKEN_URLS, hipayRequestRefusal, hipayTokenEnvironment, isPreparedHipayRequest } from './hipay.generated.js';
|
|
19
20
|
export function preparationMode(psp) {
|
|
20
21
|
return psp === 'adyen' || psp === 'fiserv' || psp === 'verifone' ? 'cse' : 'token';
|
|
@@ -50,7 +51,8 @@ export function preparedApprovalAnswersRetry(psp) {
|
|
|
50
51
|
* its own signing keys. A Verifone preparation pays in the environment of the key its reviewed
|
|
51
52
|
* profile pins. CyberSource Secure Acceptance's are separate hosts too: secureacceptance is
|
|
52
53
|
* live, testsecureacceptance is test. So are Paddle VGS's two card URLs: vault is live,
|
|
53
|
-
* sandbox-vault is test. So are
|
|
54
|
+
* sandbox-vault is test. So are Paygent's two token hosts: token is live, sandbox is test. So are
|
|
55
|
+
* HiPay's two vault hosts: secure2-vault is live, stage-secure2-vault is test.
|
|
54
56
|
*/
|
|
55
57
|
export function validPreparationEnvironment(psp, environment) {
|
|
56
58
|
if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
|
|
@@ -59,7 +61,7 @@ export function validPreparationEnvironment(psp, environment) {
|
|
|
59
61
|
if (psp === 'wix_payments')
|
|
60
62
|
return environment === 'production';
|
|
61
63
|
return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe', 'fiserv', 'cybersource_flex', 'aci', 'creditguard', 'worldpay_eprotect', 'amadeus_checkout', 'verifone',
|
|
62
|
-
'cybersource_secure_acceptance', 'paddle_vgs', 'hipay'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
64
|
+
'cybersource_secure_acceptance', 'paddle_vgs', 'paygent', 'hipay'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* ACI's prepared endpoint, the literal the API names (backend checkout-prepared-processor.ts): its
|
|
@@ -107,6 +109,9 @@ export function preparationEndpoint(psp, environment) {
|
|
|
107
109
|
// Wix Payments: the tokenize URL, the only URL the claim takes for it.
|
|
108
110
|
if (psp === 'wix_payments')
|
|
109
111
|
return WIX_PAYMENTS_TOKENIZE_URL;
|
|
112
|
+
// Paygent: the token URL on the environment's host, the only URL the claim takes for it.
|
|
113
|
+
if (psp === 'paygent')
|
|
114
|
+
return paygentPreparationUrl(environment);
|
|
110
115
|
// HiPay: the token create on the environment's vault, the only URL the claim takes for it.
|
|
111
116
|
if (psp === 'hipay')
|
|
112
117
|
return HIPAY_TOKEN_URLS[environment];
|
|
@@ -267,6 +272,15 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
|
|
|
267
272
|
// site's app instance and the card frame's body), the rule the API and the Vault bind with too.
|
|
268
273
|
if (psp === 'wix_payments')
|
|
269
274
|
return isPreparedWixPaymentsRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers ?? {});
|
|
275
|
+
// Paygent: the environment's own token URL and payment-core's whole request rule (the script's text/plain
|
|
276
|
+
// and createToken's body byte for byte), the rule the API and the Vault bind with too. The method as the page
|
|
277
|
+
// sent it: the device sends only an uppercase POST. Which merchant_id and token key a live request may name
|
|
278
|
+
// is the API's to judge at the bind and the Vault's before it writes the card, against the reviewed pins
|
|
279
|
+
// each of them holds, so a merchant a later release reviews needs no new build of this library.
|
|
280
|
+
if (psp === 'paygent') {
|
|
281
|
+
return paygentEnvironment(requestUrl) === environment
|
|
282
|
+
&& paygentRequestRefusal({ url: requestUrl, method, headers: headers ?? {}, body: body ?? null }) === null;
|
|
283
|
+
}
|
|
270
284
|
// HiPay: the environment's own vault and payment-core's whole request rule (a JSON content type, a public username of
|
|
271
285
|
// that environment and a complete create, never the hosted fields' number-only post), the rule the API and the Vault
|
|
272
286
|
// bind with too.
|