@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.
@@ -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
+ }
@@ -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 (ebanxMaySend): it awaits the
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' && ebanxMaySend(ctx)
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 token's answer is still on its way
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 EBANX_SENDING_STATES = new Set(['awaiting_merchant', 'requires_user_action', 'awaiting_approval', 'ready_to_submit']);
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
- * EBANX_SENDING_STATES, and its state names the approval the attachment remembered. A new token request awaiting the
2191
- * person (awaiting_approval with no approval, or another one), a new attempt after a merchant failure, a prepared
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 ebanxMaySend(ctx) {
2195
- if (ctx.lifecycleAborted() || ctx.terminal() || !ctx.ebanx || ctx.lifecycle.isReconciling())
2216
+ function followUpMaySend(ctx, followUps) {
2217
+ if (ctx.lifecycleAborted() || ctx.terminal() || !followUps || ctx.lifecycle.isReconciling())
2196
2218
  return false;
2197
- const approval = ctx.ebanx.authorizationId;
2219
+ const approval = followUps.authorizationId;
2198
2220
  const state = ctx.lifecycle.getState();
2199
- return approval !== null && EBANX_SENDING_STATES.has(state.status) && state.authorizationId === approval;
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 EBANX_FINAL_STATES = new Set(['completed', 'failed', 'declined', 'timed_out', 'cancelled', 'outcome_unknown', 'unsupported']);
2206
- /** Whether the checkout already ended (EBANX_FINAL_STATES): its state and reason stand, whatever a later 3-D Secure call does. */
2207
- const ebanxCheckoutEnded = (ctx) => EBANX_FINAL_STATES.has(ctx.lifecycle.getState().status);
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 (EBANX_FINAL_STATES) with its reason.
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 (!ebanxCheckoutEnded(ctx))
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 (!ebanxCheckoutEnded(ctx))
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 HiPay's two vault hosts: secure2-vault is live, stage-secure2-vault is test.
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 HiPay's two vault hosts: secure2-vault is live, stage-secure2-vault is test.
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.