@agent-cards/sdk 0.23.13 → 0.23.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/PREFLIGHT.md +3 -1
  3. package/README.md +54 -13
  4. package/dist/adyen-merchant-hosted.generated.js +2 -2
  5. package/dist/amadeus.d.ts +5 -2
  6. package/dist/amadeus.js +17 -3
  7. package/dist/card-fields.generated.js +924 -3
  8. package/dist/cdp.js +83 -19
  9. package/dist/client.d.ts +97 -1
  10. package/dist/client.js +235 -16
  11. package/dist/creditguard.generated.d.ts +100 -2
  12. package/dist/creditguard.generated.js +333 -3
  13. package/dist/datatrans.generated.d.ts +19 -4
  14. package/dist/datatrans.generated.js +15 -3
  15. package/dist/ebanx.d.ts +107 -0
  16. package/dist/ebanx.generated.d.ts +78 -0
  17. package/dist/ebanx.generated.js +577 -0
  18. package/dist/ebanx.js +159 -0
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +2 -0
  21. package/dist/lifecycle.d.ts +32 -5
  22. package/dist/lifecycle.js +40 -5
  23. package/dist/paddle-tokenex.d.ts +56 -0
  24. package/dist/paddle-tokenex.generated.d.ts +67 -0
  25. package/dist/paddle-tokenex.generated.js +951 -0
  26. package/dist/paddle-tokenex.js +95 -0
  27. package/dist/paypal-card-forms.d.ts +73 -0
  28. package/dist/paypal-card-forms.js +126 -0
  29. package/dist/pre-claim.d.ts +39 -1
  30. package/dist/pre-claim.js +348 -18
  31. package/dist/preparation.d.ts +13 -0
  32. package/dist/preparation.js +22 -0
  33. package/dist/prepared-processor.d.ts +5 -1
  34. package/dist/prepared-processor.js +17 -2
  35. package/dist/primer.generated.js +960 -39
  36. package/dist/redsys.d.ts +42 -0
  37. package/dist/redsys.js +68 -0
  38. package/dist/stripe-checkout.generated.js +494 -9
  39. package/dist/wix-payments.generated.d.ts +42 -0
  40. package/dist/wix-payments.generated.js +241 -0
  41. package/package.json +8 -5
package/dist/pre-claim.js CHANGED
@@ -15,10 +15,13 @@
15
15
  // Converge's hpp/process URL, which also carries wallet, debit, follow-up and
16
16
  // card-saving bodies, WSPay's confirm URL, which also carries bodies with
17
17
  // no new card, Shiji STS's getTokenId and getCvvId, which also carry
18
- // requests the vault must never pay, and PayPal's legacy Hosted Fields
19
- // confirm and CyberSource Secure Acceptance's Silent Order POST, whose card
20
- // no Vault mode takes over, and Worldline MyCheckout's card form, held to the
21
- // card product its page chose.
18
+ // requests the vault must never pay, PayPal's legacy Hosted Fields
19
+ // confirm, PayPal's Card Fields and guest card form, and CyberSource Secure
20
+ // Acceptance's Silent Order POST, whose card no Vault mode takes over until a
21
+ // recognizer does, Worldline MyCheckout's card form, held to the
22
+ // card product its page chose, and EBANX's 3-D Secure calls and hosted confirm,
23
+ // which send the card again after its token and leave only from the
24
+ // cardholder's device, under the token request's approval.
22
25
  // The first three used to be
23
26
  // written inline in Fetch.requestPaused and again in the page.route handler, so
24
27
  // a decision could drift between the two adapters and a new family had to be
@@ -63,7 +66,7 @@ import { DATATRANS_INVALID_REQUEST } from './datatrans.generated.js';
63
66
  import { isPelecardHost, pelecardRefusal } from './pelecard.js';
64
67
  import { ACI_CHECKOUT_REUSED } from './aci.js';
65
68
  import { ACI_EXPIRY_UNSTRIPPABLE } from './aci.generated.js';
66
- import { CREDITGUARD_PPS_HOSTS, classifyCreditguardRequest, creditguardNuveiRefusal, creditguardProcessRefusal } from './creditguard.generated.js';
69
+ import { CREDITGUARD_PPS_HOSTS, classifyCreditguardRequest, creditguardCompanionAnswer, creditguardCompanionCall, creditguardNuveiRefusal, creditguardProcessRefusal, creditguardProcessTxId, } from './creditguard.generated.js';
67
70
  import { isPrimerPciHost, primerRequestVerdict } from './primer.js';
68
71
  import { convergeRefusal, isConvergeHost } from './elavon-converge.js';
69
72
  import { isWspayHost, wspayRefusal } from './wspay.js';
@@ -76,10 +79,13 @@ import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoi
76
79
  import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isPayrailsPaymentUrl, isPayrailsStaticHost, payrailsKeyRefusal, payrailsPaymentRefusal, payrailsRequestKind, payrailsSaveRefusal, } from './payrails.js';
77
80
  import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
78
81
  import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
82
+ import { isRedsysEntry, isRedsysHost, redsysEntryUrl } from './redsys.js';
79
83
  import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
84
+ import { PAYPAL_CARD_FIELDS_REASON, PAYPAL_GUEST_CHECKOUT_REASON, isPaypalCardFieldsConfirm, isPaypalGraphql, paypalCardFieldsBody, paypalGraphqlCard, paypalGraphqlNamesCardOperation, } from './paypal-card-forms.js';
80
85
  import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
81
86
  import { isWorldlineMyCheckoutEndpoint } from './worldline-mycheckout.js';
82
87
  import { paddleVgsPayEventUrl } from './paddle-vgs-pay.js';
88
+ import { EBANX_RESEND_REASON, EbanxFollowUpError, ebanxResendRefusalOf, isEbanxResendUrl, } from './ebanx.js';
83
89
  /**
84
90
  * The URL an event may name for a request. For an armed merchant profile's
85
91
  * endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
@@ -1058,18 +1064,31 @@ async function aciRefuse(ctx, code) {
1058
1064
  await ctx.effects.abort();
1059
1065
  return { kind: 'abort' };
1060
1066
  }
1061
- // CreditGuard's hosted payment page (Hyp PPS), in two parts the generic card
1067
+ // CreditGuard's hosted payment page (Hyp PPS), in three parts the generic card
1062
1068
  // path cannot judge. Every other request on the PPS hosts (the page, its
1063
- // transaction read, /card/details, /cg3ds, assets) and the stock new-card
1064
- // submit defer, synchronously, and the terminal arbiter continues or pauses
1065
- // them; the pause then needs a ready preparation for this page's transaction.
1069
+ // transaction read, assets) and the stock new-card submit defer, synchronously,
1070
+ // and the terminal arbiter continues or pauses them; the pause then needs a
1071
+ // ready preparation for this page's transaction.
1072
+ // - The page's own calls beside the submit that carry the placeholder card:
1073
+ // its /card/details lookup and, on a 3-D Secure transaction, /cc/ddc/init,
1074
+ // /cg3ds/process/init and /cg3ds/process/outcome (payment-core
1075
+ // creditguardCompanionCall). Under a ready preparation for this page's own
1076
+ // transaction each is answered here, nothing sent: a foreign card, so the
1077
+ // page asks the agent for no Israeli ID; no device check; no challenge; and
1078
+ // an outcome that is not cancelled (creditguardCompanionAnswer). The page
1079
+ // then sends its submit, and the cardholder's device runs CreditGuard's 3-D
1080
+ // Secure with the real card before it sends its own. With no preparation
1081
+ // asked for, each goes on to CreditGuard as it always has; under any other
1082
+ // preparation, or in a shape the page does not send, it is aborted.
1066
1083
  // - A /process submit the cardholder's device would not fill (a saved card,
1067
1084
  // a wallet, "save my card", installments, DCC, an Israeli ID, a gift card,
1068
1085
  // another method, an unreadable body or other headers) is aborted with the device's own
1069
1086
  // reason, before any claim, create or prompt. A ready preparation stays
1070
- // ready, so the page's corrected submit can still pay.
1071
- // - PPS runs 3-D Secure itself, sending the placeholder card to Nuvei from
1072
- // inside its frame. The nuvei recognizer would pause that as a Nuvei
1087
+ // ready, so the page's corrected submit can still pay. Under a ready
1088
+ // preparation for its own transaction the encrypted submit (jwePayload)
1089
+ // defers like the plaintext one; the API holds it to the transaction's key.
1090
+ // - PPS runs Nuvei's 3-D Secure itself, sending the placeholder card to Nuvei
1091
+ // from inside its frame. The nuvei recognizer would pause that as a Nuvei
1073
1092
  // payment. A request to a Nuvei host that a PPS document sent, or that any
1074
1093
  // frame inside a PPS document sent, is aborted instead, the checkout
1075
1094
  // becomes unsupported and any preparation is retired: the page's flow is
@@ -1100,14 +1119,84 @@ const creditguardPreClaim = (ctx) => {
1100
1119
  // Any method reaches the /process check: payment-core blocks a non-POST, a query or an unreadable
1101
1120
  // body there as well, so nothing but the stock POST ever continues to CreditGuard.
1102
1121
  const verb = method.toUpperCase();
1103
- const verdict = classifyCreditguardRequest(url, verb, body ?? '', undefined);
1122
+ const companion = creditguardCompanionCall(url, verb, body ?? '');
1123
+ if (companion !== null)
1124
+ return creditguardCompanionPreClaim(ctx, companion);
1125
+ // The encrypted submit is the page's own only for a transaction that carries a key, which the API
1126
+ // reads; here it may defer only under a ready preparation for its own transaction.
1127
+ const txId = creditguardProcessTxId(url);
1128
+ const options = txId !== null && creditguardStanding(ctx, txId) === 'ready' ? { jwe: true } : undefined;
1129
+ const verdict = classifyCreditguardRequest(url, verb, body ?? '', options);
1104
1130
  if (verdict === null)
1105
1131
  return undefined;
1106
- const refusal = creditguardProcessRefusal({ url, method: verb, headers: ctx.request.headers, body: body ?? '' }, undefined);
1132
+ const refusal = creditguardProcessRefusal({ url, method: verb, headers: ctx.request.headers, body: body ?? '' }, options);
1107
1133
  if (verdict === 'card' && refusal === null)
1108
1134
  return undefined;
1109
1135
  return creditguardBlock(ctx, refusal ?? 'malformed_body');
1110
1136
  };
1137
+ /**
1138
+ * Whether an adapter must hand this request to the pre-claim gates as a CreditGuard page's own call
1139
+ * beside its submit: a companion endpoint (payment-core creditguardCompanionCall, as loosely as a
1140
+ * request could spell one) while this build's registry serves CreditGuard. The CDP adapter pauses
1141
+ * them already through the recognizer's host-wide URL patterns; attachToPlaywright's route predicate,
1142
+ * which otherwise sees only the submit, asks this.
1143
+ */
1144
+ export function isArmedCreditguardCall(vault, url) {
1145
+ if (typeof vault?.recognizesProcessor !== 'function' || !vault.recognizesProcessor('creditguard'))
1146
+ return false;
1147
+ try {
1148
+ return creditguardCompanionCall(url, 'POST', '') !== null;
1149
+ }
1150
+ catch {
1151
+ return false;
1152
+ }
1153
+ }
1154
+ /** The preparation's standing for a CreditGuard transaction (PreparationGate.creditguardStanding); 'none' where the context has none. */
1155
+ function creditguardStanding(ctx, txId) {
1156
+ try {
1157
+ return ctx.preparationGate.creditguardStanding?.(txId) ?? 'none';
1158
+ }
1159
+ catch {
1160
+ return 'refuse';
1161
+ }
1162
+ }
1163
+ /**
1164
+ * A CreditGuard page's own call beside its submit (see the comment above): with no preparation asked
1165
+ * for it defers and goes on as before; under a ready preparation for its own transaction it is
1166
+ * answered here when it is the page's own call, in its own microtask's decision, and refused when it
1167
+ * is not; under any other preparation it is refused. A refused call never names a body field.
1168
+ */
1169
+ function creditguardCompanionPreClaim(ctx, companion) {
1170
+ let engaged = true;
1171
+ try {
1172
+ engaged = ctx.preparationGate.isEngaged();
1173
+ }
1174
+ catch {
1175
+ engaged = true;
1176
+ }
1177
+ const standing = companion.txId !== null ? creditguardStanding(ctx, companion.txId) : engaged ? 'refuse' : 'none';
1178
+ if (standing === 'none')
1179
+ return undefined;
1180
+ if (standing !== 'ready')
1181
+ return creditguardBlock(ctx, 'preparation_mismatch');
1182
+ if (companion.refusal !== null || companion.step === null)
1183
+ return creditguardBlock(ctx, companion.refusal ?? 'not_companion_request');
1184
+ return creditguardCompanionAnswered(ctx, companion.step);
1185
+ }
1186
+ async function creditguardCompanionAnswered(ctx, step) {
1187
+ const answer = creditguardCompanionAnswer(step);
1188
+ if (answer && !ctx.interceptionAborted() && !ctx.lifecycleAborted() && !ctx.terminal()) {
1189
+ try {
1190
+ await ctx.effects.answer(answer);
1191
+ ctx.onEvent?.({ type: 'creditguard_call_answered', detail: { step } });
1192
+ return { kind: 'answer' };
1193
+ }
1194
+ catch { /* the page's request is gone; nothing was forwarded */ }
1195
+ }
1196
+ ctx.onEvent?.({ type: 'blocked', detail: { reason: 'creditguard_call_unanswered', step } });
1197
+ await ctx.effects.abort();
1198
+ return { kind: 'abort' };
1199
+ }
1111
1200
  /**
1112
1201
  * Who sent a request to a Nuvei host, as far as it decides the request:
1113
1202
  * 1. The browser sets Origin and Referer, and no page can make either name another page's
@@ -1504,9 +1593,10 @@ export const ekashuPreClaim = (ctx) => {
1504
1593
  return undefined;
1505
1594
  if (isEkashuEntry(url, method))
1506
1595
  return undefined;
1507
- return ekashuAbort(ctx);
1596
+ return processorPageAbort(ctx);
1508
1597
  };
1509
- async function ekashuAbort(ctx) {
1598
+ // The stop for a card post into a processor's own page that only the cardholder's device may pay (NMI's, Redsys's).
1599
+ async function processorPageAbort(ctx) {
1510
1600
  const { url, method } = ctx.request;
1511
1601
  ctx.preparationGate.invalidate('unsupported_checkout');
1512
1602
  ctx.lifecycle.unsupported('processor_card_page');
@@ -1514,6 +1604,30 @@ async function ekashuAbort(ctx) {
1514
1604
  await ctx.effects.abort();
1515
1605
  return { kind: 'abort' };
1516
1606
  }
1607
+ // Redsys's hosted payment page (redsys.ts). Once this build's registry serves Redsys's entry (a
1608
+ // capability the API serves only to a company Agentcard turned Redsys on for), every request to
1609
+ // Redsys's hosts pauses, and this gate judges the POSTs:
1610
+ // - the merchant's entry (/sis/realizarPago or its /utf-8 twin on sis.redsys.es) defers, and the
1611
+ // terminal arbiter pauses it for the claim flow like any card request: the cardholder's device pays it;
1612
+ // - every other POST to Redsys (the card page's own post, the older page's 3-D Secure card lookup,
1613
+ // InSite's card post, and an entry on Redsys's test platform, which Agentcard pays nothing on and
1614
+ // whose page asks for a card) aborts as an unsupported checkout, and Redsys never sees the card the
1615
+ // agent typed. The agent's browser only reaches Redsys's card page when the entry was not paused (an
1616
+ // attachment made too late, say), and then only the cardholder's device can pay.
1617
+ // A GET (the card page's scripts and images) is not this gate's and continues. With Redsys not served,
1618
+ // the entry is no card request, the gate defers, and every request continues as before. Synchronous
1619
+ // until it must abort, like every gate. Exported for the gate registry's tests.
1620
+ export const redsysPreClaim = (ctx) => {
1621
+ const { url, method } = ctx.request;
1622
+ if (String(method).toUpperCase() !== 'POST' || !isRedsysHost(url))
1623
+ return undefined;
1624
+ const entry = redsysEntryUrl(url);
1625
+ if (!entry || !ctx.vault.isCardRequest(entry, 'POST'))
1626
+ return undefined;
1627
+ if (isRedsysEntry(url, method))
1628
+ return undefined;
1629
+ return processorPageAbort(ctx);
1630
+ };
1517
1631
  // PayPal's legacy Hosted Fields (paypal-hosted-fields.ts). Its tokenization frame posts the card the
1518
1632
  // agent typed to the Orders v2 confirm-payment-source on cors.api.paypal.com (or its sandbox), which
1519
1633
  // no Vault mode takes over, so every company's adapters hand each POST there to this gate:
@@ -1559,6 +1673,60 @@ async function paypalHostedFieldsUnreadable(ctx) {
1559
1673
  await ctx.effects.abort();
1560
1674
  return { kind: 'abort' };
1561
1675
  }
1676
+ // PayPal's Card Fields and guest card form (paypal-card-forms.ts). Every company's adapters hand each
1677
+ // POST to the Card Fields confirm (www and api-m, live and sandbox) and to GraphQL on www (live and
1678
+ // sandbox) to this gate:
1679
+ // - a confirm with a card number at payment_source.card.number aborts as an unsupported checkout,
1680
+ // reason `unsupported_paypal_card_fields`, with no claim, create or prompt, unless the synced
1681
+ // registry recognizes it as a card request: then it defers, and the PayPal Card Fields lane pauses
1682
+ // it for the cardholder's approval, judging the body itself;
1683
+ // - a GraphQL operation with a card aborts the same way whatever the registry serves:
1684
+ // `unsupported_paypal_guest_checkout` for the guest form's variables.card.cardNumber,
1685
+ // `unsupported_paypal_card_fields` for a card saved with no purchase (variables.paymentSource.card);
1686
+ // - a confirm the adapter could not read, and a GraphQL post it could not read whose query names one
1687
+ // of the two card operations, abort that request alone with a `blocked` event,
1688
+ // `unreadable_paypal_request`, and do not hold the attachment;
1689
+ // - anything else (a PayPal wallet confirm, a saved card, the buttons' other GraphQL operations, a GET
1690
+ // or a preflight) defers, and the arbiter continues it untouched.
1691
+ // Synchronous until it must abort, like every gate. Exported for the gate registry's tests.
1692
+ export const paypalCardFormsPreClaim = (ctx) => {
1693
+ const { url, method } = ctx.request;
1694
+ if (String(method).toUpperCase() !== 'POST')
1695
+ return undefined;
1696
+ const confirm = isPaypalCardFieldsConfirm(url);
1697
+ if (!confirm && !isPaypalGraphql(url))
1698
+ return undefined;
1699
+ if (confirm && ctx.vault.isCardRequest(url, method))
1700
+ return undefined;
1701
+ let body;
1702
+ // A reader that throws (Playwright's postData() can) proves nothing either way.
1703
+ try {
1704
+ body = ctx.request.body;
1705
+ }
1706
+ catch {
1707
+ body = null;
1708
+ }
1709
+ if (confirm) {
1710
+ const carried = paypalCardFieldsBody(body);
1711
+ if (carried === 'no_card')
1712
+ return undefined;
1713
+ return carried === 'card' ? paypalCardFormAbort(ctx, PAYPAL_CARD_FIELDS_REASON) : paypalHostedFieldsUnreadable(ctx);
1714
+ }
1715
+ const carried = paypalGraphqlCard(body);
1716
+ if (carried === 'guest_card')
1717
+ return paypalCardFormAbort(ctx, PAYPAL_GUEST_CHECKOUT_REASON);
1718
+ if (carried === 'saved_card')
1719
+ return paypalCardFormAbort(ctx, PAYPAL_CARD_FIELDS_REASON);
1720
+ return carried === 'unreadable' && paypalGraphqlNamesCardOperation(url) ? paypalHostedFieldsUnreadable(ctx) : undefined;
1721
+ };
1722
+ async function paypalCardFormAbort(ctx, reason) {
1723
+ const { url, method } = ctx.request;
1724
+ ctx.preparationGate.invalidate('unsupported_checkout');
1725
+ ctx.lifecycle.unsupported(reason);
1726
+ ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason } });
1727
+ await ctx.effects.abort();
1728
+ return { kind: 'abort' };
1729
+ }
1562
1730
  // CyberSource Secure Acceptance's Silent Order POST (cybersource-secure-acceptance.ts). The merchant's
1563
1731
  // page posts the card the agent typed, beside the fields its server signed, to a /silent/ path on
1564
1732
  // CyberSource's Secure Acceptance hosts, which no Vault mode takes over, so every company's adapters
@@ -1784,6 +1952,168 @@ async function paddleVgsPayContinued(ctx, body) {
1784
1952
  fields: ['card_first_six', 'card_last_four', 'expiry_month', 'expiry_year', 'card_brand'] } });
1785
1953
  return { kind: 'continue' };
1786
1954
  }
1955
+ // EBANX after its card token (ebanx.ts, payment-core ebanx.js). Once the cardholder's device has sent the card to
1956
+ // /ws/token, EBANX's hosted payment page and EBANX.js send the card the page holds again: its 3-D Secure calls post
1957
+ // the card, and the hosted page's confirm posts its code beside EBANX's token. On a page this SDK is attached to that
1958
+ // card is the agent's placeholder, so every company's adapters hand each POST there to this gate:
1959
+ // - one the approved token request on this attachment covers (EbanxFollowUps.claim) goes to the cardholder's
1960
+ // device under that approval (ebanxFollowUp below), and the page receives EBANX's answer to the real card;
1961
+ // - any other 3-D Secure call, or a confirm whose payment.card names a card code or number, aborts as an
1962
+ // unsupported checkout, reason `unsupported_ebanx_card_resend`, with no claim, create or prompt, so EBANX never
1963
+ // receives the placeholder as a card. The attachment then holds: the page's next try would send the placeholder
1964
+ // again. A checkout whose token request was already approved keeps that approval's state (lifecycle.unsupported),
1965
+ // and its follow-ups stop (EbanxFollowUps.stop): a later request the approval would cover is refused too, so the
1966
+ // page cannot finish 3-D Secure and pay through the confirm after a refusal. A failed send stops them the same way.
1967
+ // A send goes on only while the checkout itself names the remembered approval (ebanxMaySend): it awaits the
1968
+ // merchant, reports the bank's challenge, or holds that approval's answer for the page (awaiting_approval while
1969
+ // the token's answer is on its way, since the adapters remember the approval before they hand the answer over, or
1970
+ // ready_to_submit while the approval waits for the page's retry). A new token request awaiting the person, a new
1971
+ // attempt after a merchant failure (idle), another approval and every final merchant result refuse it, and
1972
+ // nothing more reaches the device;
1973
+ // - a confirm whose body the adapter could not read, an empty one, or one that is not a JSON object aborts that
1974
+ // request alone with a `blocked` event, `unreadable_ebanx_request`, and does not hold the attachment;
1975
+ // - any other request (a confirm for a bank transfer, cash or a wallet, -record) defers, and the arbiter continues
1976
+ // it untouched.
1977
+ // A request the synced registry recognizes as a card request defers too, so the arbiter pauses it for that
1978
+ // recognizer; no build serves one today. A GET or a preflight is not this gate's. Synchronous until it must abort or
1979
+ // send, like every gate. Exported for the gate registry's tests.
1980
+ export const ebanxPreClaim = (ctx) => {
1981
+ const { url, method } = ctx.request;
1982
+ if (String(method).toUpperCase() !== 'POST' || !isEbanxResendUrl(url) || ctx.vault.isCardRequest(url, method))
1983
+ return undefined;
1984
+ const refusal = ebanxResendRefusalOf(url, 'POST', () => ctx.request.body);
1985
+ if (refusal === null)
1986
+ return undefined;
1987
+ if (refusal === 'unreadable')
1988
+ return ebanxUnreadable(ctx);
1989
+ let body;
1990
+ try {
1991
+ body = ctx.request.body;
1992
+ }
1993
+ catch {
1994
+ body = null;
1995
+ }
1996
+ const claim = ctx.ebanx && typeof ctx.vault.sendEbanxFollowUp === 'function' && ebanxMaySend(ctx)
1997
+ ? ctx.ebanx.claim({ url, method: 'POST', headers: ctx.request.headers ?? {}, body }) : null;
1998
+ return claim ? ebanxFollowUp(ctx, claim) : ebanxResendRefused(ctx, refusal);
1999
+ };
2000
+ /**
2001
+ * The checkout states in which a send may go on under the remembered approval, an allowlist: the checkout awaits the
2002
+ * merchant, the application reported the bank's challenge (requestUserAction), the token's answer is still on its way
2003
+ * to the page (awaiting_approval: the page may send its first 3-D Secure call the moment the answer lands), or the
2004
+ * approval waits for the page's retry (ready_to_submit). Every other state refuses, idle and the final ones among them.
2005
+ */
2006
+ const EBANX_SENDING_STATES = new Set(['awaiting_merchant', 'requires_user_action', 'awaiting_approval', 'ready_to_submit']);
2007
+ /**
2008
+ * Whether a send may go on: nothing aborted the checkout, no reconciliation of the merchant's order is running (one
2009
+ * that resolves failed while a confirm is out would let the application start another attempt), it is in one of
2010
+ * EBANX_SENDING_STATES, and its state names the approval the attachment remembered. A new token request awaiting the
2011
+ * person (awaiting_approval with no approval, or another one), a new attempt after a merchant failure, a prepared
2012
+ * checkout and another approval's state all refuse.
2013
+ */
2014
+ function ebanxMaySend(ctx) {
2015
+ if (ctx.lifecycleAborted() || ctx.terminal() || !ctx.ebanx || ctx.lifecycle.isReconciling())
2016
+ return false;
2017
+ const approval = ctx.ebanx.authorizationId;
2018
+ const state = ctx.lifecycle.getState();
2019
+ return approval !== null && EBANX_SENDING_STATES.has(state.status) && state.authorizationId === approval;
2020
+ }
2021
+ /**
2022
+ * The checkout's final merchant results: completed, failed, declined, timed out, cancelled, an unknown outcome, or an
2023
+ * unsupported request. A 3-D Secure call, which charges nothing, that fails in one of them leaves that outcome alone.
2024
+ */
2025
+ const EBANX_FINAL_STATES = new Set(['completed', 'failed', 'declined', 'timed_out', 'cancelled', 'outcome_unknown', 'unsupported']);
2026
+ /** Whether the checkout already ended (EBANX_FINAL_STATES): its state and reason stand, whatever a later 3-D Secure call does. */
2027
+ const ebanxCheckoutEnded = (ctx) => EBANX_FINAL_STATES.has(ctx.lifecycle.getState().status);
2028
+ /**
2029
+ * A confirm whose card may have reached EBANX and whose answer the page never got: only a payment the merchant
2030
+ * already confirmed (completed) stands, and every other state of this approval turns unknown, since EBANX may have
2031
+ * taken the payment. No other attempt can start while the send is out (both adapters refuse a new card request then,
2032
+ * CheckoutLifecycle.followUpsInFlight), so the state names this approval; one that names another is left alone.
2033
+ */
2034
+ function ebanxConfirmUnconfirmed(ctx, authorizationId, reason) {
2035
+ const state = ctx.lifecycle.getState();
2036
+ if (state.status !== 'completed' && state.authorizationId === authorizationId) {
2037
+ ctx.lifecycle.failed(new PaymentOutcomeUnknownError(authorizationId, reason), true);
2038
+ }
2039
+ }
2040
+ async function ebanxResendRefused(ctx, refusal) {
2041
+ const { url, method } = ctx.request;
2042
+ ctx.preparationGate.invalidate('unsupported_checkout');
2043
+ ctx.ebanx?.stop();
2044
+ ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
2045
+ ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason: EBANX_RESEND_REASON,
2046
+ step: refusal === 'three_ds_card' ? 'three_d_secure' : 'confirm' } });
2047
+ await ctx.effects.abort();
2048
+ return { kind: 'abort' };
2049
+ }
2050
+ async function ebanxUnreadable(ctx) {
2051
+ ctx.onEvent?.({ type: 'blocked', detail: 'unreadable_ebanx_request' });
2052
+ await ctx.effects.abort();
2053
+ return { kind: 'abort' };
2054
+ }
2055
+ /**
2056
+ * A send the approved token request covers: the cardholder's device sends it with the real card
2057
+ * (VaultClient.sendEbanxFollowUp), and the page receives EBANX's answer, with no second prompt. The events name the
2058
+ * approval and the step, never the request, the card or EBANX's answer. When no answer comes back:
2059
+ * - a confirm the card may have reached EBANX with ends `outcome_unknown` (ebanx_confirm_unconfirmed): EBANX may
2060
+ * have charged it, and only the merchant's order can say;
2061
+ * - anything else (a 3-D Secure call, which charges nothing, or a send that never left the device) holds the
2062
+ * attachment as the refusal above does, and the page shows EBANX's own payment error.
2063
+ * Either way the page's request fails, so it never waits on a send nobody will answer. A send runs outside the
2064
+ * card-request lifecycle, so it counts as work in flight from its claim until the page has its answer or it failed
2065
+ * (CheckoutLifecycle.followUpStarted): meanwhile reconcile() and retryAfterMerchantFailure() throw, so the application
2066
+ * starts no other attempt while a confirm may pay. A confirm that may have left keeps only a payment the merchant
2067
+ * confirmed (completed); any other state of its approval turns unknown (ebanxConfirmUnconfirmed). Any other send keeps
2068
+ * a final outcome (EBANX_FINAL_STATES) with its reason.
2069
+ */
2070
+ async function ebanxFollowUp(ctx, claim) {
2071
+ const settle = ctx.lifecycle.followUpStarted();
2072
+ try {
2073
+ return await sendEbanxFollowUp(ctx, claim);
2074
+ }
2075
+ finally {
2076
+ settle();
2077
+ }
2078
+ }
2079
+ async function sendEbanxFollowUp(ctx, claim) {
2080
+ const detail = { authorizationId: claim.authorizationId, step: claim.kind };
2081
+ ctx.onEvent?.({ type: 'ebanx_follow_up_requested', detail });
2082
+ let answer;
2083
+ try {
2084
+ answer = await ctx.vault.sendEbanxFollowUp(claim.authorizationId, claim.request, { signal: ctx.lifecycle.abort.signal, until: claim.until });
2085
+ }
2086
+ catch (error) {
2087
+ claim.settle(null);
2088
+ ctx.ebanx?.stop();
2089
+ const reason = error instanceof EbanxFollowUpError ? error.reason : 'follow_up_unavailable';
2090
+ const cardSent = error instanceof EbanxFollowUpError ? error.cardSent : true;
2091
+ ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason, cardSent } });
2092
+ if (cardSent && claim.kind === 'confirm')
2093
+ ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_confirm_unconfirmed');
2094
+ else if (!ebanxCheckoutEnded(ctx))
2095
+ ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
2096
+ await ctx.effects.abort();
2097
+ return { kind: 'abort' };
2098
+ }
2099
+ claim.settle({ status: answer.status, text: answer.body });
2100
+ try {
2101
+ await ctx.effects.answer({ status: answer.status, headers: answer.headers, body: answer.body });
2102
+ }
2103
+ catch {
2104
+ // EBANX answered the device and the page never received it: a confirm's charge is the merchant's to confirm.
2105
+ ctx.ebanx?.stop();
2106
+ ctx.onEvent?.({ type: 'ebanx_follow_up_failed', detail: { ...detail, reason: 'handoff_failed', cardSent: true } });
2107
+ if (claim.kind === 'confirm')
2108
+ ebanxConfirmUnconfirmed(ctx, claim.authorizationId, 'ebanx_follow_up_handoff_failed');
2109
+ else if (!ebanxCheckoutEnded(ctx))
2110
+ ctx.lifecycle.unsupported(EBANX_RESEND_REASON);
2111
+ await ctx.effects.abort();
2112
+ return { kind: 'abort' };
2113
+ }
2114
+ ctx.onEvent?.({ type: 'ebanx_follow_up_answered', detail: { ...detail, status: answer.status } });
2115
+ return { kind: 'answer' };
2116
+ }
1787
2117
  // The terminal arbiter, always last. A request that no gate claimed and that is
1788
2118
  // neither a native Stripe step nor a recognized card request continues, unless
1789
2119
  // it matches a declared payment endpoint we do not support (then it aborts as an
@@ -1817,8 +2147,8 @@ async function unsupportedContinue(ctx) {
1817
2147
  * arbiter runs after all of them and is not part of this list. A gate appended
1818
2148
  * after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
1819
2149
  * pauses each one itself. */
1820
- export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, paypalHostedFieldsPreClaim,
1821
- cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim];
2150
+ export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, paypalHostedFieldsPreClaim,
2151
+ paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim];
1822
2152
  /** Run each gate in order; the first to return a decision wins. If none does,
1823
2153
  * the terminal arbiter decides between continue, abort (unsupported) and pause.
1824
2154
  * Returns synchronously while every gate defers synchronously (an ordinary card
@@ -28,6 +28,19 @@ export declare class PreparationGate {
28
28
  assertDocument(): Promise<void>;
29
29
  private readDocument;
30
30
  isEngaged(): boolean;
31
+ /**
32
+ * Where a CreditGuard hosted page's own call beside its submit (its card lookup, its 3-D Secure
33
+ * calls: pre-claim.ts creditguardCompanionPreClaim) stands against this attachment's one
34
+ * preparation, which it never claims:
35
+ * 'none' no preparation was asked for, so the call goes on as it did before the lane held any;
36
+ * 'ready' a ready CreditGuard preparation, still inside its window, framed the page of this very
37
+ * transaction: the SDK answers the call itself and the placeholder goes nowhere, and the
38
+ * same preparation admits the page's encrypted submit (jwePayload) for the claim to bind;
39
+ * 'refuse' anything else (a preparation still being made, spent, failed or expired, or one for
40
+ * another processor or transaction): the call is refused, so the placeholder never
41
+ * reaches CreditGuard on a page the cardholder did not approve.
42
+ */
43
+ creditguardStanding(txId: string): 'none' | 'ready' | 'refuse';
31
44
  /** Whether the one preparation was bound to a card request (claim() took it). */
32
45
  isConsumed(): boolean;
33
46
  /**
@@ -2,6 +2,7 @@ import { CheckoutPreparationError } from './client.js';
2
2
  import { amountRefusal, validAmountInput } from './client.js';
3
3
  import { matchesPreparation, validPreparationEnvironment, preparationEndpoint, preparationBindsByShape, preparationRequestMethod, preparedRequestRefusal } from './prepared-processor.js';
4
4
  import { SECURE_ACCEPTANCE_PSP, secureAcceptanceCheckoutProfile } from './cybersource-secure-acceptance.js';
5
+ import { creditguardPageProcessUrl, creditguardProcessTxId } from './creditguard.generated.js';
5
6
  /** A local, one-use rendezvous. It never starts or retries a merchant request. */
6
7
  export class PreparationGate {
7
8
  opts;
@@ -216,6 +217,27 @@ export class PreparationGate {
216
217
  }
217
218
  }
218
219
  isEngaged() { return this.state !== 'unused'; }
220
+ /**
221
+ * Where a CreditGuard hosted page's own call beside its submit (its card lookup, its 3-D Secure
222
+ * calls: pre-claim.ts creditguardCompanionPreClaim) stands against this attachment's one
223
+ * preparation, which it never claims:
224
+ * 'none' no preparation was asked for, so the call goes on as it did before the lane held any;
225
+ * 'ready' a ready CreditGuard preparation, still inside its window, framed the page of this very
226
+ * transaction: the SDK answers the call itself and the placeholder goes nowhere, and the
227
+ * same preparation admits the page's encrypted submit (jwePayload) for the claim to bind;
228
+ * 'refuse' anything else (a preparation still being made, spent, failed or expired, or one for
229
+ * another processor or transaction): the call is refused, so the placeholder never
230
+ * reaches CreditGuard on a page the cardholder did not approve.
231
+ */
232
+ creditguardStanding(txId) {
233
+ if (this.state === 'unused')
234
+ return 'none';
235
+ const prepared = this.prepared;
236
+ if (this.state !== 'ready' || !prepared || prepared.psp !== 'creditguard' || !(Date.parse(prepared.expiresAt) > Date.now()))
237
+ return 'refuse';
238
+ const processUrl = typeof prepared.paymentPageUrl === 'string' ? creditguardPageProcessUrl(prepared.paymentPageUrl) : null;
239
+ return processUrl !== null && creditguardProcessTxId(processUrl) === txId ? 'ready' : 'refuse';
240
+ }
219
241
  /** Whether the one preparation was bound to a card request (claim() took it). */
220
242
  isConsumed() { return this.state === 'consumed'; }
221
243
  /**
@@ -20,8 +20,12 @@
20
20
  * paddle_vgs: prepared only. Paddle's checkout frame aborts its card request to its VGS card vault 10
21
21
  * seconds after Pay and sends it once more, too soon for an approval after Pay. Its environments are its
22
22
  * two card URLs: vault.paddle.com (production) and sandbox-vault.paddle.com (sandbox).
23
+ *
24
+ * wix_payments: prepared only. Wix's payment widget fails the payment 30 seconds after it asks Wix
25
+ * Cashier's card frame for a token, too soon for an approval after Pay. Its one environment is
26
+ * production: Wix's tokenizer has no test host.
23
27
  */
24
- 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';
28
+ 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';
25
29
  export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
26
30
  /**
27
31
  * How the approved card reaches the processor: the device's own request (token), or
@@ -14,6 +14,7 @@ import { amadeusEnvironment, amadeusPreparationUrls, amadeusRequestRefusal, isPr
14
14
  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
+ import { WIX_PAYMENTS_TOKENIZE_URL, isPreparedWixPaymentsRequest } from './wix-payments.generated.js';
17
18
  export function preparationMode(psp) {
18
19
  return psp === 'adyen' || psp === 'fiserv' || psp === 'verifone' ? 'cse' : 'token';
19
20
  }
@@ -53,6 +54,9 @@ export function preparedApprovalAnswersRetry(psp) {
53
54
  export function validPreparationEnvironment(psp, environment) {
54
55
  if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
55
56
  return environment === 'shared';
57
+ // Wix Payments has production alone: its tokenizer has no test host.
58
+ if (psp === 'wix_payments')
59
+ return environment === 'production';
56
60
  return ['square', 'braintree', 'worldpay', 'adyen', 'checkout_com', 'paysafe', 'fiserv', 'cybersource_flex', 'aci', 'creditguard', 'worldpay_eprotect', 'amadeus_checkout', 'verifone',
57
61
  'cybersource_secure_acceptance', 'paddle_vgs'].includes(psp) && ['production', 'sandbox'].includes(environment);
58
62
  }
@@ -99,6 +103,9 @@ export function preparationEndpoint(psp, environment) {
99
103
  // Paddle VGS: the environment's one card URL, the only URL the claim takes for it.
100
104
  if (psp === 'paddle_vgs')
101
105
  return PADDLE_VGS_CARDS_URLS[environment];
106
+ // Wix Payments: the tokenize URL, the only URL the claim takes for it.
107
+ if (psp === 'wix_payments')
108
+ return WIX_PAYMENTS_TOKENIZE_URL;
102
109
  // A Verifone endpoint is its reviewed profile's leg, never one per environment (verifone.ts).
103
110
  if (psp === 'verifone')
104
111
  throw new Error('unsupported_preparation_processor');
@@ -177,7 +184,11 @@ function freshCardBody(psp, body) {
177
184
  * preparation's, the submit is the /process URL of the page the preparation read, on that page's
178
185
  * own host and for its txId, and the request is the page's own new-card submit exactly as the
179
186
  * device will judge it, with ppDomain bound to the approved merchant page. A saved card, a wallet,
180
- * a refused flag, another transaction or another PPS host never spends consent.
187
+ * a refused flag, another transaction or another PPS host never spends consent. The encrypted
188
+ * variant (jwePayload) is the page's own submit when the transaction carries a key, so it binds
189
+ * too: the API's create reads the transaction and holds the body to it (a jwePayload only under a
190
+ * key, and only while the API admits the encrypted card), and the device seals the approved card
191
+ * under the key the API's own read hands it.
181
192
  */
182
193
  function matchesPreparedCreditguard(environment, requestUrl, body, headers, binding) {
183
194
  let hostname = '', merchantHost = '';
@@ -194,7 +205,7 @@ function matchesPreparedCreditguard(environment, requestUrl, body, headers, bind
194
205
  const txId = creditguardProcessTxId(requestUrl);
195
206
  if (page === null || txId === null || page !== requestUrl)
196
207
  return false;
197
- return creditguardProcessRefusal({ url: requestUrl, method: 'POST', headers, body: body ?? '' }, { merchantHost }) === null;
208
+ return creditguardProcessRefusal({ url: requestUrl, method: 'POST', headers, body: body ?? '' }, { merchantHost, jwe: true }) === null;
198
209
  }
199
210
  /**
200
211
  * The approved merchant page's host, with its port if it has one: what the Amadeus SDK sends in its
@@ -248,6 +259,10 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
248
259
  // the JSON:API content type and the frame's body), the rule the API and the Vault bind with too.
249
260
  if (psp === 'paddle_vgs')
250
261
  return isPreparedPaddleVgsRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers ?? {});
262
+ // Wix Payments: production, the tokenize URL and payment-core's whole request rule (a JSON content type, the
263
+ // site's app instance and the card frame's body), the rule the API and the Vault bind with too.
264
+ if (psp === 'wix_payments')
265
+ return isPreparedWixPaymentsRequest(requestUrl, method.toUpperCase(), body ?? null, environment, headers ?? {});
251
266
  if (psp === 'creditguard')
252
267
  return matchesPreparedCreditguard(environment, requestUrl, body, headers, binding);
253
268
  // A Verifone request is judged only through the profile its preparation names (matchesPreparation).