@agent-cards/sdk 0.23.22 → 0.23.23

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/cdp.js CHANGED
@@ -8,6 +8,7 @@ import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
8
8
  import { JuspayCheckoutGate } from './juspay-gate.js';
9
9
  import { ACI_CHECKOUT_REUSED, AciGate, playwrightReplacesNavigationBody } from './aci.js';
10
10
  import { EprotectGate, classifyEprotectRequest, eprotectScriptAnswer } from './eprotect.js';
11
+ import { SbpsGate, classifySbpsRequest, sbpsScriptAnswer } from './sbps.js';
11
12
  import { dispatchPreClaim, eventUrl, isArmedCreditguardCall, routesHipayVault } from './pre-claim.js';
12
13
  import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
13
14
  import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
@@ -15,11 +16,12 @@ import { isToastPaymentMethodsUrl, readToastBody } from './toast.generated.js';
15
16
  import { PADDLE_VGS_PAY_PATTERNS, PaddleVgsPayGate, isPaddleCheckoutService, recognizesPaddleVgs } from './paddle-vgs-pay.js';
16
17
  import { DatatransRepeats } from './datatrans.js';
17
18
  import { TokenexIframeRepeats, isArmedTokenexIframeUrl } from './tokenex-iframe.js';
19
+ import { isArmedPaytrailUrl } from './paytrail.js';
18
20
  import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
19
21
  import { paddleTokenexTokenContinuation } from './paddle-tokenex.js';
20
22
  import { twoC2pContinuation, twoC2pRetire } from './twoc2p.js';
21
23
  import { isArmedEkashuUrl } from './ekashu.js';
22
- import { isArmedTransbankUrl } from './transbank.js';
24
+ import { isArmedTransbankUrl, transbankMerchantReturn, transbankMerchantReturnAnswer } from './transbank.js';
23
25
  import { isArmedRedsysUrl } from './redsys.js';
24
26
  import { isArmedPrzelewy24Url } from './przelewy24.js';
25
27
  import { isArmedQuickpayUrl } from './quickpay.js';
@@ -1641,6 +1643,22 @@ function eprotectResumedReplay(replay, request) {
1641
1643
  throw new PaymentOutcomeUnknownError(replay.authorizationId, 'eprotect_registration_unconfirmed');
1642
1644
  return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1643
1645
  }
1646
+ /**
1647
+ * SB Payment Service's token request, resumed by the business, on the same terms as eProtect's above. The read
1648
+ * carries the token as the API stored it, which the paused <script> cannot run, so it gets the answer authorize()
1649
+ * gives on the create path (client.ts): the token inside the callback the paused URL named, under a script type,
1650
+ * and only a token payment-core's judge accepts for that URL. A read in any other mode never answers it either.
1651
+ * The paused URL alone decides, whatever recognizer claimed it. Any other request's replay passes as it came, and
1652
+ * a create's replay never comes here, so no answer is wrapped twice.
1653
+ */
1654
+ function sbpsResumedReplay(replay, request) {
1655
+ if (classifySbpsRequest(request.url, request.method)?.kind !== 'token_request')
1656
+ return replay;
1657
+ const answer = replay.mode === 'token' ? sbpsScriptAnswer(request.url, replay) : null;
1658
+ if (replay.mode !== 'token' || !answer)
1659
+ throw new PaymentOutcomeUnknownError(replay.authorizationId, 'sbps_token_unconfirmed');
1660
+ return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1661
+ }
1644
1662
  /** The recogniser that claimed a paused request, by processor name, so a caller never re-matches the URL; null on a duck-typed vault without the read. */
1645
1663
  function recognizerNameOf(opts, url, method) {
1646
1664
  const read = opts.vault.recognizerOf;
@@ -1888,6 +1906,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1888
1906
  const payrexxFollowUps = new PayrexxFollowUps();
1889
1907
  const aci = new AciGate(opts.vault);
1890
1908
  const eprotect = new EprotectGate(opts.vault);
1909
+ const sbps = new SbpsGate(opts.vault);
1891
1910
  // The merchant's follow-up request after a delivered Secure Acceptance reply (cybersource-secure-acceptance.ts).
1892
1911
  const secureAcceptanceFollowUpWatch = new SecureAcceptanceFollowUpWatch(opts.onEvent);
1893
1912
  lifecycle.abort.signal.addEventListener('abort', () => secureAcceptanceFollowUpWatch.end(), { once: true });
@@ -2805,7 +2824,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2805
2824
  // headers and body are read lazily, inside the gate that needs them (see PreClaimContext.request).
2806
2825
  request: { url: request.url, method: request.method, get headers() { return request.headers; }, get body() { return pausedBody(request); },
2807
2826
  navigation: resourceType === 'Document' },
2808
- vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect,
2827
+ vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect, sbps,
2809
2828
  // This adapter drives Chromium, which sends the body Fetch.continueRequest replaces on a
2810
2829
  // navigation (the ACI browser test's classic case, design gate G3).
2811
2830
  replacesNavigationBody: async () => true,
@@ -3191,7 +3210,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3191
3210
  holdRelease.settle(outcome);
3192
3211
  throw new HoldAnsweredError(outcome);
3193
3212
  }
3194
- const replay = pause ? eprotectResumedReplay(await pause.replay, request) : await authorization;
3213
+ const replay = pause ? sbpsResumedReplay(eprotectResumedReplay(await pause.replay, request), request) : await authorization;
3195
3214
  attempt.assertLive();
3196
3215
  approvedId = replay.authorizationId;
3197
3216
  if (lifecycle.isCancelled())
@@ -3241,8 +3260,14 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3241
3260
  // answered the device. The paused NAVIGATION is fulfilled with a page
3242
3261
  // that says exactly that (see hosted-form.ts for why not an abort and
3243
3262
  // why not a fake result page), and the same form is refused if the
3244
- // page posts it again.
3245
- const page = hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
3263
+ // page posts it again. A Webpay entry whose reviewed shop names its
3264
+ // return page goes to that page instead, with the token of the link
3265
+ // the approval was created from, the one the phone paid, since Webpay
3266
+ // tells the shop of a payment only through that return (transbank.ts
3267
+ // transbankMerchantReturn).
3268
+ const merchantReturn = transbankMerchantReturn(request.url, pageOrigin);
3269
+ const page = merchantReturn ? transbankMerchantReturnAnswer(merchantReturn)
3270
+ : hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
3246
3271
  // A navigation response is never CORS-checked, so the CORS wrap is
3247
3272
  // inert here; it is applied so every fulfill goes through one path.
3248
3273
  await cdp.send('Fetch.fulfillRequest', {
@@ -3254,8 +3279,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3254
3279
  attempt.assertLive();
3255
3280
  lastSubmitted = { url: deliverRequest.url, body: deliverBody, at: Date.now(), authorizationId: replay.authorizationId };
3256
3281
  // Named for what it is: a device-attested submission with no
3257
- // processor evidence, never an `authorized` event.
3258
- opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome } });
3282
+ // processor evidence, never an `authorized` event. A return names the
3283
+ // shop's host, never the URL, which carries Webpay's token.
3284
+ opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome,
3285
+ ...(merchantReturn ? { merchantReturn: { host: new URL(merchantReturn).hostname } } : {}) } });
3259
3286
  }
3260
3287
  else if (replay.mode === 'cse') {
3261
3288
  // The device encrypted the card for the processor; the request itself
@@ -3572,6 +3599,7 @@ export async function attachToPlaywright(page, opts) {
3572
3599
  const aci = new AciGate(opts.vault);
3573
3600
  let navigationBodyReplaced;
3574
3601
  const eprotect = new EprotectGate(opts.vault);
3602
+ const sbps = new SbpsGate(opts.vault);
3575
3603
  // The merchant's follow-up request after a delivered Secure Acceptance reply (cybersource-secure-acceptance.ts).
3576
3604
  const secureAcceptanceFollowUpWatch = new SecureAcceptanceFollowUpWatch(opts.onEvent);
3577
3605
  lifecycle.abort.signal.addEventListener('abort', () => secureAcceptanceFollowUpWatch.end(), { once: true });
@@ -3873,6 +3901,9 @@ export async function attachToPlaywright(page, opts) {
3873
3901
  || opts.vault.isCardRequest(url.toString(), 'GET')
3874
3902
  || guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
3875
3903
  || eprotect.matches(url.toString())
3904
+ // SB Payment Service's token request is a GET too, so its family is routed by the SBPS gate (never
3905
+ // its scripts or the 3-D Secure info request).
3906
+ || sbps.matches(url.toString())
3876
3907
  // An armed merchant profile's endpoint or a sibling of it (merchant-hosted.ts), as attachToCdp arms it.
3877
3908
  || (typeof opts.vault.merchantProfileOf === 'function' && opts.vault.merchantProfileOf(url.toString()) !== null)
3878
3909
  // An armed Verifone merchant's origin (verifone.ts), as attachToCdp arms it.
@@ -3898,6 +3929,10 @@ export async function attachToPlaywright(page, opts) {
3898
3929
  // Every request to a Webpay host once its entry is served (transbank.ts), as attachToCdp arms the hosts: the
3899
3930
  // token's submit and Webpay's card steps reach the Webpay gate and fail, and the entry pauses.
3900
3931
  || isArmedTransbankUrl(opts.vault, url.toString())
3932
+ // Every request to Paytrail's entry host and Payment Highway's card page host once Paytrail's entry is served
3933
+ // (paytrail.ts), as attachToCdp arms the recognizer's host and its abort host: the loading page's post and the
3934
+ // card page's card requests reach the Paytrail gate and fail.
3935
+ || isArmedPaytrailUrl(opts.vault, url.toString())
3901
3936
  // A CreditGuard page's own calls beside its submit (its card lookup and its 3-D Secure calls), for an
3902
3937
  // org the API served CreditGuard to, as attachToCdp arms its hosts: the CreditGuard gate answers them
3903
3938
  // under a ready preparation and lets them go on with none (pre-claim.ts isArmedCreditguardCall).
@@ -3965,7 +4000,7 @@ export async function attachToPlaywright(page, opts) {
3965
4000
  catch {
3966
4001
  return false;
3967
4002
  } } },
3968
- vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect,
4003
+ vault: opts.vault, guards, mercado: mercadoCheckout, stripe: stripeCheckout, aci, eprotect, sbps,
3969
4004
  // Asked once, and only for ACI's classic-mode /payment: whether this page's engine sends
3970
4005
  // the body route.continue replaces on a navigation (aci.ts playwrightReplacesNavigationBody).
3971
4006
  replacesNavigationBody: () => (navigationBodyReplaced ??= playwrightReplacesNavigationBody(page)),
@@ -4323,13 +4358,17 @@ export async function attachToPlaywright(page, opts) {
4323
4358
  }
4324
4359
  else if (replay.mode === 'hosted_form') {
4325
4360
  // Same as the CDP path: the paused navigation resolves to the
4326
- // synthetic page, and a re-post of this form is refused.
4327
- const synthetic = hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
4361
+ // synthetic page, or a reviewed Webpay shop's return page, and a
4362
+ // re-post of this form is refused.
4363
+ const merchantReturn = transbankMerchantReturn(request.url(), pageOrigin);
4364
+ const synthetic = merchantReturn ? transbankMerchantReturnAnswer(merchantReturn)
4365
+ : hostedFormSubmittedPage({ authorizationId: replay.authorizationId, merchant: opts.merchant, submittedAt: replay.submittedAt });
4328
4366
  // Inert on a navigation (never CORS-checked); one path for every fulfill.
4329
4367
  await deliverTo.fulfill({ status: synthetic.status, headers: withCorsHeaders(synthetic.headers, corsHeadersFor(deliverRequest.url(), deliverRequest.headers())), body: synthetic.body });
4330
4368
  assertRequestLive();
4331
4369
  lastSubmitted = { url: deliverRequest.url(), body: deliverBody, at: Date.now(), authorizationId: replay.authorizationId };
4332
- opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome } });
4370
+ opts.onEvent?.({ type: 'submitted_on_device', detail: { authorizationId: replay.authorizationId, submittedAt: replay.submittedAt, outcome: replay.outcome,
4371
+ ...(merchantReturn ? { merchantReturn: { host: new URL(merchantReturn).hostname } } : {}) } });
4333
4372
  }
4334
4373
  else if (replay.mode === 'cse') {
4335
4374
  // Same as the CDP path: the request continues from this browser
package/dist/client.d.ts CHANGED
@@ -211,6 +211,12 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
211
211
  * never lets the agent's browser post to (pre-claim.ts, redsys.ts). Served on the same terms as NMI's: only
212
212
  * to a build that lists it, for a company Agentcard turned Redsys on for.
213
213
  *
214
+ * `paytrail_hosted_payment_page`: Paytrail's card method, whose card-free signed Visa and Mastercard
215
+ * entry this build pauses for the cardholder's device, and whose loading page post, card page posts and
216
+ * card-number lookup on Payment Highway's host this build never lets the agent's browser send
217
+ * (pre-claim.ts, paytrail.ts). Served on the same terms as NMI's and Redsys's: only to a build that lists
218
+ * it, for a company Agentcard turned Paytrail on for.
219
+ *
214
220
  * `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
215
221
  * vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
216
222
  * checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
@@ -327,8 +333,8 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
327
333
  * This build pauses only the form's own multipart body at that exact URL; the cardholder's
328
334
  * device sends the card through the checkout relay, and the frame gets the token answer
329
335
  * payment-core's judge rebuilt, with the CORS answer for the frame's own origin, null. The
330
- * request names no amount, so the agent's pair, above zero, is the one the cardholder approves
331
- * (PAY_NL_PSP below). Served on the same terms: only to a build that lists it, for a company
336
+ * request names no amount, so the agent's pair, above zero, is the one the cardholder approves.
337
+ * Served on the same terms: only to a build that lists it, for a company
332
338
  * Agentcard turned Pay. on for.
333
339
  *
334
340
  * `paypal_card_fields`: PayPal Card Fields' confirm, the POST its v5 frame sends to
@@ -396,8 +402,8 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
396
402
  * encrypted under Clover's key. This build pauses only the frame's own body at that exact URL; the
397
403
  * cardholder's device builds the request again with the real card under the key payment-core pins
398
404
  * and sends it to Clover itself, and the frame gets the token answer payment-core's judge rebuilt.
399
- * The request names no amount, so the agent's pair, above zero, is the one the cardholder approves
400
- * (CLOVER_PSP below). Served on the same terms: only to a build that lists it, for a company
405
+ * The request names no amount, so the agent's pair, above zero, is the one the cardholder approves.
406
+ * Served on the same terms: only to a build that lists it, for a company
401
407
  * Agentcard turned Clover on for.
402
408
  *
403
409
  * `eximbay_hosted_payment_page`: Eximbay's payment window, whose card-free signed entry
@@ -415,6 +421,48 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
415
421
  * this build writes it over the agent's placeholder and nothing else before it
416
422
  * continues the request. Served on the same terms: only to a build that lists it, for
417
423
  * a company Agentcard turned Toast on for.
424
+ *
425
+ * `xendit_card_token`: the card token request Xendit's hosted invoice checkout
426
+ * (checkout.xendit.co/web/<invoice id>, where Xendit's payment and donation links land) sends
427
+ * when the payer presses Pay Now: Xendit.js posts the card as JSON to
428
+ * https://api.xendit.co/v2/credit_card_tokens from the page itself, under the business's
429
+ * publishable key. This build pauses only the card form's single-use token body at that exact URL
430
+ * (payment-core xendit.js); a token that keeps the card on file, Xendit.js's code-only storeCVN post
431
+ * and any other body there fail in the browser before anything is created. The cardholder's device
432
+ * sends the request itself with the real card, and the page gets the token answer payment-core's
433
+ * judge rebuilt, after which the page runs 3-D Secure and charges the token without the card. The
434
+ * token names the invoice's amount and currency, so no agent pair is needed: the API reads the
435
+ * token's and holds an agent's pair to it. Served on the same terms: only to a build that lists it,
436
+ * for a company Agentcard turned Xendit on for.
437
+ *
438
+ * `omise_card_token`: the card token request every Omise card form sends, Omise.js's pay.html frame
439
+ * or a merchant's own page calling Omise.js or Omise's Node library: a JSON POST to
440
+ * https://vault.omise.co/tokens under the merchant's public key. This build pauses only the card
441
+ * form's token body at that exact URL (payment-core omise.js); a wallet's tokenization and any other
442
+ * body there fail in the browser before anything is created. The cardholder's device sends the
443
+ * request itself with the real card, and the page gets the token answer payment-core's judge
444
+ * rebuilt. The request names no amount, so the agent's pair, above zero, is the one the cardholder
445
+ * approves, and the API pays only a merchant payment-core pins for the checkout
446
+ * page the create names. Served on the same terms: only to a build that lists it, for a company
447
+ * Agentcard turned Omise on for.
448
+ *
449
+ * `sbps_token_jsonp`: SB Payment Service's card token request, the JSONP <script>
450
+ * GET its token script sends to /token/generateToken on token.sps-system.com. This
451
+ * build pauses it only as the browser writes it, aborts any other request on that
452
+ * path, and answers the paused script with the token as the API recorded it, in the
453
+ * request's own callback. The device rebuilds the card's ciphertext and sends it
454
+ * through the checkout relay. Served on the same terms: only to a build that lists
455
+ * it, for a company Agentcard turned SB Payment Service on for.
456
+ *
457
+ * `midtrans_card_token`: the card token Midtrans' Snap popup posts as one JSON request to
458
+ * api.midtrans.com/v2/token (api.sandbox.midtrans.com in test), under the business's
459
+ * publishable client key. The cardholder's device sends it and the page gets back Midtrans'
460
+ * token, which the merchant's own server charges afterwards, card free, running 3-D Secure
461
+ * there. The token names no amount, so authorize() needs the agent's pair.
462
+ * Served on the same terms, and the API holds the lane to Agentcard's own company
463
+ * until a reviewed change binds the client key to the merchant.
464
+ *
465
+ * The list itself is capabilities.json, sorted, one per line (scripts/generate-capabilities.mjs).
418
466
  */
419
467
  export declare const SUPPORTED_CAPABILITIES: readonly string[];
420
468
  /**
@@ -599,7 +647,10 @@ export interface VerifoneReplay extends ExecutionMetadata {
599
647
  * paused the navigation, so there is no response to replay and no outcome
600
648
  * here: resolve the paused navigation with hostedFormSubmittedPage() (the
601
649
  * adapters do), never resubmit the form, and confirm the order with the
602
- * merchant, which learns the outcome from the processor.
650
+ * merchant, which learns the outcome from the processor. Webpay's merchant
651
+ * learns it only from the buyer's browser, so the adapters send a paused
652
+ * Webpay entry whose reviewed shop names its return page to that page instead
653
+ * (transbankMerchantReturn).
603
654
  *
604
655
  * THIS IS NOT AN APPROVED PAYMENT. `kind` and `outcome` say so in words so
605
656
  * no caller mistakes it for one: the API finishes such an authorization as
@@ -1363,6 +1414,9 @@ export declare class CheckoutAuthError extends Error {
1363
1414
  * https://payment.quickpay.net/{session}/3ds_auth.
1364
1415
  * On FastSpring's store hosts the session id reads `{session}`:
1365
1416
  * https://daisydisk.onfastspring.com/session/{session}/payment.
1417
+ * On PayU Latam's hosts the checkout session's key reads `{key}`:
1418
+ * https://checkout.payulatam.com/ppp-web-gateway-payu/checkout/submit/co/credit_card/{key}.
1419
+ * Each mask in src/url-masks/ says what it reads in its own file.
1366
1420
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1367
1421
  * same segments masked for every processor whose host it names.
1368
1422
  * A URL is read by its host only when the parser finds one. Text before a URL