@agent-cards/sdk 0.23.6 → 0.23.7

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/dist/client.js CHANGED
@@ -31,6 +31,7 @@ import { PELECARD_CAPABILITY, PELECARD_PSP } from './pelecard.js';
31
31
  import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
32
32
  import { PAYRAILS_CAPABILITY, payrailsSaveAnswer } from './payrails.js';
33
33
  import { WSPAY_CAPABILITY, WSPAY_PSP } from './wspay.js';
34
+ import { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_FAST_POLL_MS, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REPLY_WINDOW_MS, secureAcceptanceCheckoutProfile, secureAcceptanceCheckoutRefusal, secureAcceptanceFrameAnswer, } from './cybersource-secure-acceptance.js';
34
35
  /**
35
36
  * The modes this SDK can finish. Asked for on syncRegistry (the API serves
36
37
  * only recognizers in these modes, so a request this build cannot complete
@@ -158,8 +159,15 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
158
159
  * `ekashu_hosted_payment_page`: NMI's hosted payment page, whose card-free entry this
159
160
  * build pauses for the cardholder's device and whose card page this build never lets
160
161
  * the agent's browser post to (pre-claim.ts, ekashu.ts); served on the same terms.
162
+ * `cybersource_sa_token_create`: CyberSource Secure Acceptance's token create
163
+ * (POST /silent/embedded/token/create), which this build pauses only as the
164
+ * merchant's hidden frame sends it, binds to a prepared checkout, and answers with
165
+ * CyberSource's own reply page once neither the API nor the cardholder's device
166
+ * finds a card in it (cybersource-secure-acceptance.ts). Served on the same terms,
167
+ * so every other company keeps the stop every build applies to a card posted to
168
+ * Secure Acceptance.
161
169
  */
162
- export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page'];
170
+ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page', SECURE_ACCEPTANCE_CAPABILITY];
163
171
  /**
164
172
  * Processors whose paused request names no amount at all, so the pair the
165
173
  * agent passes is the amount the cardholder approves: authorize() refuses one
@@ -173,11 +181,16 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
173
181
  * pair is also the figure the API compares with WSPay's answer when that
174
182
  * answer names one. A WSPay 3-D Secure step answers with no amount, so on that
175
183
  * path nothing Agentcard sees confirms what WSPay charges once the challenge
176
- * completes.
184
+ * completes. A CyberSource Secure Acceptance token create signs an amount of 0:
185
+ * it creates a token and charges nothing, and the merchant's server charges its
186
+ * own cart total later, so the agent's pair, which the prepared checkout already
187
+ * carries, is the total the cardholder approves, and the card that pays can spend
188
+ * no more than that (the API's card rule).
177
189
  */
178
- const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP];
190
+ const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP];
179
191
  /** How AmountRequiredError names a processor. */
180
- const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay' };
192
+ const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
193
+ [SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance' };
181
194
  /**
182
195
  * Processors whose request and answer name no amount at all, so the total the
183
196
  * payment page shows (`pageAmount`) is the one figure that ties the agent's pair
@@ -305,6 +318,22 @@ function creditguardPreparationRefusal(error) {
305
318
  const reason = error.details.reason;
306
319
  return new CheckoutPreparationError(null, error.code, typeof reason === 'string' && /^[a-z0-9_]{1,64}$/.test(reason) ? reason : undefined);
307
320
  }
321
+ /**
322
+ * The API's refusal of a Secure Acceptance preparation (SECURE_ACCEPTANCE_PREPARATION_REFUSALS, and the
323
+ * relay's 503), answered before any row or prompt, as its own reason with the API's `details.reason` as
324
+ * the detail: `unsupported_checkout` (`secure_acceptance_merchant_pending`) tells an agent the merchant is
325
+ * not on yet, which `preparation_unconfirmed` would hide. Null for any other failure.
326
+ */
327
+ function secureAcceptancePreparationRefusal(error) {
328
+ if (!(error instanceof CheckoutApiError) || error.code === null)
329
+ return null;
330
+ const named = (error.status >= 400 && error.status < 500 && SECURE_ACCEPTANCE_PREPARATION_REFUSALS.includes(error.code))
331
+ || (error.status === 503 && error.code === 'cybersource_sa_relay_unavailable');
332
+ if (!named)
333
+ return null;
334
+ const reason = error.details.reason;
335
+ return new CheckoutPreparationError(null, error.code, typeof reason === 'string' && /^[a-z0-9_]{1,64}$/.test(reason) ? reason : undefined);
336
+ }
308
337
  export class CardEncryptedError extends Error {
309
338
  psp;
310
339
  constructor(psp) {
@@ -932,6 +961,34 @@ const FISERV_PREPARATION_REFUSALS = [
932
961
  * holds that pin). Only for an eProtect checkout.
933
962
  */
934
963
  const EPROTECT_PREPARATION_REFUSALS = ['merchant_profile_mismatch'];
964
+ /**
965
+ * The API's refusals of a CyberSource Secure Acceptance preparation, before any preparation exists,
966
+ * that prepareCheckout surfaces by name, with the API's own `details.reason` as the error's `detail`
967
+ * where it gives one: the company is not one Agentcard turned the lane on for
968
+ * (`processor_unavailable`), the environment is not the client's test or live mode, the merchant is
969
+ * not one Agentcard turned on, or is still in review (`unsupported_checkout`, with
970
+ * `secure_acceptance_merchant_pending`, `secure_acceptance_merchant_observe` and the like), a live
971
+ * checkout's merchant is not the name the reviewed entry carries (`merchant_profile_mismatch`), and,
972
+ * as a 503, the relay that carries the cardholder's device to CyberSource is not on yet
973
+ * (`cybersource_sa_relay_unavailable`). Only for a Secure Acceptance checkout. The card follows the rule
974
+ * of every prepared checkout, a card the cardholder stored, so its refusal stays
975
+ * `preparation_unconfirmed`, as for Worldpay eProtect.
976
+ */
977
+ const SECURE_ACCEPTANCE_PREPARATION_REFUSALS = [
978
+ 'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'unsupported_checkout', 'merchant_profile_mismatch',
979
+ ];
980
+ /**
981
+ * The reason the cardholder's device declines a bound prepared row with, per processor, when its own
982
+ * send boundary passed before the card left it (the Vault's sendWindow, 12 seconds after it last saw
983
+ * the checkout unbound): a prepared Amadeus SDK add and a prepared Secure Acceptance token create. It
984
+ * names the same known non-payment the adapters' own send window closes on (amadeus.ts), which can
985
+ * close later, so authorize() ends the attempt the way that window does, as a CheckoutPreparationError
986
+ * with that reason, and never as the cardholder's "no".
987
+ */
988
+ const PREPARED_SEND_WINDOW_DECLINES = Object.freeze({
989
+ amadeus_checkout: 'amadeus_send_window_closed',
990
+ [SECURE_ACCEPTANCE_PSP]: 'secure_acceptance_send_window_closed',
991
+ });
935
992
  /**
936
993
  * Refusals a CyberSource Flex preparation that tokenizes before Pay answers (4xx) before any
937
994
  * preparation exists, surfaced as the CheckoutPreparationError reason: the processor is off for
@@ -1131,15 +1188,17 @@ export class VaultClient {
1131
1188
  // creditguard_pps_process, primer_payment_instruments,
1132
1189
  // payrails_device_encrypt, converge_hpp_process, worldpay_eprotect_jsonp,
1133
1190
  // wspay_confirm, shiji_sts_pinned_key, amadeus_checkout_actions.v1,
1134
- // worldline_mycheckout_form, verifone_card_capture) and
1191
+ // worldline_mycheckout_form, verifone_card_capture, ekashu_hosted_payment_page,
1192
+ // cybersource_sa_token_create) and
1135
1193
  // the caller's company is one Agentcard turned that processor on for, so this
1136
1194
  // build arms Fiserv's card capture, GMO's getToken, CyberSource Flex's card
1137
1195
  // fields, Juspay's card flow, Datatrans's Secure Fields card post, the
1138
1196
  // Authorize.Net DPM or SIM form, Pelecard's card submission, ACI's split card,
1139
1197
  // CreditGuard's hosted page submit, Primer's card tokenization, Payrails' card
1140
1198
  // save, Converge's payment page, Worldpay eProtect's registration, WSPay's
1141
- // card confirm, Shiji STS's card leg, Amadeus Checkout's card add and
1142
- // Worldline MyCheckout's card form only for
1199
+ // card confirm, Shiji STS's card leg, Amadeus Checkout's card add,
1200
+ // Worldline MyCheckout's card form and CyberSource Secure Acceptance's token
1201
+ // create only for
1143
1202
  // such a company, and an older build never receives a recognizer it cannot
1144
1203
  // judge. Every other org's registry never recognizes a Flex, Juspay, Datatrans,
1145
1204
  // Pelecard, ACI, CreditGuard, Primer, Payrails, Converge, eProtect, WSPay, Shiji STS, Amadeus or
@@ -1148,8 +1207,10 @@ export class VaultClient {
1148
1207
  // SIM, Pelecard, ACI, CreditGuard, Primer, Payrails, Converge, eProtect, WSPay,
1149
1208
  // Shiji STS, Amadeus and MyCheckout requests go through untouched; the create checks the org again (403
1150
1209
  // processor_unavailable), and the SDK refuses ACI's card-number request,
1151
- // CreditGuard's submit, an eProtect registration and an Amadeus card add locally
1152
- // without a prepared checkout.
1210
+ // CreditGuard's submit, an eProtect registration, an Amadeus card add and a
1211
+ // Secure Acceptance token create locally without a prepared checkout. A Secure
1212
+ // Acceptance card post that no served recognizer covers is stopped for every
1213
+ // company (pre-claim.ts).
1153
1214
  const capabilities = SUPPORTED_CAPABILITIES.length ? `&capabilities=${SUPPORTED_CAPABILITIES.join(',')}` : '';
1154
1215
  // merchant_profiles=1 asks for the reviewed Adyen merchant profiles too, each
1155
1216
  // as a `{ merchant_profile }` entry after the recognizers; an API that
@@ -1403,6 +1464,16 @@ export class VaultClient {
1403
1464
  throw fail('paypage_id_invalid');
1404
1465
  if (eprotect && !input.cardId)
1405
1466
  throw fail('card_required');
1467
+ // CyberSource Secure Acceptance: the preparation names the CyberSource merchant profile its token create
1468
+ // may name (secureAcceptanceProfileId, which on the production host this build can fill from the entry
1469
+ // it reviewed for the page's origin) and the paying card, whose billing name and address the agent types
1470
+ // into the merchant's form, where the merchant's server signs them. The API refuses either missing, and a
1471
+ // profile id on any other processor; refusing here asks nobody to approve a preparation that could never bind.
1472
+ const secureAcceptance = input.psp === SECURE_ACCEPTANCE_PSP;
1473
+ if (!secureAcceptance && input.secureAcceptanceProfileId !== undefined)
1474
+ throw fail('secure_acceptance_profile_id_invalid');
1475
+ if (secureAcceptance && !input.cardId)
1476
+ throw fail('card_required');
1406
1477
  // CyberSource Flex, tokenized before Pay: the loaded card field's capture context and fields,
1407
1478
  // verified here against the keys payment-core pins before anything is created (the API and the
1408
1479
  // cardholder's device verify them again).
@@ -1448,6 +1519,17 @@ export class VaultClient {
1448
1519
  merchantOrigin: input.merchantOrigin, merchant: input.merchant }) : null;
1449
1520
  if (eprotectRefusal)
1450
1521
  throw fail(eprotectRefusal);
1522
+ // A Secure Acceptance checkout names a profile id: the one passed, of CyberSource's shape, or on the
1523
+ // production host the one the entry this build reviewed for the page's origin pins. Under a reviewed
1524
+ // entry it names that entry's merchant, the one the API shows on the approval, so the approval, the
1525
+ // handle this returns and every authorization it pays name one merchant. A profile id this build has not
1526
+ // reviewed is the API's to judge (unsupported_checkout); the test host needs no entry.
1527
+ const secureAcceptanceRefusal = secureAcceptance ? secureAcceptanceCheckoutRefusal({ environment: input.environment,
1528
+ profileId: input.secureAcceptanceProfileId, merchantOrigin: input.merchantOrigin, merchant: input.merchant }) : null;
1529
+ if (secureAcceptanceRefusal)
1530
+ throw fail(secureAcceptanceRefusal);
1531
+ const secureAcceptanceProfileId = secureAcceptance ? secureAcceptanceCheckoutProfile({ environment: input.environment,
1532
+ profileId: input.secureAcceptanceProfileId, merchantOrigin: input.merchantOrigin }) : null;
1451
1533
  const timeoutMs = input.timeoutMs ?? 15 * 60_000;
1452
1534
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
1453
1535
  throw fail('timeout_invalid');
@@ -1469,6 +1551,7 @@ export class VaultClient {
1469
1551
  ...(declaration ? { sandbox_declaration: { endpoint: declaration.endpoint, client_key: declaration.clientKey, environment: 'test' } } : {}),
1470
1552
  ...(input.psp === 'creditguard' ? { payment_page_url: input.paymentPageUrl } : {}),
1471
1553
  ...(eprotect ? { paypage_id: input.paypageId } : {}),
1554
+ ...(secureAcceptance ? { secure_acceptance_profile_id: secureAcceptanceProfileId } : {}),
1472
1555
  ...(flexContext ? { capture_context: flexContext.captureContext,
1473
1556
  microform: { version: flexContext.microform.version, fields: [...flexContext.microform.fields] } } : {}),
1474
1557
  }, AbortSignal.timeout(30_000), signal);
@@ -1511,6 +1594,8 @@ export class VaultClient {
1511
1594
  || state.checkout_key !== input.checkoutKey
1512
1595
  // eProtect: the merchant and the card the cardholder approved are the ones asked for.
1513
1596
  || (eprotect && (state.paypage_id !== input.paypageId || state.card_id !== input.cardId))
1597
+ // Secure Acceptance: the merchant profile and the card the cardholder approved are the ones asked for.
1598
+ || (secureAcceptance && (state.secure_acceptance_profile_id !== secureAcceptanceProfileId || state.card_id !== input.cardId))
1514
1599
  // The API says a preparation tokenizes before Pay exactly when this one carried a capture context.
1515
1600
  || (state.tokenize_before_pay === true) !== !!flexContext)
1516
1601
  throw fail('ready_unconfirmed', id);
@@ -1526,6 +1611,7 @@ export class VaultClient {
1526
1611
  ...(declaration ? { sandboxDeclaration: declaration } : {}),
1527
1612
  ...(input.psp === 'creditguard' ? { paymentPageUrl: input.paymentPageUrl } : {}),
1528
1613
  ...(eprotect ? { paypageId: input.paypageId } : {}),
1614
+ ...(secureAcceptance ? { secureAcceptanceProfileId: secureAcceptanceProfileId } : {}),
1529
1615
  ...(flexContext ? { cybersourceFlex: Object.freeze({ captureContext: flexContext.captureContext,
1530
1616
  microform: Object.freeze({ version: flexContext.microform.version, fields: Object.freeze([...flexContext.microform.fields]) }) }) } : {}),
1531
1617
  });
@@ -1542,7 +1628,8 @@ export class VaultClient {
1542
1628
  catch (error) {
1543
1629
  if (error instanceof CheckoutPreparationError)
1544
1630
  throw error;
1545
- const refusal = input.psp === 'creditguard' && id === null ? creditguardPreparationRefusal(error) : null;
1631
+ const refusal = input.psp === 'creditguard' && id === null ? creditguardPreparationRefusal(error)
1632
+ : secureAcceptance && id === null && !signal.aborted ? secureAcceptancePreparationRefusal(error) : null;
1546
1633
  if (refusal)
1547
1634
  throw refusal;
1548
1635
  // A refusal the API answered before any preparation existed names its rule: one
@@ -1621,6 +1708,8 @@ export class VaultClient {
1621
1708
  * response to replay into the browser. Your process never sees a card.
1622
1709
  */
1623
1710
  async authorize(input) {
1711
+ // When the paused request reached this call: a Secure Acceptance reply window runs from here.
1712
+ const calledAt = Date.now();
1624
1713
  // A reviewed merchant's own card endpoint is checked whole before anything else
1625
1714
  // (admitCardEndpoint): the profile, the approval, the amount and the session.
1626
1715
  const cardEndpoint = input.request.merchant_card_endpoint === undefined ? null : admitCardEndpoint(input);
@@ -1803,6 +1892,12 @@ export class VaultClient {
1803
1892
  if (cardMethod === 'GET' && !(eprotect && isEprotectRegistration(input.request))) {
1804
1893
  throw new Error(`this ${rec?.psp} GET is not a card registration this version of @agent-cards/checkout can complete: ${redactUrl(input.request.url)}.`);
1805
1894
  }
1895
+ // A CyberSource Secure Acceptance token create (always prepared: its recognizer requires it). The
1896
+ // merchant's page asks its hidden frame for the reply for 30 seconds from Save, so a reply not read
1897
+ // SECURE_ACCEPTANCE_REPLY_WINDOW_MS after the pause will never be read: the outcome is then unknown,
1898
+ // and the authorization is retired when the API confirms the card never moved.
1899
+ const secureAcceptance = rec?.psp === SECURE_ACCEPTANCE_PSP;
1900
+ const secureAcceptanceReplyBy = secureAcceptance ? calledAt + SECURE_ACCEPTANCE_REPLY_WINDOW_MS : Infinity;
1806
1901
  // The amount is a hint: a pair or nothing. Refuse half a pair here, before
1807
1902
  // a network call: the API would too, and a retry loop cannot fix a
1808
1903
  // missing field. The processor's own amount is read by Agentcard.
@@ -1924,6 +2019,17 @@ export class VaultClient {
1924
2019
  }
1925
2020
  throw new PaymentOutcomeUnknownError(id, 'preparation_already_bound');
1926
2021
  }
2022
+ // A Secure Acceptance create refused because the relay that carries the cardholder's device to
2023
+ // CyberSource is off (503 cybersource_sa_relay_unavailable): the API answers it before any rule, row
2024
+ // or bind (assertSecureAcceptanceRelayReady at the create, and again inside the bind's transaction),
2025
+ // so nothing was created and the card cannot leave. Named, as prepare() names it, never a create
2026
+ // that may have committed. The preparation, which no request can bind while the relay is off, is
2027
+ // retired so the cardholder's page stops waiting for it.
2028
+ if (secureAcceptance && error instanceof CheckoutApiError && error.status === 503 && error.code === 'cybersource_sa_relay_unavailable') {
2029
+ if (preparation)
2030
+ await this.cancelPreparation(preparation.id).catch(() => { });
2031
+ throw new CheckoutPreparationError(preparation?.id ?? null, error.code);
2032
+ }
1927
2033
  // A missing answer or generic 5xx can hide a committed row and a delivered approval link.
1928
2034
  // Only the documented pre-create read-back errors prove it is safe to retry.
1929
2035
  const safeReadFailure = error instanceof CheckoutApiError
@@ -1945,8 +2051,12 @@ export class VaultClient {
1945
2051
  // A prepared Worldpay eProtect registration too, for 15 seconds: its
1946
2052
  // merchants give up on the paused script 10 to 15 seconds after Pay, so a
1947
2053
  // registration is read every 500 ms instead of waiting out 2 seconds.
2054
+ // A prepared CyberSource Secure Acceptance token create too, for 30 seconds:
2055
+ // the merchant's page asks its frame for the reply for 30 seconds from Save,
2056
+ // and the API names 500 ms as such a row's poll interval.
1948
2057
  const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
1949
- : preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000 : 0;
2058
+ : preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
2059
+ : preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
1950
2060
  // A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
1951
2061
  // token and approves as soon as this bind reaches it, while the page may give createToken only
1952
2062
  // seconds (Alaska Airlines' flight checkout gives it three). Poll every 100 ms for 10 seconds,
@@ -2003,16 +2113,24 @@ export class VaultClient {
2003
2113
  throw new PaymentOutcomeUnknownError(authorizationId, input.merchantSignal?.aborted ? 'merchant_request_aborted' : 'local_cancel');
2004
2114
  const pollInterval = Date.now() < preparedFastPollUntil ? Math.min(this.pollIntervalMs, 500)
2005
2115
  : Date.now() < preparedFlexPollUntil ? Math.min(this.pollIntervalMs, 100) : this.pollIntervalMs;
2006
- await interruptibleSleep(Math.min(pollInterval, Math.max(0, deadline - Date.now())), stopSignal);
2116
+ // A Secure Acceptance read also wakes when its reply window closes (Infinity for every other processor).
2117
+ await interruptibleSleep(Math.min(pollInterval, Math.max(0, deadline - Date.now()), Math.max(0, secureAcceptanceReplyBy - Date.now())), stopSignal);
2007
2118
  if (stopSignal?.aborted)
2008
2119
  throw new PaymentOutcomeUnknownError(authorizationId, input.merchantSignal?.aborted ? 'merchant_request_aborted' : 'local_cancel');
2009
2120
  let s;
2010
2121
  const deadlineSignal = AbortSignal.timeout(Math.max(1, deadline - Date.now()));
2011
- const pollSignal = stopSignal ? AbortSignal.any([stopSignal, deadlineSignal]) : deadlineSignal;
2122
+ // A Secure Acceptance read is also bounded by its reply window, whatever time the caller has left: once
2123
+ // the window closes the merchant's page has stopped asking for the reply, so a read still unanswered then
2124
+ // is late, never a failed poll that could still hand the frame a page.
2125
+ const replySignal = secureAcceptance ? AbortSignal.timeout(Math.max(1, secureAcceptanceReplyBy - Date.now())) : undefined;
2126
+ const pollSignal = stopSignal || replySignal
2127
+ ? AbortSignal.any([...(stopSignal ? [stopSignal] : []), deadlineSignal, ...(replySignal ? [replySignal] : [])]) : deadlineSignal;
2012
2128
  try {
2013
2129
  s = await this.get(`/v2/checkout/authorizations/${authorizationId}`, pollSignal);
2014
2130
  }
2015
2131
  catch {
2132
+ if (replySignal?.aborted && !stopSignal?.aborted)
2133
+ throw new PaymentOutcomeUnknownError(authorizationId, 'secure_acceptance_reply_late');
2016
2134
  throw new PaymentOutcomeUnknownError(authorizationId, input.merchantSignal?.aborted ? 'merchant_request_aborted' : 'authorization_poll_failed');
2017
2135
  }
2018
2136
  if (input.merchantSignal?.aborted)
@@ -2020,6 +2138,12 @@ export class VaultClient {
2020
2138
  if (!s || typeof s !== 'object' || Array.isArray(s) || typeof s.status !== 'string') {
2021
2139
  throw new PaymentOutcomeUnknownError(authorizationId, 'authorization_status_malformed');
2022
2140
  }
2141
+ // An approval read once the reply window has closed reaches nobody: the merchant's page has stopped asking,
2142
+ // so CyberSource's page is never handed to the frame. The card has reached CyberSource, so the outcome is
2143
+ // unknown, as for a reply that never came.
2144
+ if (secureAcceptance && s.status === 'approved' && (Date.now() >= secureAcceptanceReplyBy || replySignal?.aborted)) {
2145
+ throw new PaymentOutcomeUnknownError(authorizationId, 'secure_acceptance_reply_late');
2146
+ }
2023
2147
  execution = executionMetadata(s, authorizationId, execution);
2024
2148
  checkAutopilotAction(s);
2025
2149
  if (nativeCheckout && (s.status === 'submitted_on_device' ||
@@ -2202,6 +2326,23 @@ export class VaultClient {
2202
2326
  response.headers = answer.headers;
2203
2327
  response.body = answer.body;
2204
2328
  }
2329
+ if (secureAcceptance) {
2330
+ // CyberSource Secure Acceptance: the paused frame navigation is answered with CyberSource's own
2331
+ // reply page, byte for byte, with status 200 and its one content type, and only a page
2332
+ // payment-core's reply rule accepts for the paused request (secureAcceptanceFrameAnswer): one
2333
+ // hidden-input form posting to the receipt page the request signed, the request's own echoes, a
2334
+ // masked number and a token. The device and the API already checked it for the card. Anything
2335
+ // else is not an answer to hand the frame, and the card has already reached CyberSource. No
2336
+ // autopilot run sends one (payment-core, the authority and the API refuse it).
2337
+ if (execution.executionMode === 'autopilot')
2338
+ throw new PaymentOutcomeUnknownError(authorizationId, 'autopilot_submission_mode_invalid');
2339
+ const answer = secureAcceptanceFrameAnswer(input.request, response);
2340
+ if (!answer)
2341
+ throw new PaymentOutcomeUnknownError(authorizationId, 'secure_acceptance_reply_unconfirmed');
2342
+ response.status = answer.status;
2343
+ response.headers = answer.headers;
2344
+ response.body = answer.body;
2345
+ }
2205
2346
  if (shopOrder) {
2206
2347
  let receipt;
2207
2348
  try {
@@ -2312,6 +2453,12 @@ export class VaultClient {
2312
2453
  if (s.reason === 'processor_refused') {
2313
2454
  throw new ProcessorRefusedError(String(created.id), typeof s.psp_error_code === 'string' ? s.psp_error_code : null, s.psp === 'razorpay' ? parseProcessorError(s.processor_error) : null);
2314
2455
  }
2456
+ // The cardholder's device passed its own send boundary before the card left it and declined the
2457
+ // row with that window's reason: the checkout failed unsent, as when the adapters' own window
2458
+ // closes first (PREPARED_SEND_WINDOW_DECLINES), not a decline the cardholder made.
2459
+ if (preparation && typeof s.reason === 'string' && PREPARED_SEND_WINDOW_DECLINES[preparation.psp] === s.reason) {
2460
+ throw new CheckoutPreparationError(preparation.id, s.reason);
2461
+ }
2315
2462
  throw new ApprovalDeclinedError(s.reason ?? 'no reason given');
2316
2463
  }
2317
2464
  if (s.status === 'cancelled') {
@@ -2326,6 +2473,11 @@ export class VaultClient {
2326
2473
  }
2327
2474
  if (s.status !== 'awaiting_approval')
2328
2475
  throw new PaymentOutcomeUnknownError(authorizationId, 'authorization_status_unrecognized');
2476
+ // A Secure Acceptance row neither approved nor declined when its reply window closes: the merchant's
2477
+ // page stopped asking for the reply, and the device may have sent the card. Unknown, never a decline;
2478
+ // the cleanup below retires the row only when the API confirms the card never moved.
2479
+ if (Date.now() >= secureAcceptanceReplyBy)
2480
+ throw new PaymentOutcomeUnknownError(authorizationId, 'secure_acceptance_reply_late');
2329
2481
  }
2330
2482
  // A local deadline is not server-side expiry; the person may still use the approval link.
2331
2483
  throw new PaymentOutcomeUnknownError(authorizationId, 'local_approval_timeout');
@@ -54,7 +54,7 @@ declare function parseCreditguardTransaction(body: any, txId: any): {
54
54
  personalIdAlwaysEnabled: boolean;
55
55
  inputs: any;
56
56
  } | null;
57
- declare function creditguardTransactionRefusal(txn: any, expectation: any): "otp" | "keep_on_file" | "dcc" | "installments" | "personal_id" | "jwe_unsupported" | "amount_mismatch" | "environment_mismatch" | "currency_mismatch" | "transaction_unreadable" | "three_d_secure" | "transaction_type" | "form_inputs" | "card_field_hidden" | "no_retries_left" | "jwe_public_key_malformed" | null;
57
+ declare function creditguardTransactionRefusal(txn: any, expectation: any): "otp" | "keep_on_file" | "dcc" | "installments" | "personal_id" | "jwe_unsupported" | "transaction_type" | "amount_mismatch" | "environment_mismatch" | "currency_mismatch" | "transaction_unreadable" | "three_d_secure" | "form_inputs" | "card_field_hidden" | "no_retries_left" | "jwe_public_key_malformed" | null;
58
58
  declare function creditguardSendWindowRefusal(timing: any): "timing_unknown" | "page_expired" | "recaptcha_expired" | null;
59
59
  declare var CREDITGUARD_REFUSAL_CODES: readonly string[];
60
60
  declare function creditguardResponseOutcome(status: any, body: any, options: any): {
@@ -1,37 +1,3 @@
1
- /**
2
- * CyberSource Secure Acceptance's Silent Order POST, which this SDK refuses for every company.
3
- *
4
- * CyberSource's Secure Acceptance Checkout API, formerly Silent Order POST, takes the card from a form
5
- * on the merchant's own page. The merchant's server signs the order fields, and the shopper's browser
6
- * posts them, with the card fields left unsigned, to a /silent/ path on one of CyberSource's Secure
7
- * Acceptance hosts: /silent/pay, /silent/token/create, /silent/token/update, and the iframe form of
8
- * each under /silent/embedded/ (CyberSource's endpoint table, read 2026-10-08). Arrow Electronics'
9
- * checkout (its public bundles, read 2026-10-08; no card was sent) builds that form once its server
10
- * has signed the fields and submits it into a hidden iframe:
11
- *
12
- * <iframe name="secureAcceptanceiFrame" style="display:none">
13
- * <form action="https://secureacceptance.cybersource.com/silent/embedded/token/create" method="post"
14
- * target="secureAcceptanceiFrame">: access_key, profile_id, transaction_type=create_payment_token,
15
- * amount=0 and the other signed fields with their signature, then card_type, card_number,
16
- * card_expiry_date (MM-YYYY) and card_cvn
17
- *
18
- * No Vault mode takes this post over, so no company can pay it, and an SDK without this guard let the
19
- * card the agent typed reach CyberSource. Both adapters arm /silent/ on the three hosts whatever the
20
- * synced registry serves, over https and http, each host also spelled with the trailing dot of a fully
21
- * qualified name, and the pre-claim gate (cybersourceSecureAcceptancePreClaim, pre-claim.ts) judges
22
- * each request there, whether a fetch, an XHR or a form navigation of the page or of a frame sent it,
23
- * by its body and by its URL's query (cybersourceSecureAcceptanceRequest):
24
- * - a card_number that holds a value: stop it, so CyberSource never receives the card. A form
25
- * navigation is answered with the stopped page (card-endpoint-page.ts), which names CyberSource,
26
- * since an aborted page load renders nothing and invites another click on Pay; any other request
27
- * fails. The checkout reads `unsupported` with reason `unsupported_cybersource_secure_acceptance`
28
- * and holds, as every unsupported refusal does;
29
- * - no card_number, or an empty one, as in a payment with a stored payment_token: continue untouched;
30
- * - a body the adapter could not read: stop that request alone, since nothing proves it carries no card.
31
- * A request the synced registry recognizes as a card request belongs to that recognizer instead; no
32
- * build serves one today. Hosted Checkout, the same hosts without /silent/, takes the card on
33
- * CyberSource's own page, and neither adapter arms it.
34
- */
35
1
  export declare const CYBERSOURCE_SECURE_ACCEPTANCE_REASON: "unsupported_cybersource_secure_acceptance";
36
2
  /** Where the stopped page says the card was going. */
37
3
  export declare const CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT = "CyberSource";
@@ -76,3 +42,145 @@ export declare function cybersourceSecureAcceptanceRequest(url: string, method:
76
42
  * body that also parses as JSON, say, cannot carry the card past the gate.
77
43
  */
78
44
  export declare function cybersourceSecureAcceptanceBody(body: string | null, contentType?: string): 'card' | 'no_card' | 'unreadable';
45
+ /** The processor id, as the registry and the API name it. */
46
+ export declare const SECURE_ACCEPTANCE_PSP: "cybersource_secure_acceptance";
47
+ /** The dark-launch capability the API serves the Secure Acceptance recognizer behind (payment-core definitions.js). */
48
+ export declare const SECURE_ACCEPTANCE_CAPABILITY: "cybersource_sa_token_create";
49
+ /** How long after the pause a paused token request may still be sent; past it, nothing being handed over, it is retired. */
50
+ export declare const SECURE_ACCEPTANCE_SEND_WINDOW_MS = 20000;
51
+ /** How long after the pause CyberSource's reply may still be read; past it the outcome is unknown. */
52
+ export declare const SECURE_ACCEPTANCE_REPLY_WINDOW_MS = 45000;
53
+ /** How long after the create the authorization is read every 500 ms instead of at the caller's interval. */
54
+ export declare const SECURE_ACCEPTANCE_FAST_POLL_MS = 30000;
55
+ /** How long after the page is delivered the merchant's follow-up request is watched for. */
56
+ export declare const SECURE_ACCEPTANCE_FOLLOW_UP_MS = 10000;
57
+ /** The one content type the paused frame's answer carries, as CyberSource serves its page. */
58
+ export declare const SECURE_ACCEPTANCE_ANSWER_CONTENT_TYPE: string;
59
+ /** A reviewed production merchant, as payment-core's SECURE_ACCEPTANCE_MERCHANTS lists it; the fields this SDK reads. */
60
+ export interface SecureAcceptanceMerchant {
61
+ id: string;
62
+ merchant: string;
63
+ environment: string;
64
+ status: string;
65
+ origins: readonly string[];
66
+ profileIds: readonly string[];
67
+ accessKeys?: readonly string[];
68
+ pending?: readonly string[];
69
+ /** The one request the merchant's page sends once the receipt answered it, watched for after delivery. */
70
+ followUp?: {
71
+ method: string;
72
+ url: string;
73
+ } | null;
74
+ }
75
+ /** Why prepare() refuses a Secure Acceptance checkout before any network call. */
76
+ export type SecureAcceptanceCheckoutRefusal = 'secure_acceptance_profile_id_invalid' | 'merchant_profile_mismatch';
77
+ /** Why a paused Secure Acceptance request ends with an unknown outcome, beside the reasons every processor shares. */
78
+ export type SecureAcceptanceUnknownReason = 'secure_acceptance_reply_unconfirmed' | 'secure_acceptance_reply_late' | 'response_expired';
79
+ /**
80
+ * The profile id a Secure Acceptance checkout prepares, or null when it can name none. A profile id the
81
+ * caller passes must have CyberSource's UUID shape, and is the one the bound request must carry. On the
82
+ * production host the caller may leave it out when exactly one entry this build reviewed lists the
83
+ * checkout page's origin, under exactly one profile id: that one is used, so the agent names nothing it
84
+ * cannot see. The test host has no entries (it takes only CyberSource's test cards), so a sandbox
85
+ * checkout always names its own. `entries` is for tests.
86
+ */
87
+ export declare function secureAcceptanceCheckoutProfile(input: {
88
+ environment: string;
89
+ profileId?: unknown;
90
+ merchantOrigin: string;
91
+ }, entries?: readonly SecureAcceptanceMerchant[]): string | null;
92
+ /**
93
+ * Why a Secure Acceptance checkout may not be prepared under `merchant`, or null when it may:
94
+ * `secure_acceptance_profile_id_invalid` when it names no profile id this build can stand behind
95
+ * (secureAcceptanceCheckoutProfile), and, on the production host, `merchant_profile_mismatch` when the
96
+ * entry this build reviewed for that profile id and origin names another merchant, the one the approval
97
+ * shows. A profile id this build has not reviewed is the API's to judge (unsupported_checkout), as an
98
+ * eProtect paypageId is. `entries` is for tests.
99
+ */
100
+ export declare function secureAcceptanceCheckoutRefusal(input: {
101
+ environment: string;
102
+ profileId?: unknown;
103
+ merchantOrigin: string;
104
+ merchant: string;
105
+ }, entries?: readonly SecureAcceptanceMerchant[]): SecureAcceptanceCheckoutRefusal | null;
106
+ /**
107
+ * Whether a paused request is the one shape the lane pays: a POST to the exact token-create URL on one of
108
+ * the two hosts (payment-core isSecureAcceptanceTokenRequest), sent as a navigation of a frame, as a
109
+ * merchant's form targeted at its hidden iframe sends it. A top navigation, a fetch and an XHR are not.
110
+ */
111
+ export declare function isSecureAcceptanceFramePost(request: {
112
+ url: string;
113
+ method: string;
114
+ navigation: 'top' | 'frame' | null | undefined;
115
+ }): boolean;
116
+ /**
117
+ * The answer a paused token request gets, or null when the approved reply is not one to hand the frame:
118
+ * CyberSource's own page, byte for byte as the API stored it, with status 200 and the one content type
119
+ * CyberSource serves it with, and no other header. The page must pass payment-core's reply rule for the
120
+ * paused request (secureAcceptanceReplyOutcome: one hidden-input form posting to the receipt page the
121
+ * request signed, the request's own echoes, a masked number, a token, no card), run here with no card and
122
+ * no last four: the device and the API checked those.
123
+ */
124
+ export declare function secureAcceptanceFrameAnswer(request: {
125
+ url: string;
126
+ body?: string | null;
127
+ }, response: {
128
+ status: number;
129
+ headers?: Record<string, string>;
130
+ body: string;
131
+ }): {
132
+ status: 200;
133
+ headers: Record<string, string>;
134
+ body: string;
135
+ } | null;
136
+ /**
137
+ * The request a reviewed merchant's page sends once its receipt page answered (Arrow: the payment-method
138
+ * set), for a production preparation whose profile id and origin an entry lists with one; null otherwise,
139
+ * the test host included. `entries` is for tests.
140
+ */
141
+ export declare function secureAcceptanceFollowUp(preparation: {
142
+ environment: string;
143
+ secureAcceptanceProfileId?: string | null;
144
+ merchantOrigin: string;
145
+ }, entries?: readonly SecureAcceptanceMerchant[]): {
146
+ method: string;
147
+ url: string;
148
+ } | null;
149
+ /**
150
+ * The last four digits of the masked number CyberSource's reply page echoes (req_card_number, masked as
151
+ * x's and the last four), or null when the page carries no single such field. The page passed the reply
152
+ * rule before it was delivered, so this only reads it back for the follow-up event; it never carries more
153
+ * than those four digits.
154
+ */
155
+ export declare function secureAcceptanceReplyLast4(page: string): string | null;
156
+ /**
157
+ * One attachment's watch for the merchant's follow-up request after a delivered reply. Read-only: it
158
+ * pauses, changes and delays nothing. The adapters report every request the page sends (CDP
159
+ * Network.requestWillBeSent, Playwright page.on('request')) through saw(); when none matches the
160
+ * merchant's follow-up within SECURE_ACCEPTANCE_FOLLOW_UP_MS, the event `secure_acceptance_reply_unused`
161
+ * names the authorization and the masked number's last four, so the agent can find the card the merchant
162
+ * may have kept (in its saved cards, ending in those four) before it ever asks for a new checkout.
163
+ */
164
+ export declare class SecureAcceptanceFollowUpWatch {
165
+ private readonly onEvent?;
166
+ private readonly ms;
167
+ private watching;
168
+ constructor(onEvent?: ((event: {
169
+ type: string;
170
+ detail?: unknown;
171
+ }) => void) | undefined, ms?: number);
172
+ /** Start watching for `followUp` after the reply for `authorizationId` was delivered; a watch already running ends unreported. */
173
+ begin(followUp: {
174
+ method: string;
175
+ url: string;
176
+ }, detail: {
177
+ authorizationId: string;
178
+ last4: string | null;
179
+ }): void;
180
+ /** A request the page sent: the follow-up, when it is the one being watched for, ends the watch. */
181
+ saw(method: unknown, url: unknown): void;
182
+ /** True while a watch runs. */
183
+ get active(): boolean;
184
+ /** End the watch with no event. */
185
+ end(): void;
186
+ }
@@ -0,0 +1,66 @@
1
+ declare var SECURE_ACCEPTANCE_PSP: string;
2
+ declare var SECURE_ACCEPTANCE_CAPABILITY: string;
3
+ declare var SECURE_ACCEPTANCE_HOSTS: Readonly<{
4
+ production: "secureacceptance.cybersource.com";
5
+ sandbox: "testsecureacceptance.cybersource.com";
6
+ }>;
7
+ declare var SECURE_ACCEPTANCE_TOKEN_CREATE_PATH: string;
8
+ declare var SECURE_ACCEPTANCE_SIGNED_FIELDS: readonly string[];
9
+ declare var SECURE_ACCEPTANCE_OPTIONAL_SIGNED_FIELDS: readonly string[];
10
+ declare var SECURE_ACCEPTANCE_UNSIGNED_FIELDS: readonly string[];
11
+ declare var SECURE_ACCEPTANCE_CARD_TYPES: Readonly<{
12
+ visa: "001";
13
+ mastercard: "002";
14
+ amex: "003";
15
+ discover: "004";
16
+ }>;
17
+ declare var SECURE_ACCEPTANCE_TEST_PANS: Readonly<Set<unknown>>;
18
+ declare var SECURE_ACCEPTANCE_REQUEST_REFUSALS: readonly string[];
19
+ declare var SECURE_ACCEPTANCE_REFUSAL_REASON_CODES: readonly string[];
20
+ declare var SECURE_ACCEPTANCE_REPLY_REASONS: readonly string[];
21
+ declare var SECURE_ACCEPTANCE_PAGE_CONTENT_TYPE: string;
22
+ declare var SECURE_ACCEPTANCE_MERCHANTS: any;
23
+ declare function secureAcceptanceEnvironment(hostname: any): "production" | "sandbox" | null;
24
+ declare function secureAcceptancePreparationUrl(environment: any): string;
25
+ declare function isSecureAcceptanceTokenRequest(url: any, method: any): boolean;
26
+ declare function secureAcceptanceRequestRefusal(request: any, options: any): "card_fields" | "profile_id" | "access_key" | "currency" | "signed_date_time" | "locale" | "origin_header" | "not_token_create" | "content_type" | "referer_header" | "body_unreadable" | "signed_fields_unreviewed" | "unsigned_fields_unreviewed" | "fields_unreviewed" | "transaction_type" | "amount_not_zero" | "payment_method" | "receipt_page" | "keeps_card" | "edits_saved_card" | "card_type" | "bill_to" | "signature" | "request_ids" | "device_fingerprint" | null;
27
+ declare function secureAcceptanceRequestFacts(request: any): {
28
+ environment: string | null;
29
+ profileId: any;
30
+ accessKey: any;
31
+ transactionUuid: any;
32
+ referenceNumber: any;
33
+ currency: any;
34
+ signedDateTime: any;
35
+ } | null;
36
+ declare function isPreparedSecureAcceptanceRequest(url: any, method: any, body: any, headers: any, environment: any, binding: any): boolean;
37
+ declare function secureAcceptanceMerchantEntry(profileId: any, merchantOrigin: any, entries?: any): any;
38
+ declare function secureAcceptanceMerchantRefusal(environment: any, profileId: any, merchantOrigin: any, accessKey: any, entries?: any): "unsupported_environment" | "invalid_profile_id" | "secure_acceptance_merchant_unreviewed" | "secure_acceptance_merchant_pending" | "secure_acceptance_access_key_unreviewed" | "secure_acceptance_merchant_observe" | null;
39
+ declare function secureAcceptanceCardType(number: any): "001" | "002" | "003" | "004" | null;
40
+ declare function secureAcceptanceCardRefusal(environment: any, card: any): "unsupported_environment" | "sandbox_host_requires_documented_test_card" | "invalid_card_number" | "live_host_refuses_documented_test_card" | "card_network_unsupported" | "invalid_expiry" | "invalid_cvn" | null;
41
+ declare function isSecureAcceptanceDeviceRequest(templateBody: any, deviceBody: any, environment: any): boolean;
42
+ declare function secureAcceptanceDeviceRequest(request: any, card: any, options: any): {
43
+ url: any;
44
+ method: string;
45
+ headers: {
46
+ "content-type": string;
47
+ origin: string;
48
+ };
49
+ body: string;
50
+ where: string[];
51
+ };
52
+ declare function secureAcceptanceScannedPage(bodyText: any, template: any): any;
53
+ declare function secureAcceptanceReplyOutcome(reply: any, template: any, card: any, options: any): {
54
+ outcome: string;
55
+ reason: any;
56
+ } | {
57
+ outcome: string;
58
+ page: any;
59
+ code?: undefined;
60
+ } | {
61
+ outcome: string;
62
+ code: any;
63
+ page?: undefined;
64
+ };
65
+ declare function secureAcceptanceRefusalCode(bodyText: any): any;
66
+ export { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_CARD_TYPES, SECURE_ACCEPTANCE_HOSTS, SECURE_ACCEPTANCE_MERCHANTS, SECURE_ACCEPTANCE_OPTIONAL_SIGNED_FIELDS, SECURE_ACCEPTANCE_PAGE_CONTENT_TYPE, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REFUSAL_REASON_CODES, SECURE_ACCEPTANCE_REPLY_REASONS, SECURE_ACCEPTANCE_REQUEST_REFUSALS, SECURE_ACCEPTANCE_SIGNED_FIELDS, SECURE_ACCEPTANCE_TEST_PANS, SECURE_ACCEPTANCE_TOKEN_CREATE_PATH, SECURE_ACCEPTANCE_UNSIGNED_FIELDS, isPreparedSecureAcceptanceRequest, isSecureAcceptanceDeviceRequest, isSecureAcceptanceTokenRequest, secureAcceptanceCardRefusal, secureAcceptanceCardType, secureAcceptanceDeviceRequest, secureAcceptanceEnvironment, secureAcceptanceMerchantEntry, secureAcceptanceMerchantRefusal, secureAcceptancePreparationUrl, secureAcceptanceRefusalCode, secureAcceptanceReplyOutcome, secureAcceptanceRequestFacts, secureAcceptanceRequestRefusal, secureAcceptanceScannedPage };