@agent-cards/sdk 0.23.23 → 0.23.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +8 -1
- package/dist/agent-amount.generated.js +1 -1
- package/dist/amadeus.d.ts +5 -2
- package/dist/amadeus.js +18 -3
- package/dist/amazon-payment-services.d.ts +25 -0
- package/dist/amazon-payment-services.generated.d.ts +76 -0
- package/dist/amazon-payment-services.generated.js +883 -0
- package/dist/amazon-payment-services.js +53 -0
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +3 -2
- package/dist/cdp.js +66 -4
- package/dist/client.d.ts +48 -3
- package/dist/client.js +112 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/openpay.generated.d.ts +67 -0
- package/dist/openpay.generated.js +446 -0
- package/dist/paddle-checkout-watch.d.ts +70 -0
- package/dist/paddle-checkout-watch.js +239 -0
- package/dist/paddle-tokenex.generated.d.ts +29 -1
- package/dist/paddle-tokenex.generated.js +175 -26
- package/dist/prepared-processor.d.ts +12 -4
- package/dist/prepared-processor.js +31 -5
- package/dist/primer.generated.js +3 -2
- package/dist/stripe-checkout.generated.js +3 -2
- package/dist/tap-payments.d.ts +43 -0
- package/dist/tap-payments.generated.d.ts +59 -0
- package/dist/tap-payments.generated.js +990 -0
- package/dist/tap-payments.js +72 -0
- package/package.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Amazon Payment Services (formerly PayFort; psp `amazon_payment_services`), its custom merchant page: the merchant's own
|
|
2
|
+
// card form posts top-level, as a document navigation, to /FortAPI/paymentPage on checkout.payfort.com (its sandbox,
|
|
3
|
+
// sbcheckout.payfort.com), form encoded, with service_command=TOKENIZATION and the merchant's signed fields beside the
|
|
4
|
+
// card. APS answers the post with a page whose script posts APS's fields to the form's return_url, or with a redirect
|
|
5
|
+
// to that return_url carrying them in its query, and the merchant charges the token in those fields later, from its own
|
|
6
|
+
// server. The rules are payment-core's (amazon-payment-services.generated.ts): the API applies them at create and at the
|
|
7
|
+
// approve, and the cardholder's phone again before it sends the form through the checkout relay.
|
|
8
|
+
//
|
|
9
|
+
// The processor ships dark. The API serves the amazon_payment_services recognizer only to a build that declares the
|
|
10
|
+
// `aps_custom_merchant_page` capability (client.ts SUPPORTED_CAPABILITIES), and only to an org Agentcard enabled for it,
|
|
11
|
+
// so an SDK the API never served it leaves every APS form alone. This module holds the last check before an approval's
|
|
12
|
+
// answer reaches the paused navigation.
|
|
13
|
+
import { APS_CAPABILITY as CORE_CAPABILITY, APS_PAGE_CONTENT_TYPE, APS_PSP as CORE_PSP, apsAnswerOutcome } from './amazon-payment-services.generated.js';
|
|
14
|
+
export const APS_PSP = CORE_PSP;
|
|
15
|
+
export const APS_CAPABILITY = CORE_CAPABILITY;
|
|
16
|
+
/**
|
|
17
|
+
* The answer the paused APS form navigation is fulfilled with, or null. The API stores APS's answer only once
|
|
18
|
+
* payment-core's judge reads it as a token for the paused form (apsAnswerOutcome), and this reads the stored answer
|
|
19
|
+
* again, against the paused request, before the agent's browser gets it:
|
|
20
|
+
* page status 200 under APS's HTML type alone, APS's page byte for byte, whose one form posts APS's fields to
|
|
21
|
+
* the paused form's own return_url;
|
|
22
|
+
* redirect status 302 or 303 with one Location alone: the paused form's return_url, then APS's fields in the query.
|
|
23
|
+
* Anything else is null: a refusal, an unknown answer, another merchant's or another return page's answer, a card
|
|
24
|
+
* number anywhere in it, or a header beside the one each shape carries. A navigation reads no CORS header, so none is
|
|
25
|
+
* added. Autopilot never sends an APS form (payment-core autopilot.js), so the caller refuses an unattended answer.
|
|
26
|
+
*/
|
|
27
|
+
export function apsNavigationAnswer(request, response) {
|
|
28
|
+
try {
|
|
29
|
+
const headers = {};
|
|
30
|
+
for (const [name, value] of Object.entries(response.headers ?? {})) {
|
|
31
|
+
const lower = name.toLowerCase();
|
|
32
|
+
if (typeof value !== 'string' || Object.prototype.hasOwnProperty.call(headers, lower))
|
|
33
|
+
return null;
|
|
34
|
+
headers[lower] = value;
|
|
35
|
+
}
|
|
36
|
+
const names = Object.keys(headers);
|
|
37
|
+
// No card is in hand here: the judge reads the answer for any card number, with no card of its own to search for.
|
|
38
|
+
const verdict = apsAnswerOutcome({ status: response.status, headers, bodyText: response.body }, { url: request.url, body: request.body ?? '' }, undefined, undefined);
|
|
39
|
+
if (verdict.outcome !== 'token' || typeof verdict.body !== 'string')
|
|
40
|
+
return null;
|
|
41
|
+
if (verdict.shape === 'page') {
|
|
42
|
+
if (names.length !== 1 || headers['content-type'] !== APS_PAGE_CONTENT_TYPE)
|
|
43
|
+
return null;
|
|
44
|
+
return { status: 200, headers: { 'content-type': APS_PAGE_CONTENT_TYPE }, body: verdict.body };
|
|
45
|
+
}
|
|
46
|
+
if (names.length !== 1 || typeof verdict.location !== 'string' || response.body !== '')
|
|
47
|
+
return null;
|
|
48
|
+
return { status: response.status, headers: { location: verdict.location }, body: '' };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Written by scripts/generate-capabilities.mjs from capabilities.json. Do not edit; git ignores it.
|
|
2
2
|
/** capabilities.json's list, sorted; client.ts exports it as SUPPORTED_CAPABILITIES. */
|
|
3
|
-
export const DECLARED_CAPABILITIES = ["aci_split_card.v1", "amadeus_checkout_actions.v1", "authorize_net_dpm_form", "capture_context", "clover_card_token", "comgate_hosted_payment_page", "converge_hpp_process", "creditguard_pps_process", "cybersource_sa_token_create", "datatrans_secure_fields_tokenize", "ekashu_hosted_payment_page", "eximbay_hosted_payment_page", "fiserv_card_capture", "gmo_shared_pk", "gopay_card_form", "gravy_session_fields_put", "hipay_vault_token", "juspay_txns.v1", "lyra_krypton_payment", "makecommerce_card_frame", "midtrans_card_token", "moneris_hosted_tokenization", "omise_card_token", "paddle_tokenex_pay", "paddle_vgs_cards", "pay_nl_card_token", "payfast_card_pay", "paygent_token", "paymongo_checkout_pay", "paypal_card_fields", "payrails_device_encrypt", "payrexx_card_frame", "paytrail_hosted_payment_page", "pelecard_transaction", "poynt_collect_nonce", "primer_payment_instruments", "przelewy24_hosted_payment_page", "quickpay_hosted_payment_page", "redsys_hosted_payment_page", "revolut_card_field", "sbps_token_jsonp", "shiji_sts_pinned_key", "sumup_checkout_process", "toast_pinned_key", "tokenex_iframe_tokenize", "transbank_webpay_hosted_payment_page", "twoc2p_secure_pay", "verifone_card_capture", "wix_payments_tokenize", "worldline_mycheckout_form", "worldpay_eprotect_jsonp", "wspay_confirm", "xendit_card_token"];
|
|
3
|
+
export const DECLARED_CAPABILITIES = ["aci_split_card.v1", "amadeus_checkout_actions.v1", "aps_custom_merchant_page", "authorize_net_dpm_form", "capture_context", "clover_card_token", "comgate_hosted_payment_page", "converge_hpp_process", "creditguard_pps_process", "cybersource_sa_token_create", "datatrans_secure_fields_tokenize", "ekashu_hosted_payment_page", "eximbay_hosted_payment_page", "fastspring_card_payment", "fiserv_card_capture", "gmo_shared_pk", "gopay_card_form", "gravy_session_fields_put", "hipay_vault_token", "juspay_txns.v1", "lyra_krypton_payment", "makecommerce_card_frame", "midtrans_card_token", "moneris_hosted_tokenization", "omise_card_token", "openpay_tokenizer", "paddle_tokenex_pay", "paddle_vgs_cards", "pay_nl_card_token", "payfast_card_pay", "paygent_token", "paymongo_checkout_pay", "paypal_card_fields", "payrails_device_encrypt", "payrexx_card_frame", "paytrail_hosted_payment_page", "pelecard_transaction", "poynt_collect_nonce", "primer_payment_instruments", "przelewy24_hosted_payment_page", "quickpay_hosted_payment_page", "redsys_hosted_payment_page", "revolut_card_field", "sbps_token_jsonp", "shiji_sts_pinned_key", "sumup_checkout_process", "tap_card_sdk_token", "toast_pinned_key", "tokenex_iframe_tokenize", "transbank_webpay_hosted_payment_page", "twoc2p_secure_pay", "verifone_card_capture", "wix_payments_tokenize", "worldline_mycheckout_form", "worldpay_eprotect_jsonp", "wspay_confirm", "xendit_card_token"];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 30277fd34048f75e9f6b6252d783092c471aca03a64462bb4f458f9417b8cc82
|
|
4
4
|
// src/card-number-key.js
|
|
5
5
|
var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
|
|
6
6
|
// src/json-path.js
|
|
@@ -4615,7 +4615,8 @@ function toastBodyVerdict(body) {
|
|
|
4615
4615
|
// src/fastspring.js
|
|
4616
4616
|
var FASTSPRING_PSP = "fastspring";
|
|
4617
4617
|
var FASTSPRING_STORE_PINS = /* @__PURE__ */ Object.freeze([
|
|
4618
|
-
Object.freeze({ environment: "production", store: "daisydisk", storefront: "", merchant: "DaisyDisk" })
|
|
4618
|
+
Object.freeze({ environment: "production", store: "daisydisk", storefront: "", merchant: "DaisyDisk" }),
|
|
4619
|
+
Object.freeze({ environment: "production", store: "stardock", storefront: "/popup-store", merchant: "Stardock" })
|
|
4619
4620
|
]);
|
|
4620
4621
|
var URL_LIMIT11 = 256;
|
|
4621
4622
|
var BODY_LIMIT28 = 32768;
|
package/dist/cdp.js
CHANGED
|
@@ -20,6 +20,7 @@ import { isArmedPaytrailUrl } from './paytrail.js';
|
|
|
20
20
|
import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
|
|
21
21
|
import { paddleTokenexTokenContinuation } from './paddle-tokenex.js';
|
|
22
22
|
import { twoC2pContinuation, twoC2pRetire } from './twoc2p.js';
|
|
23
|
+
import { tapPaymentsContinuation } from './tap-payments.js';
|
|
23
24
|
import { isArmedEkashuUrl } from './ekashu.js';
|
|
24
25
|
import { isArmedTransbankUrl, transbankMerchantReturn, transbankMerchantReturnAnswer } from './transbank.js';
|
|
25
26
|
import { isArmedRedsysUrl } from './redsys.js';
|
|
@@ -39,6 +40,7 @@ import { replayTemplatedUrl, reviewedMerchantProfileUrlPatterns } from './mercha
|
|
|
39
40
|
import { verifoneTemplatedUrl } from './verifone.js';
|
|
40
41
|
import { MerchantHandoffWatch } from './merchant-handoff.js';
|
|
41
42
|
import { MERCHANT_PAGE_WAIT_MS, MerchantTotalWatch } from './merchant-total-watch.js';
|
|
43
|
+
import { PaddleCheckoutWatch } from './paddle-checkout-watch.js';
|
|
42
44
|
import { hostedFormSubmittedPage } from './hosted-form.js';
|
|
43
45
|
import { armSendWindow, failsAsNetworkError, preparedSendWindowMs, sendWindowProcessor } from './amadeus.js';
|
|
44
46
|
import { HoldAnsweredError, HoldEndedError, HoldGate, HoldRefusedError, matchHoldOnlyRule } from './hold.js';
|
|
@@ -365,7 +367,7 @@ function cseAuthorizedDetail(replay, url, shijiCvv = null) {
|
|
|
365
367
|
* processor takes cseContinuationBody (cse-body.ts cseBody), and `shijiCvv` and
|
|
366
368
|
* `shijiCvvKeySequenceSha256` are null.
|
|
367
369
|
*/
|
|
368
|
-
function cseContinuation(opts, url, body, replay, approved, preparation) {
|
|
370
|
+
function cseContinuation(opts, url, body, replay, approved, preparation, headers) {
|
|
369
371
|
const shiji = shijiStsTokenContinuation(url, body, replay.substitutions, approved, replay.kind === 'merchant_hosted' || replay.kind === 'verifone' ? undefined : replay.shijiStsCompanion);
|
|
370
372
|
if (shiji)
|
|
371
373
|
return { body: shiji.body, shijiCvv: shiji.cvv, shijiCvvKeySequenceSha256: shiji.cvvKeySequenceSha256 };
|
|
@@ -384,6 +386,13 @@ function cseContinuation(opts, url, body, replay, approved, preparation) {
|
|
|
384
386
|
: null;
|
|
385
387
|
if (twoc2p)
|
|
386
388
|
return { body: twoc2p.body, shijiCvv: null, shijiCvvKeySequenceSha256: null };
|
|
389
|
+
// Tap writes one RSA ciphertext at the body root under the merchant's pinned key, which the generic Adyen swap refuses
|
|
390
|
+
// as no JWE, so it is dispatched by the token URL to substituteTapPayments (tap-payments.ts), only into the one request
|
|
391
|
+
// the approval was made for, and only while the request that continues names the merchant's publishable key the
|
|
392
|
+
// approved one named: the browser sends that request's own headers.
|
|
393
|
+
const tap = replay.kind === undefined ? tapPaymentsContinuation(url, body, headers, replay.substitutions, approved) : null;
|
|
394
|
+
if (tap)
|
|
395
|
+
return { body: tap.body, shijiCvv: null, shijiCvvKeySequenceSha256: null };
|
|
387
396
|
return { body: cseContinuationBody(opts, body, replay, url, preparation), shijiCvv: null, shijiCvvKeySequenceSha256: null };
|
|
388
397
|
}
|
|
389
398
|
/**
|
|
@@ -1436,7 +1445,7 @@ function merchantAttempt(lifecycle, survives) {
|
|
|
1436
1445
|
// known non-payment, so the prepared checkout ends failed with that reason, and a fresh attachment can
|
|
1437
1446
|
// prepare again. Every other stop leaves the outcome unknown.
|
|
1438
1447
|
const error = () => reason === 'amadeus_send_window_closed' || reason === 'secure_acceptance_send_window_closed'
|
|
1439
|
-
|| reason === 'wix_payments_send_window_closed' || reason === 'hipay_send_window_closed'
|
|
1448
|
+
|| reason === 'wix_payments_send_window_closed' || reason === 'hipay_send_window_closed' || reason === 'openpay_send_window_closed'
|
|
1440
1449
|
? new CheckoutPreparationError(lifecycle.getState().preparationId ?? null, reason)
|
|
1441
1450
|
: new PaymentOutcomeUnknownError(lifecycle.getState().authorizationId, reason);
|
|
1442
1451
|
const attempt = {
|
|
@@ -2016,6 +2025,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2016
2025
|
// The merchant's own total (merchant-total-watch.ts): its checkout responses, recorded
|
|
2017
2026
|
// read-only from attach on, and the approval a hand-off answers, for the charge report.
|
|
2018
2027
|
const merchantTotal = new MerchantTotalWatch(opts.onEvent, opts.vault);
|
|
2028
|
+
// Paddle's own checkout answers (paddle-checkout-watch.ts), recorded read-only from attach on for a TokenEx /pay.
|
|
2029
|
+
const paddleCheckout = new PaddleCheckoutWatch();
|
|
2019
2030
|
// A reviewed card endpoint merchant's own record of the order (card-endpoint-facts.ts).
|
|
2020
2031
|
const cardEndpointRecord = new CardEndpointFactsWatch();
|
|
2021
2032
|
let handoffReplay = null;
|
|
@@ -2525,6 +2536,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2525
2536
|
merchantTotal.sent(key, { method: params.request.method, url: params.request.url,
|
|
2526
2537
|
...(typeof params.request.postData === 'string' ? { requestBody: params.request.postData } : {}),
|
|
2527
2538
|
...(typeof params.loaderId === 'string' && params.loaderId ? { page: `${owner}:${params.loaderId}` } : {}) });
|
|
2539
|
+
paddleCheckout.sent(key, { method: params.request.method, url: params.request.url });
|
|
2528
2540
|
cardEndpointRecord.sent(key, { method: params.request.method, url: params.request.url, resourceType: params.type ?? null, networkId: params.requestId });
|
|
2529
2541
|
}
|
|
2530
2542
|
if (matchesActive) {
|
|
@@ -2564,6 +2576,14 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2564
2576
|
cdp.send('Network.getResponseBody', { requestId: params.requestId }, sessionId).then((read) => merchantTotal.log.read(key, typeof read?.body === 'string'
|
|
2565
2577
|
? (read.base64Encoded ? Buffer.from(read.body, 'base64').toString('utf8') : read.body) : null, completedAt), () => merchantTotal.log.read(key, null, completedAt));
|
|
2566
2578
|
}
|
|
2579
|
+
if (paddleCheckout.tracks(key)) {
|
|
2580
|
+
// One of Paddle's checkout answers: stamped finished at once, so a /pay's pause waits for its text, then read
|
|
2581
|
+
// and cut to the terms by the watch.
|
|
2582
|
+
const completedAt = Date.now();
|
|
2583
|
+
paddleCheckout.finished(key, completedAt);
|
|
2584
|
+
cdp.send('Network.getResponseBody', { requestId: params.requestId }, sessionId).then((read) => paddleCheckout.read(key, typeof read?.body === 'string'
|
|
2585
|
+
? (read.base64Encoded ? Buffer.from(read.body, 'base64').toString('utf8') : read.body) : null, completedAt), () => paddleCheckout.read(key, null, completedAt));
|
|
2586
|
+
}
|
|
2567
2587
|
if (payrails && payrailsAnswers.delete(requestKey(sessionId, params.requestId))
|
|
2568
2588
|
&& !(Number(params.encodedDataLength) > PAYRAILS_INIT_BODY_LIMIT)) {
|
|
2569
2589
|
payrails.reading(cdp.send('Network.getResponseBody', { requestId: params.requestId }, sessionId).then((answer) => {
|
|
@@ -2578,6 +2598,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2578
2598
|
}
|
|
2579
2599
|
if (method === 'Network.responseReceived') {
|
|
2580
2600
|
merchantTotal.log.answered(requestKey(sessionId, params.requestId), params.response?.status);
|
|
2601
|
+
paddleCheckout.answered(requestKey(sessionId, params.requestId), params.response?.status);
|
|
2581
2602
|
const recordKey = cardEndpointRecord.keyFor(requestKey(sessionId, params.requestId), params.requestId);
|
|
2582
2603
|
if (recordKey)
|
|
2583
2604
|
cardEndpointRecord.answered(recordKey, params.response?.status);
|
|
@@ -2606,6 +2627,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2606
2627
|
|| (activeRequest && activeRequest.networkId === params.requestId && activeRequest.sourceSessionId === sessionId)))
|
|
2607
2628
|
holdGate.lost();
|
|
2608
2629
|
merchantTotal.log.failed(requestKey(sessionId, params.requestId));
|
|
2630
|
+
paddleCheckout.failed(requestKey(sessionId, params.requestId));
|
|
2609
2631
|
const recordKey = cardEndpointRecord.keyFor(requestKey(sessionId, params.requestId), params.requestId);
|
|
2610
2632
|
if (recordKey)
|
|
2611
2633
|
cardEndpointRecord.failed(recordKey);
|
|
@@ -3124,6 +3146,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
3124
3146
|
// The merchant's own record of the order, as of the pause, for a profile that reads one.
|
|
3125
3147
|
const cardRecord = cardEndpoint ? await cardEndpointFacts(cardEndpointRecord, cardEndpoint, request.url, pausedAt, attempt.signal) : null;
|
|
3126
3148
|
attempt.assertLive();
|
|
3149
|
+
// Paddle's own checkout answers as of the pause, once the texts of those that finished before it are read
|
|
3150
|
+
// (a Billing /pay only; sent only with a Paddle TokenEx create, which a pause-only business makes itself).
|
|
3151
|
+
const paddleAnswers = opts.pauseOnly ? undefined : await paddleCheckout.capture(pausedAt, request.url, attempt.signal);
|
|
3152
|
+
attempt.assertLive();
|
|
3127
3153
|
// Hold mode: nothing is created until the person's total releases the request (hold.ts).
|
|
3128
3154
|
if (holdGate) {
|
|
3129
3155
|
if (holdGate.state !== 'watching') {
|
|
@@ -3179,6 +3205,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
3179
3205
|
// What the merchant's own checkout said its total was, as this browser recorded it
|
|
3180
3206
|
// (read only for a reviewed profile priced by it): the page is the card request's.
|
|
3181
3207
|
merchantTotal: merchantTotal.capture(pausedAt, cardPage),
|
|
3208
|
+
// Paddle's own checkout answers, as this browser recorded them (sent only with a Paddle TokenEx create).
|
|
3209
|
+
paddleCheckout: paddleAnswers,
|
|
3182
3210
|
onAuthorizationCreated: (id) => {
|
|
3183
3211
|
lifecycle.approvalCreated(id);
|
|
3184
3212
|
if (holdSandbox && !holdRequestFailed) {
|
|
@@ -3299,7 +3327,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
3299
3327
|
// companion cannot arrive first, and dropped if the continue fails.
|
|
3300
3328
|
// Both go only to the request the approval was created from, from the
|
|
3301
3329
|
// frame that sent it (cseContinuation refuses any other body).
|
|
3302
|
-
const continuation = cseContinuation(opts, deliverRequest.url, deliverBody, replay, { url: request.url, body }, preparation);
|
|
3330
|
+
const continuation = cseContinuation(opts, deliverRequest.url, deliverBody, replay, { url: request.url, body, headers: request.headers }, preparation, deliverRequest.headers);
|
|
3303
3331
|
const postData = Buffer.from(continuation.body).toString('base64');
|
|
3304
3332
|
if (continuation.shijiCvv !== null) {
|
|
3305
3333
|
shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
|
|
@@ -3629,6 +3657,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3629
3657
|
// but sends no document request, so it keeps the page id, as CDP's loaderId does. Nor does
|
|
3630
3658
|
// a document request that commits nothing: one that failed, or one answered 204 or 205.
|
|
3631
3659
|
const merchantTotal = new MerchantTotalWatch(opts.onEvent, opts.vault);
|
|
3660
|
+
// Paddle's own checkout answers, as attachToCdp records them (paddle-checkout-watch.ts).
|
|
3661
|
+
const paddleCheckout = new PaddleCheckoutWatch();
|
|
3632
3662
|
// A reviewed card endpoint merchant's own record of the order (card-endpoint-facts.ts).
|
|
3633
3663
|
const cardEndpointRecord = new CardEndpointFactsWatch();
|
|
3634
3664
|
let documentSerial = 1;
|
|
@@ -3690,6 +3720,10 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3690
3720
|
...(typeof body === 'string' ? { requestBody: body } : {}), page: `doc:${documentSerial}` });
|
|
3691
3721
|
}
|
|
3692
3722
|
catch { /* a request the page already dropped */ }
|
|
3723
|
+
try {
|
|
3724
|
+
paddleCheckout.sent(keyOf(request), { method: request.method(), url: request.url() });
|
|
3725
|
+
}
|
|
3726
|
+
catch { /* a request the page already dropped */ }
|
|
3693
3727
|
try {
|
|
3694
3728
|
cardEndpointRecord.sent(keyOf(request), { method: request.method(), url: request.url(),
|
|
3695
3729
|
resourceType: typeof request.resourceType === 'function' ? request.resourceType() : null });
|
|
@@ -3731,6 +3765,28 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3731
3765
|
cardEndpointRecord.read(key, text, finishedAt);
|
|
3732
3766
|
}).catch(() => cardEndpointRecord.failed(key));
|
|
3733
3767
|
}
|
|
3768
|
+
if (paddleCheckout.tracks(key)) {
|
|
3769
|
+
// One of Paddle's checkout answers: stamped finished at once, so a /pay's pause waits for its text, then its
|
|
3770
|
+
// status and its text, cut to the terms by the watch.
|
|
3771
|
+
const answeredAt = Date.now();
|
|
3772
|
+
paddleCheckout.finished(key, answeredAt);
|
|
3773
|
+
Promise.resolve().then(async () => {
|
|
3774
|
+
const response = await request.response();
|
|
3775
|
+
if (!response) {
|
|
3776
|
+
paddleCheckout.failed(key);
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
paddleCheckout.answered(key, response.status());
|
|
3780
|
+
let text = null;
|
|
3781
|
+
try {
|
|
3782
|
+
text = await response.text();
|
|
3783
|
+
}
|
|
3784
|
+
catch {
|
|
3785
|
+
text = null;
|
|
3786
|
+
}
|
|
3787
|
+
paddleCheckout.read(key, text, answeredAt);
|
|
3788
|
+
}).catch(() => paddleCheckout.failed(key));
|
|
3789
|
+
}
|
|
3734
3790
|
if (!merchantTotal.log.tracks(key))
|
|
3735
3791
|
return;
|
|
3736
3792
|
const completedAt = Date.now();
|
|
@@ -3800,6 +3856,7 @@ export async function attachToPlaywright(page, opts) {
|
|
|
3800
3856
|
if (request === documentRequest)
|
|
3801
3857
|
documentRequest = null;
|
|
3802
3858
|
merchantTotal.log.failed(keyOf(request));
|
|
3859
|
+
paddleCheckout.failed(keyOf(request));
|
|
3803
3860
|
cardEndpointRecord.failed(keyOf(request));
|
|
3804
3861
|
if (activeRequest && activeRequest.request === request && activeRequest.attempt.lose()) {
|
|
3805
3862
|
// The page's own script gave up on its request; the cardholder's
|
|
@@ -4292,6 +4349,9 @@ export async function attachToPlaywright(page, opts) {
|
|
|
4292
4349
|
// The merchant's own record of the order, as of the pause (see attachToCdp).
|
|
4293
4350
|
const cardRecord = cardEndpoint ? await cardEndpointFacts(cardEndpointRecord, cardEndpoint, request.url(), pausedAt, attempt.signal) : null;
|
|
4294
4351
|
assertRequestLive();
|
|
4352
|
+
// Paddle's own checkout answers as of the pause, once their texts are read (see attachToCdp).
|
|
4353
|
+
const paddleAnswers = await paddleCheckout.capture(pausedAt, request.url(), attempt.signal);
|
|
4354
|
+
assertRequestLive();
|
|
4295
4355
|
const replay = await opts.vault.authorize({
|
|
4296
4356
|
user: opts.user,
|
|
4297
4357
|
merchant: opts.merchant,
|
|
@@ -4311,6 +4371,8 @@ export async function attachToPlaywright(page, opts) {
|
|
|
4311
4371
|
merchantSignal: attempt.signal,
|
|
4312
4372
|
// Same as the CDP path: the merchant's total as this browser recorded it.
|
|
4313
4373
|
merchantTotal: merchantTotal.capture(pausedAt, pausedPage),
|
|
4374
|
+
// And Paddle's own checkout answers, as this browser recorded them.
|
|
4375
|
+
paddleCheckout: paddleAnswers,
|
|
4314
4376
|
onAuthorizationCreated: (id) => lifecycle.approvalCreated(id),
|
|
4315
4377
|
onApprovalUrl: (url) => { if (!preparation && !attempt.signal.aborted) {
|
|
4316
4378
|
lifecycle.approvalUrl(url);
|
|
@@ -4381,7 +4443,7 @@ export async function attachToPlaywright(page, opts) {
|
|
|
4381
4443
|
// A Shiji STS getTokenId binds the device's cvv to its companion
|
|
4382
4444
|
// first, exactly as in attachToCdp: only for the request the
|
|
4383
4445
|
// approval was created from, and its frame.
|
|
4384
|
-
const continuation = cseContinuation(opts, deliverRequest.url(), deliverBody, replay, { url: request.url(), body }, preparation);
|
|
4446
|
+
const continuation = cseContinuation(opts, deliverRequest.url(), deliverBody, replay, { url: request.url(), body, headers: request.headers() }, preparation, deliverRequest.headers());
|
|
4385
4447
|
if (continuation.shijiCvv !== null) {
|
|
4386
4448
|
// The document the getTokenId paused in. One that cannot be read leaves the CVV nowhere to
|
|
4387
4449
|
// go, so the card is not tokenized either.
|
package/dist/client.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type PreparationMode, type PreparationProcessor } from './prepared-proc
|
|
|
6
6
|
import { type VerifoneProfile, type VerifoneSubstitutions } from './verifone.js';
|
|
7
7
|
import { type MerchantSessionCapture } from './merchant-session-probe.js';
|
|
8
8
|
import { type PaddleTokenexServedFacts } from './paddle-tokenex.js';
|
|
9
|
+
import type { PaddleCheckoutCapture } from './paddle-checkout-watch.js';
|
|
9
10
|
import { VoiceModule, type VoiceOptions } from './voice.js';
|
|
10
11
|
import { BuyModule } from './buy.js';
|
|
11
12
|
import { RequestRegistry } from './requests.js';
|
|
@@ -462,6 +463,42 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
|
|
|
462
463
|
* Served on the same terms, and the API holds the lane to Agentcard's own company
|
|
463
464
|
* until a reviewed change binds the client key to the merchant.
|
|
464
465
|
*
|
|
466
|
+
* `tap_card_sdk_token`: the token request Tap Payments' card frame sends to sdk.tap.company, carrying
|
|
467
|
+
* the card only as one ciphertext the frame writes under the merchant's own RSA key. This build pauses
|
|
468
|
+
* it and writes over the placeholder's ciphertext the one the cardholder's device wrote under the key
|
|
469
|
+
* payment-core pins for the request's publishable key (tap-payments.ts), into that request alone; 3-D
|
|
470
|
+
* Secure and the merchant's charge then run in the agent's browser. Served on the same terms: only to
|
|
471
|
+
* a build that lists it, for a company Agentcard turned Tap on for.
|
|
472
|
+
*
|
|
473
|
+
* `fastspring_card_payment`: the card payment FastSpring's checkout posts, on every store's own
|
|
474
|
+
* host, to <store>.onfastspring.com[/popup-<name>]/session/<id>/payment under the session's
|
|
475
|
+
* X-Session-Token. This build pauses only the checkout's own card body at that exact URL (a card
|
|
476
|
+
* FastSpring saved, a renewal, a gift, a tax id's second card send and a CAPTCHA answer are refused
|
|
477
|
+
* at the pause, and another method's payment with no card continues); the cardholder's device writes
|
|
478
|
+
* the real card into the paused body and sends it through the checkout relay, and the checkout gets
|
|
479
|
+
* FastSpring's answer as payment-core's judge rebuilt it. The request names no amount, so the agent's
|
|
480
|
+
* pair, above zero, is the total the checkout shows. Served on the same terms:
|
|
481
|
+
* only to a build that lists it, for a company Agentcard turned FastSpring on for.
|
|
482
|
+
*
|
|
483
|
+
* `openpay_tokenizer`: the token create openpay.js sends from a merchant's own page to
|
|
484
|
+
* api.openpay.mx/v1/<merchant id>/tokens (api.openpay.co, api.openpay.pe; sandbox-api for a test
|
|
485
|
+
* row), only through a prepared checkout: openpay.js abandons the request 40 seconds after Pay, so
|
|
486
|
+
* the cardholder approves before Pay. The token URL names the merchant, so the claim binds the
|
|
487
|
+
* request by its host's environment and its shape, never by the prepared URL. The cardholder's
|
|
488
|
+
* device sends the card to Openpay itself and the page gets back Openpay's token alone, rebuilt by
|
|
489
|
+
* payment-core's judge. The request names no amount, so the agent's pair, above zero
|
|
490
|
+
* (agent-amount-processors.json), is the one the cardholder approves. Served on the same terms: only
|
|
491
|
+
* to a build that lists it, for a company Agentcard turned Openpay on for.
|
|
492
|
+
*
|
|
493
|
+
* `aps_custom_merchant_page`: Amazon Payment Services' custom merchant page, whose own card form
|
|
494
|
+
* posts top-level to /FortAPI/paymentPage on checkout.payfort.com (sbcheckout.payfort.com on APS's
|
|
495
|
+
* sandbox) with service_command=TOKENIZATION. This build pauses that navigation; the cardholder's
|
|
496
|
+
* phone sends the form with the real card through the checkout relay, and the paused navigation
|
|
497
|
+
* gets APS's own answer, its page or its redirect to the form's return page, only as payment-core's
|
|
498
|
+
* judge reads it for that form (amazon-payment-services.ts apsNavigationAnswer). The form names no
|
|
499
|
+
* amount, so the agent's pair, above zero, is the one the cardholder approves.
|
|
500
|
+
* Served on the same terms: only to a build that lists it, for a company Agentcard turned APS on for.
|
|
501
|
+
*
|
|
465
502
|
* The list itself is capabilities.json, sorted, one per line (scripts/generate-capabilities.mjs).
|
|
466
503
|
*/
|
|
467
504
|
export declare const SUPPORTED_CAPABILITIES: readonly string[];
|
|
@@ -964,6 +1001,13 @@ export interface AuthorizeInput extends ExecutionMetadata {
|
|
|
964
1001
|
* one from its network events, or the payment is refused before any create.
|
|
965
1002
|
*/
|
|
966
1003
|
merchantTotal?: MerchantTotalCapture;
|
|
1004
|
+
/**
|
|
1005
|
+
* For a Paddle TokenEx /pay: when it paused and the answers Paddle's checkout service gave the frame before it,
|
|
1006
|
+
* as this browser recorded them (see paddle-checkout-watch.ts). The adapters pass it, and it rides the create only
|
|
1007
|
+
* for that processor. The API holds `amount` to the total they state for today, and admits a free trial's $0.00
|
|
1008
|
+
* only on them; without them a $0.00 Paddle create is refused as before.
|
|
1009
|
+
*/
|
|
1010
|
+
paddleCheckout?: PaddleCheckoutCapture;
|
|
967
1011
|
}
|
|
968
1012
|
export declare class CardEncryptedError extends Error {
|
|
969
1013
|
psp: string;
|
|
@@ -1625,9 +1669,10 @@ export declare class VaultClient {
|
|
|
1625
1669
|
* capability (syncRegistry does), so before a sync, or against an API that
|
|
1626
1670
|
* withheld it, the adapters leave that processor's traffic exactly as they
|
|
1627
1671
|
* found it. prepare() asks this for ACI, whose card-number request names a
|
|
1628
|
-
* checkout id the merchant mints only at Pay time,
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1672
|
+
* checkout id the merchant mints only at Pay time, for Openpay, whose token
|
|
1673
|
+
* URL names the merchant's own id, and for a processor whose card request is
|
|
1674
|
+
* a GET (Worldpay eProtect), since that request is recognized by a query the
|
|
1675
|
+
* page has not written yet.
|
|
1631
1676
|
*/
|
|
1632
1677
|
recognizesProcessor(psp: string): boolean;
|
|
1633
1678
|
/**
|
package/dist/client.js
CHANGED
|
@@ -18,8 +18,10 @@ import { AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
|
|
|
18
18
|
import { PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
|
|
19
19
|
import { PAYGENT_PSP, paygentAnswerOutcome } from './paygent.generated.js';
|
|
20
20
|
import { PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
|
|
21
|
+
import { TAP_PAYMENTS_PSP, isTapPaymentsServedSubstitution } from './tap-payments.js';
|
|
21
22
|
import { WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
|
|
22
23
|
import { HIPAY_PSP } from './hipay.generated.js';
|
|
24
|
+
import { OPENPAY_PSP, openpayAnswerOutcome } from './openpay.generated.js';
|
|
23
25
|
import { TOAST_PSP, isToastCiphertext } from './toast.generated.js';
|
|
24
26
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
25
27
|
import { VoiceModule } from './voice.js';
|
|
@@ -38,6 +40,7 @@ import { JUSPAY_PSP, juspayPageAnswer } from './juspay-gate.js';
|
|
|
38
40
|
import { CARD_ENDPOINT_MODE, CARD_ENDPOINT_PSP, CardEndpointRefusedError, cardEndpointById, cardEndpointLive, cardEndpointSessionRefusal, } from './card-endpoint.js';
|
|
39
41
|
import { merchantCardEndpointFactsRefusal as sharedFactsRefusal, readMerchantCardEndpointFacts as sharedReadFacts, } from './merchant-card-endpoints.generated.js';
|
|
40
42
|
import { TOKENEX_IFRAME_PSP, tokenexIframePageAnswer, tokenexIframePlaceholderFits } from './tokenex-iframe.js';
|
|
43
|
+
import { APS_PSP, apsNavigationAnswer } from './amazon-payment-services.js';
|
|
41
44
|
import { PRIMER_PSP, primerTokenAnswer } from './primer.js';
|
|
42
45
|
import { isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
|
|
43
46
|
import { EbanxFollowUpError } from './ebanx.js';
|
|
@@ -453,6 +456,42 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
453
456
|
* Served on the same terms, and the API holds the lane to Agentcard's own company
|
|
454
457
|
* until a reviewed change binds the client key to the merchant.
|
|
455
458
|
*
|
|
459
|
+
* `tap_card_sdk_token`: the token request Tap Payments' card frame sends to sdk.tap.company, carrying
|
|
460
|
+
* the card only as one ciphertext the frame writes under the merchant's own RSA key. This build pauses
|
|
461
|
+
* it and writes over the placeholder's ciphertext the one the cardholder's device wrote under the key
|
|
462
|
+
* payment-core pins for the request's publishable key (tap-payments.ts), into that request alone; 3-D
|
|
463
|
+
* Secure and the merchant's charge then run in the agent's browser. Served on the same terms: only to
|
|
464
|
+
* a build that lists it, for a company Agentcard turned Tap on for.
|
|
465
|
+
*
|
|
466
|
+
* `fastspring_card_payment`: the card payment FastSpring's checkout posts, on every store's own
|
|
467
|
+
* host, to <store>.onfastspring.com[/popup-<name>]/session/<id>/payment under the session's
|
|
468
|
+
* X-Session-Token. This build pauses only the checkout's own card body at that exact URL (a card
|
|
469
|
+
* FastSpring saved, a renewal, a gift, a tax id's second card send and a CAPTCHA answer are refused
|
|
470
|
+
* at the pause, and another method's payment with no card continues); the cardholder's device writes
|
|
471
|
+
* the real card into the paused body and sends it through the checkout relay, and the checkout gets
|
|
472
|
+
* FastSpring's answer as payment-core's judge rebuilt it. The request names no amount, so the agent's
|
|
473
|
+
* pair, above zero, is the total the checkout shows. Served on the same terms:
|
|
474
|
+
* only to a build that lists it, for a company Agentcard turned FastSpring on for.
|
|
475
|
+
*
|
|
476
|
+
* `openpay_tokenizer`: the token create openpay.js sends from a merchant's own page to
|
|
477
|
+
* api.openpay.mx/v1/<merchant id>/tokens (api.openpay.co, api.openpay.pe; sandbox-api for a test
|
|
478
|
+
* row), only through a prepared checkout: openpay.js abandons the request 40 seconds after Pay, so
|
|
479
|
+
* the cardholder approves before Pay. The token URL names the merchant, so the claim binds the
|
|
480
|
+
* request by its host's environment and its shape, never by the prepared URL. The cardholder's
|
|
481
|
+
* device sends the card to Openpay itself and the page gets back Openpay's token alone, rebuilt by
|
|
482
|
+
* payment-core's judge. The request names no amount, so the agent's pair, above zero
|
|
483
|
+
* (agent-amount-processors.json), is the one the cardholder approves. Served on the same terms: only
|
|
484
|
+
* to a build that lists it, for a company Agentcard turned Openpay on for.
|
|
485
|
+
*
|
|
486
|
+
* `aps_custom_merchant_page`: Amazon Payment Services' custom merchant page, whose own card form
|
|
487
|
+
* posts top-level to /FortAPI/paymentPage on checkout.payfort.com (sbcheckout.payfort.com on APS's
|
|
488
|
+
* sandbox) with service_command=TOKENIZATION. This build pauses that navigation; the cardholder's
|
|
489
|
+
* phone sends the form with the real card through the checkout relay, and the paused navigation
|
|
490
|
+
* gets APS's own answer, its page or its redirect to the form's return page, only as payment-core's
|
|
491
|
+
* judge reads it for that form (amazon-payment-services.ts apsNavigationAnswer). The form names no
|
|
492
|
+
* amount, so the agent's pair, above zero, is the one the cardholder approves.
|
|
493
|
+
* Served on the same terms: only to a build that lists it, for a company Agentcard turned APS on for.
|
|
494
|
+
*
|
|
456
495
|
* The list itself is capabilities.json, sorted, one per line (scripts/generate-capabilities.mjs).
|
|
457
496
|
*/
|
|
458
497
|
export const SUPPORTED_CAPABILITIES = DECLARED_CAPABILITIES;
|
|
@@ -843,7 +882,8 @@ export class AmountRequiredError extends Error {
|
|
|
843
882
|
const processor = PROCESSOR_NAMES[psp] ?? psp;
|
|
844
883
|
// A Payrexx card request names the frame's total, but the merchant's page charges its own total after the token.
|
|
845
884
|
const lacks = psp === REVOLUT_PSP ? 'names its amount but not its currency'
|
|
846
|
-
: psp === PAYREXX_PSP ? "only mints a token the merchant's page charges later"
|
|
885
|
+
: psp === PAYREXX_PSP ? "only mints a token the merchant's page charges later"
|
|
886
|
+
: psp === TAP_PAYMENTS_PSP ? 'does not decide the amount charged' : 'names no amount';
|
|
847
887
|
// "An Omise payment request", "A Clover payment request": the article follows the name's first letter.
|
|
848
888
|
const article = /^[AEIOU]/.test(processor) ? 'An' : 'A';
|
|
849
889
|
super(`${article} ${processor} payment request ${lacks}, so pass amount and currency: that is the amount the cardholder approves. Nothing was created and nothing was charged.`);
|
|
@@ -1844,19 +1884,33 @@ const PAYGENT_PREPARATION_REFUSALS = [
|
|
|
1844
1884
|
*/
|
|
1845
1885
|
const HIPAY_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'sandbox_host_on_live_row', 'merchant_profile_mismatch',
|
|
1846
1886
|
'amount_required'];
|
|
1887
|
+
/**
|
|
1888
|
+
* The API's refusals of an Openpay preparation, before any preparation exists, that prepareCheckout passes on by
|
|
1889
|
+
* name: the company is not turned on for Openpay (`processor_unavailable`), the environment is not the caller's key
|
|
1890
|
+
* mode's (`live_host_on_sandbox_row`, `sandbox_host_on_live_row`), a live checkout's merchant is not the name the
|
|
1891
|
+
* reviewed pin for its origin carries (`merchant_profile_mismatch`), and the price is zero (`amount_required`), since
|
|
1892
|
+
* the token create names no amount. An origin no reviewed pin lists answers `unsupported_checkout`, which
|
|
1893
|
+
* PREPARATION_REFUSALS names for every checkout. The API holds the pins, not this build, so a merchant a later release
|
|
1894
|
+
* reviews needs no new SDK. Only for an Openpay checkout.
|
|
1895
|
+
*/
|
|
1896
|
+
const OPENPAY_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'sandbox_host_on_live_row',
|
|
1897
|
+
'merchant_profile_mismatch', 'amount_required'];
|
|
1847
1898
|
/**
|
|
1848
1899
|
* The reason the cardholder's device declines a bound prepared row with, per processor, when its own
|
|
1849
1900
|
* send boundary passed before the card left it (the Vault's sendWindow, 12 seconds after it last saw
|
|
1850
1901
|
* the checkout unbound): a prepared Amadeus SDK add, a prepared Secure Acceptance token create and a
|
|
1851
1902
|
* prepared Wix tokenize request. It names the same known non-payment the adapters' own send window
|
|
1852
1903
|
* closes on (amadeus.ts), which can close later, so authorize() ends the attempt the way that window
|
|
1853
|
-
* does, as a CheckoutPreparationError with that reason, and never as the cardholder's "no".
|
|
1904
|
+
* does, as a CheckoutPreparationError with that reason, and never as the cardholder's "no". A prepared
|
|
1905
|
+
* HiPay token create too, and a prepared Openpay token create, whose device sends the card at most 25
|
|
1906
|
+
* seconds after it last saw the checkout unbound, inside the adapters' 30.
|
|
1854
1907
|
*/
|
|
1855
1908
|
const PREPARED_SEND_WINDOW_DECLINES = Object.freeze({
|
|
1856
1909
|
amadeus_checkout: 'amadeus_send_window_closed',
|
|
1857
1910
|
[SECURE_ACCEPTANCE_PSP]: 'secure_acceptance_send_window_closed',
|
|
1858
1911
|
[WIX_PAYMENTS_PSP]: 'wix_payments_send_window_closed',
|
|
1859
1912
|
[HIPAY_PSP]: 'hipay_send_window_closed',
|
|
1913
|
+
[OPENPAY_PSP]: 'openpay_send_window_closed',
|
|
1860
1914
|
});
|
|
1861
1915
|
/**
|
|
1862
1916
|
* Refusals a CyberSource Flex preparation that tokenizes before Pay answers (4xx) before any
|
|
@@ -2180,9 +2234,10 @@ export class VaultClient {
|
|
|
2180
2234
|
* capability (syncRegistry does), so before a sync, or against an API that
|
|
2181
2235
|
* withheld it, the adapters leave that processor's traffic exactly as they
|
|
2182
2236
|
* found it. prepare() asks this for ACI, whose card-number request names a
|
|
2183
|
-
* checkout id the merchant mints only at Pay time,
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
2237
|
+
* checkout id the merchant mints only at Pay time, for Openpay, whose token
|
|
2238
|
+
* URL names the merchant's own id, and for a processor whose card request is
|
|
2239
|
+
* a GET (Worldpay eProtect), since that request is recognized by a query the
|
|
2240
|
+
* page has not written yet.
|
|
2186
2241
|
*/
|
|
2187
2242
|
recognizesProcessor(psp) {
|
|
2188
2243
|
return this.registry.some((rec) => rec.psp === psp);
|
|
@@ -2425,7 +2480,8 @@ export class VaultClient {
|
|
|
2425
2480
|
const secureAcceptanceProfileId = secureAcceptance ? secureAcceptanceCheckoutProfile({ environment: input.environment,
|
|
2426
2481
|
profileId: input.secureAcceptanceProfileId, merchantOrigin: input.merchantOrigin }) : null;
|
|
2427
2482
|
// A live Paygent checkout's merchant is the API's to judge against the reviewed pins it holds, before anyone is
|
|
2428
|
-
// asked to approve (PAYGENT_PREPARATION_REFUSALS); test mode needs no pin.
|
|
2483
|
+
// asked to approve (PAYGENT_PREPARATION_REFUSALS); test mode needs no pin. So is a live Openpay checkout's
|
|
2484
|
+
// origin and merchant (OPENPAY_PREPARATION_REFUSALS); Openpay's sandbox needs no pin either.
|
|
2429
2485
|
const timeoutMs = input.timeoutMs ?? 15 * 60_000;
|
|
2430
2486
|
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647)
|
|
2431
2487
|
throw fail('timeout_invalid');
|
|
@@ -2547,6 +2603,7 @@ export class VaultClient {
|
|
|
2547
2603
|
|| (input.psp === WIX_PAYMENTS_PSP && WIX_PAYMENTS_PREPARATION_REFUSALS.includes(error.code))
|
|
2548
2604
|
|| (input.psp === PAYGENT_PSP && PAYGENT_PREPARATION_REFUSALS.includes(error.code))
|
|
2549
2605
|
|| (input.psp === HIPAY_PSP && HIPAY_PREPARATION_REFUSALS.includes(error.code))
|
|
2606
|
+
|| (input.psp === OPENPAY_PSP && OPENPAY_PREPARATION_REFUSALS.includes(error.code))
|
|
2550
2607
|
|| (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
|
|
2551
2608
|
|| (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
|
|
2552
2609
|
throw fail(error.code);
|
|
@@ -2845,7 +2902,8 @@ export class VaultClient {
|
|
|
2845
2902
|
// like a missing one.
|
|
2846
2903
|
// A ComGate payment charges the total ComGate already holds for it, the same way, and a Webpay transaction the
|
|
2847
2904
|
// total the merchant's server bound to it, and a Payrexx page charges its own
|
|
2848
|
-
// total after the frame's token.
|
|
2905
|
+
// total after the frame's token. The charge that follows a Tap token charges the order the merchant's session
|
|
2906
|
+
// holds, the same way.
|
|
2849
2907
|
// A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay, a
|
|
2850
2908
|
// PayMongo attach or a MakeCommerce card post charges the total its payment, checkout session or
|
|
2851
2909
|
// transaction already holds, so a zero pair would show the cardholder no price at all: it is refused
|
|
@@ -2854,6 +2912,7 @@ export class VaultClient {
|
|
|
2854
2912
|
// A Toast payment method is charged by the page's own confirm for whatever its intent says, the same way.
|
|
2855
2913
|
// An Omise token is charged later for whatever the merchant's server decides, as a Clover one is.
|
|
2856
2914
|
// An SB Payment Service token is charged later the same way.
|
|
2915
|
+
// An Openpay token is charged later for whatever the merchant's server decides, as a HiPay one is.
|
|
2857
2916
|
if (hasAmount && rec && ABOVE_ZERO_PROCESSORS.includes(rec.psp) && Number(input.amount) <= 0)
|
|
2858
2917
|
throw new AmountRequiredError(rec.psp);
|
|
2859
2918
|
// Nor is the agent's pair tied to the page by anything but the total the
|
|
@@ -2920,6 +2979,12 @@ export class VaultClient {
|
|
|
2920
2979
|
// Only the profile's id: the API takes the leg, the key and its environment from the
|
|
2921
2980
|
// profile Agentcard reviewed, never from the caller.
|
|
2922
2981
|
...(verifoneProfile ? { verifone: { profile: verifoneProfile.id } } : {}),
|
|
2982
|
+
// Paddle TokenEx only: Paddle's checkout answers this browser recorded before the /pay paused, which the API
|
|
2983
|
+
// holds `amount` to (paddle-checkout-watch.ts). Any other processor's create would be refused with them.
|
|
2984
|
+
...(rec?.psp === PADDLE_TOKENEX_PSP && input.paddleCheckout?.answers.length ? { paddle_checkout: {
|
|
2985
|
+
paused_at: input.paddleCheckout.pausedAt,
|
|
2986
|
+
answers: input.paddleCheckout.answers.map((answer) => ({ method: answer.method, url: answer.url, status: answer.status, completed_at: answer.completedAt, body: answer.body })),
|
|
2987
|
+
} } : {}),
|
|
2923
2988
|
// A prepared checkout already carries the page origin as merchant_origin.
|
|
2924
2989
|
...(preparation
|
|
2925
2990
|
? { preparation_id: preparation.id, checkout_key: preparation.checkoutKey, merchant_origin: preparation.merchantOrigin, form_guidance: 'filled' }
|
|
@@ -3003,10 +3068,14 @@ export class VaultClient {
|
|
|
3003
3068
|
// A prepared HiPay token create too, for 20 seconds: HiPay's hosted fields give it 20 seconds from Pay, so
|
|
3004
3069
|
// an answer the cardholder's device records late in that span still reaches the frame before it gives up,
|
|
3005
3070
|
// and the API names 500 ms as such a row's poll interval.
|
|
3071
|
+
// A prepared Openpay token create too, for 40 seconds: openpay.js gives it 40 seconds from Pay, so the token
|
|
3072
|
+
// reaches the page within half a second of the device's answer, and the API names 500 ms as such a row's poll
|
|
3073
|
+
// interval.
|
|
3006
3074
|
const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
|
|
3007
3075
|
: preparation?.psp === PADDLE_VGS_PSP || preparation?.psp === HIPAY_PSP ? Date.now() + 20_000
|
|
3008
|
-
: preparation?.psp ===
|
|
3009
|
-
: preparation?.psp ===
|
|
3076
|
+
: preparation?.psp === OPENPAY_PSP ? Date.now() + 40_000
|
|
3077
|
+
: preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
|
|
3078
|
+
: preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
|
|
3010
3079
|
// A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
|
|
3011
3080
|
// token and approves as soon as this bind reaches it, while the page may give createToken only
|
|
3012
3081
|
// seconds (Alaska Airlines' flight checkout gives it three). Poll every 100 ms for 10 seconds,
|
|
@@ -3179,11 +3248,14 @@ export class VaultClient {
|
|
|
3179
3248
|
// Paddle TokenEx carries the two ciphertexts at tokenex_card and the five facts beside them in
|
|
3180
3249
|
// `paddle_tokenex_facts` (the expiry parts are numbers, so they cannot ride `fields`).
|
|
3181
3250
|
const paddleTokenex = rec?.psp === PADDLE_TOKENEX_PSP;
|
|
3251
|
+
// Tap carries its one ciphertext at the body root too (`at` is ''), and nothing to remove (tap-payments.ts).
|
|
3252
|
+
const tap = rec?.psp === TAP_PAYMENTS_PSP;
|
|
3182
3253
|
const fieldsOk = shiji ? isShijiStsServedSubstitution(sub) && isShijiStsServedCompanion(s.shiji_sts_companion)
|
|
3183
3254
|
: paddleTokenex ? isPaddleTokenexServedSubstitution(sub) && isPaddleTokenexServedFacts(s.paddle_tokenex_facts)
|
|
3184
|
-
:
|
|
3185
|
-
&& sub.
|
|
3186
|
-
|
|
3255
|
+
: tap ? isTapPaymentsServedSubstitution(sub)
|
|
3256
|
+
: sub && typeof sub === 'object' && sub.encoding === 'json' && typeof sub.at === 'string' && sub.at
|
|
3257
|
+
&& sub.fields && typeof sub.fields === 'object' && !Array.isArray(sub.fields)
|
|
3258
|
+
&& Object.values(sub.fields).every((v) => typeof v === 'string' && v.length > 0);
|
|
3187
3259
|
if (!fieldsOk)
|
|
3188
3260
|
throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
|
|
3189
3261
|
// A Fiserv approval carries exactly its envelope's four members at
|
|
@@ -3388,6 +3460,19 @@ export class VaultClient {
|
|
|
3388
3460
|
response.headers = { 'content-type': 'application/json' };
|
|
3389
3461
|
response.body = judged.body;
|
|
3390
3462
|
}
|
|
3463
|
+
if (rec?.psp === OPENPAY_PSP) {
|
|
3464
|
+
// Openpay's answer enters the page only as payment-core rebuilds an approved token from its id, masked number,
|
|
3465
|
+
// brand and points flag (openpay.js openpayAnswerOutcome: HTTP 200 or 201 and a token in shape), under its
|
|
3466
|
+
// approved status and a JSON content type. openpay.js hands any 2xx to the page's success callback, so
|
|
3467
|
+
// anything else, an error object Openpay answers inside a 2xx included, is never handed over as an approval;
|
|
3468
|
+
// the card may have left the device, so the outcome is unknown rather than declined.
|
|
3469
|
+
const judged = openpayAnswerOutcome(response.status, response.body, null);
|
|
3470
|
+
if (judged.outcome !== 'approved' || typeof judged.body !== 'string')
|
|
3471
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'openpay_token_unconfirmed');
|
|
3472
|
+
response.status = response.status === 201 ? 201 : 200;
|
|
3473
|
+
response.headers = { 'content-type': 'application/json' };
|
|
3474
|
+
response.body = judged.body;
|
|
3475
|
+
}
|
|
3391
3476
|
if (rec?.psp === PRIMER_PSP) {
|
|
3392
3477
|
// Primer's answer enters the page only as one single-use card token rebuilt from
|
|
3393
3478
|
// payment-core's allow-list, under a JSON content type (primer.ts primerTokenAnswer).
|
|
@@ -3412,6 +3497,21 @@ export class VaultClient {
|
|
|
3412
3497
|
response.headers = answer.headers;
|
|
3413
3498
|
response.body = answer.body;
|
|
3414
3499
|
}
|
|
3500
|
+
if (rec?.psp === APS_PSP) {
|
|
3501
|
+
// Amazon Payment Services: the paused top-level navigation is answered with APS's own page under its HTML
|
|
3502
|
+
// type, or with APS's redirect to the paused form's return page, and only with a token payment-core's judge
|
|
3503
|
+
// reads for that form (amazon-payment-services.ts apsNavigationAnswer). Anything else is not an answer to
|
|
3504
|
+
// hand the browser: the card has already reached APS. No autopilot run sends an APS form (payment-core, the
|
|
3505
|
+
// authority and the API refuse it), so an approval the API reports as unattended is not handed over either.
|
|
3506
|
+
if (execution.executionMode === 'autopilot')
|
|
3507
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'autopilot_submission_mode_invalid');
|
|
3508
|
+
const answer = apsNavigationAnswer(input.request, response);
|
|
3509
|
+
if (!answer)
|
|
3510
|
+
throw new PaymentOutcomeUnknownError(authorizationId, 'aps_answer_unconfirmed');
|
|
3511
|
+
response.status = answer.status;
|
|
3512
|
+
response.headers = answer.headers;
|
|
3513
|
+
response.body = answer.body;
|
|
3514
|
+
}
|
|
3415
3515
|
let processorContext;
|
|
3416
3516
|
if (mercadoContext) {
|
|
3417
3517
|
try {
|