@agent-cards/sdk 0.23.13 → 0.23.14
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 +35 -0
- package/PREFLIGHT.md +1 -1
- package/README.md +52 -13
- package/dist/adyen-merchant-hosted.generated.js +2 -2
- package/dist/card-fields.generated.js +841 -3
- package/dist/cdp.js +68 -12
- package/dist/client.d.ts +87 -1
- package/dist/client.js +196 -12
- package/dist/creditguard.generated.d.ts +100 -2
- package/dist/creditguard.generated.js +333 -3
- package/dist/datatrans.generated.d.ts +19 -4
- package/dist/datatrans.generated.js +15 -3
- package/dist/ebanx.d.ts +107 -0
- package/dist/ebanx.generated.d.ts +78 -0
- package/dist/ebanx.generated.js +577 -0
- package/dist/ebanx.js +159 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/lifecycle.d.ts +28 -5
- package/dist/lifecycle.js +37 -5
- package/dist/paddle-tokenex.d.ts +56 -0
- package/dist/paddle-tokenex.generated.d.ts +67 -0
- package/dist/paddle-tokenex.generated.js +951 -0
- package/dist/paddle-tokenex.js +95 -0
- package/dist/pre-claim.d.ts +38 -1
- package/dist/pre-claim.js +291 -17
- package/dist/preparation.d.ts +13 -0
- package/dist/preparation.js +22 -0
- package/dist/prepared-processor.js +6 -2
- package/dist/primer.generated.js +876 -38
- package/dist/redsys.d.ts +42 -0
- package/dist/redsys.js +68 -0
- package/dist/stripe-checkout.generated.js +462 -9
- package/package.json +8 -5
package/dist/client.js
CHANGED
|
@@ -15,6 +15,7 @@ import { CONVERGE_CAPABILITY } from './elavon-converge.js';
|
|
|
15
15
|
import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
|
|
16
16
|
import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
|
|
17
17
|
import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP, paddleVgsCardFactsOf } from './paddle-vgs.generated.js';
|
|
18
|
+
import { PADDLE_TOKENEX_CAPABILITY, PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
|
|
18
19
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
19
20
|
import { VoiceModule } from './voice.js';
|
|
20
21
|
import { BuyModule } from './buy.js';
|
|
@@ -32,6 +33,7 @@ import { PELECARD_CAPABILITY, PELECARD_PSP } from './pelecard.js';
|
|
|
32
33
|
import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
|
|
33
34
|
import { PAYRAILS_CAPABILITY, isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
|
|
34
35
|
import { WSPAY_CAPABILITY, WSPAY_PSP } from './wspay.js';
|
|
36
|
+
import { EbanxFollowUpError } from './ebanx.js';
|
|
35
37
|
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';
|
|
36
38
|
/**
|
|
37
39
|
* The modes this SDK can finish. Asked for on syncRegistry (the API serves
|
|
@@ -187,6 +189,11 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
187
189
|
* Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
|
|
188
190
|
* only after a sync.
|
|
189
191
|
*
|
|
192
|
+
* `redsys_hosted_payment_page`: Redsys's hosted payment page (the TPV Virtual redirect), whose
|
|
193
|
+
* card-free signed entry this build pauses for the cardholder's device and whose card page this build
|
|
194
|
+
* never lets the agent's browser post to (pre-claim.ts, redsys.ts). Served on the same terms as NMI's: only
|
|
195
|
+
* to a build that lists it, for a company Agentcard turned Redsys on for.
|
|
196
|
+
*
|
|
190
197
|
* `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
|
|
191
198
|
* vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
|
|
192
199
|
* checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
|
|
@@ -194,8 +201,32 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
194
201
|
* device sends the card itself and the frame gets back the stored card's id alone.
|
|
195
202
|
* Served on the same terms: only to a build that lists it, for a company Agentcard
|
|
196
203
|
* turned Paddle on for.
|
|
204
|
+
*
|
|
205
|
+
* `paddle_tokenex_pay`: the /pay Paddle's checkout frame sends to its checkout service
|
|
206
|
+
* (checkout-service.paddle.com) on a checkout that names TokenEx. The frame encrypts the
|
|
207
|
+
* card in the page under the account's TokenEx key, so /pay carries the placeholder's two
|
|
208
|
+
* ciphertexts and facts; this build pauses it and writes over them the device's own, under
|
|
209
|
+
* the key payment-core pins (paddle-tokenex.ts). Served on the same terms: only to a build
|
|
210
|
+
* that lists it, for a company Agentcard turned Paddle on for.
|
|
211
|
+
*
|
|
212
|
+
* `lyra_krypton_payment`: the card payment Lyra's Krypton form (PayZen, Systempay,
|
|
213
|
+
* Sogecommerce) sends from its hidden checkout frame to ProcessPayment/2 on the brand's API
|
|
214
|
+
* host. This build pauses only the frame's card body under a form token that pays once; the
|
|
215
|
+
* cardholder's device sends the card through the checkout relay, and the frame gets Lyra's
|
|
216
|
+
* answer byte for byte with Lyra's payment id, so it runs any 3-D Secure step itself and its
|
|
217
|
+
* card-free follow-up passes untouched. Served on the same terms: only to a build that lists
|
|
218
|
+
* it, for a company Agentcard turned Lyra on for.
|
|
219
|
+
*
|
|
220
|
+
* `payfast_card_pay`: the card tab's pay on Payfast's hosted payment page (POST
|
|
221
|
+
* /eng/method/CreditCard/<uuid>/pay on payment.payfast.io, www.payfast.co.za or the sandbox),
|
|
222
|
+
* which this build pauses only as the card tab's own form body and answers with Payfast's own
|
|
223
|
+
* answer as the API recorded it: `[true]` once the payment is done, or the 3-D Secure step the
|
|
224
|
+
* card tab then runs in the agent's browser. The cardholder's device sends the card through the
|
|
225
|
+
* checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
226
|
+
* Agentcard turned Payfast on for.
|
|
197
227
|
*/
|
|
198
|
-
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, 'gravy_session_fields_put', 'moneris_hosted_tokenization', PADDLE_VGS_CAPABILITY
|
|
228
|
+
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, 'gravy_session_fields_put', 'moneris_hosted_tokenization', PADDLE_VGS_CAPABILITY, PADDLE_TOKENEX_CAPABILITY,
|
|
229
|
+
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay'];
|
|
199
230
|
/**
|
|
200
231
|
* Processors whose paused request names no amount at all, so the pair the
|
|
201
232
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -218,13 +249,17 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
|
|
|
218
249
|
* token later, so the agent's pair, above zero (MONERIS_PSP below), is the one
|
|
219
250
|
* figure the approval shows. Paddle VGS's card request carries none either: Paddle
|
|
220
251
|
* charges the stored card at /pay for what the checkout says, so the agent's pair,
|
|
221
|
-
* which the prepared checkout carries, is the total the cardholder approves.
|
|
252
|
+
* which the prepared checkout carries, is the total the cardholder approves. Payfast's
|
|
253
|
+
* card pay carries none either: the merchant's entry bound the total to the payment,
|
|
254
|
+
* so the agent sends the total Payfast's page shows, above zero (PAYFAST_PSP below),
|
|
255
|
+
* and the API holds it to the payment Payfast itself reports.
|
|
222
256
|
*/
|
|
223
257
|
const MONERIS_PSP = 'moneris';
|
|
224
|
-
const
|
|
258
|
+
const PAYFAST_PSP = 'payfast';
|
|
259
|
+
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP];
|
|
225
260
|
/** How AmountRequiredError names a processor. */
|
|
226
261
|
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
|
|
227
|
-
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle' };
|
|
262
|
+
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast' };
|
|
228
263
|
/**
|
|
229
264
|
* Processors whose request and answer name no amount at all, so the total the
|
|
230
265
|
* payment page shows (`pageAmount`) is the one figure that ties the agent's pair
|
|
@@ -840,6 +875,14 @@ function refusalsOf(v) {
|
|
|
840
875
|
}
|
|
841
876
|
return out;
|
|
842
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* How often sendEbanxFollowUp reads a send back while the cardholder's device has it: every half second, whatever the
|
|
880
|
+
* client's own `pollIntervalMs`. One send is out at a time per approval, so this is 120 reads a minute, inside the
|
|
881
|
+
* API's 240 a minute per caller and authorization (routes/api/v2/checkout-follow-ups.ts).
|
|
882
|
+
*/
|
|
883
|
+
export const EBANX_FOLLOW_UP_READ_MS = 500;
|
|
884
|
+
/** The longest sendEbanxFollowUp waits after reads the API refused as too many (429): 1, 2, 4, 8, then 15 seconds. */
|
|
885
|
+
export const EBANX_FOLLOW_UP_BACKOFF_MAX_MS = 15_000;
|
|
843
886
|
export class CheckoutApiError extends Error {
|
|
844
887
|
status;
|
|
845
888
|
path;
|
|
@@ -1012,18 +1055,69 @@ const ACI_CHECKOUT_ID = /[0-9a-f]{32}\.[a-z0-9]{1,16}-vm-tx[0-9]{1,3}/gi;
|
|
|
1012
1055
|
function maskAciCheckout(path) {
|
|
1013
1056
|
return path.replace(ACI_CHECKOUT, '$1{checkoutId}').replace(ACI_CHECKOUT_ID, '{checkoutId}');
|
|
1014
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Payfast's hosted payment page sends the card to /eng/method/CreditCard/<uuid>/pay on its own host
|
|
1060
|
+
* with no auth header and no CSRF token (payment-core payfast.js): the payment's uuid is the
|
|
1061
|
+
* request's only credential, and with it anyone can read that payment page, its amount and its
|
|
1062
|
+
* merchant. Every route of the page carries the same uuid, so on every host Payfast owns (payfast.io and
|
|
1063
|
+
* payfast.co.za and any name under them) any id of its shape, in any case or after an escaped slash,
|
|
1064
|
+
* reads {uuid}.
|
|
1065
|
+
*/
|
|
1066
|
+
const PAYFAST_HOST = /(?:^|\.)payfast\.(?:io|co\.za)\.?$/i;
|
|
1067
|
+
const PAYFAST_UUID = /[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi;
|
|
1068
|
+
function maskPayfastUuid(path) {
|
|
1069
|
+
return path.replace(PAYFAST_UUID, '{uuid}');
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Paddle's checkout frame pays a TokenEx checkout with its own /pay on checkout-service.paddle.com
|
|
1073
|
+
* (sandbox-checkout-service in test), and the checkout id in that path is the checkout's only
|
|
1074
|
+
* credential there, no cookies (payment-core paddle-tokenex.js): with it, and the card in the body,
|
|
1075
|
+
* a payment reaches that checkout. On those hosts the segment between /checkout/ or
|
|
1076
|
+
* /transaction-checkout/ and /pay, in any case or after an escaped slash, reads {checkout}.
|
|
1077
|
+
*/
|
|
1078
|
+
const PADDLE_CHECKOUT_HOST = /^(?:sandbox-)?checkout-service\.paddle\.com\.?$/i;
|
|
1079
|
+
const PADDLE_PAY = /(\/(?:checkout|transaction-checkout)(?:\/|%2f))[^/]*(\/pay)/gi;
|
|
1080
|
+
function maskPaddleCheckout(path) {
|
|
1081
|
+
return path.replace(PADDLE_PAY, '$1{checkout}$2');
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Lyra's Krypton frame sends its payment to ProcessPayment/2 on the brand's API host with the form
|
|
1085
|
+
* token's session id as a `;jsessionid=` path parameter (payment-core lyra.js). With the form token the
|
|
1086
|
+
* body carries, that id continues the payment on Lyra's side, so on Lyra's hosts it reads `{session}`,
|
|
1087
|
+
* in any case: https://api.systempay.fr/api-payment/V4/Charge/Public/ProcessPayment/2;jsessionid={session}.
|
|
1088
|
+
*/
|
|
1089
|
+
const LYRA_HOST = /^(?:api\.payzen\.eu|api\.systempay\.fr|api-sogecommerce\.societegenerale\.eu|api\.lyra\.com)\.?$/i;
|
|
1090
|
+
const LYRA_SESSION = /;jsessionid=[^/;?#]*/gi;
|
|
1091
|
+
function maskLyraSession(path) {
|
|
1092
|
+
return path.replace(LYRA_SESSION, ';jsessionid={session}');
|
|
1093
|
+
}
|
|
1015
1094
|
/**
|
|
1016
1095
|
* The processors whose card request carries its own credential in the URL path, with the hosts
|
|
1017
1096
|
* that send it, the text that names those hosts in a string that is not a URL, and the mask. The
|
|
1018
1097
|
* guard in redact-url.test.mjs fails for a payment-core recognizer that no one has sorted into
|
|
1019
1098
|
* this list or out of it.
|
|
1020
1099
|
*/
|
|
1100
|
+
/**
|
|
1101
|
+
* Redsys's hosted payment page names its session in the path of every page and post after the entry
|
|
1102
|
+
* (/sis/pagoTarjeta;jsessionid=<id>), and that id is what carries a card post into the session the
|
|
1103
|
+
* merchant's signed entry opened. On Redsys's hosts the session id, in any case or escaped, reads
|
|
1104
|
+
* {session}. The entry itself carries none.
|
|
1105
|
+
*/
|
|
1106
|
+
const REDSYS_HOST = /^sis(?:-t)?\.redsys\.es\.?$/i;
|
|
1107
|
+
const REDSYS_SESSION = /((?:;|%3b)jsessionid(?:=|%3d))[^/?#;]*/gi;
|
|
1108
|
+
function maskRedsysSession(path) {
|
|
1109
|
+
return path.replace(REDSYS_SESSION, '$1{session}');
|
|
1110
|
+
}
|
|
1021
1111
|
const PATH_CREDENTIALS = [
|
|
1022
1112
|
{ host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
|
|
1023
1113
|
{ host: GRAVY_HOST, named: /gr4vy\.app/i, mask: maskGravySession },
|
|
1024
1114
|
{ host: RAPYD_HOST, named: /rapyd\.net/i, mask: maskRapydToken },
|
|
1025
1115
|
{ host: STRIPE_API_HOST, named: /api\.stripe\.com/i, mask: maskStripeSession },
|
|
1026
1116
|
{ host: ACI_HOST, named: /oppwa\.com/i, mask: maskAciCheckout },
|
|
1117
|
+
{ host: PAYFAST_HOST, named: /payfast\.(?:io|co\.za)/i, mask: maskPayfastUuid },
|
|
1118
|
+
{ host: PADDLE_CHECKOUT_HOST, named: /checkout-service\.paddle\.com/i, mask: maskPaddleCheckout },
|
|
1119
|
+
{ host: REDSYS_HOST, named: /redsys\.es/i, mask: maskRedsysSession },
|
|
1120
|
+
{ host: LYRA_HOST, named: /(?:payzen\.eu|systempay\.fr|societegenerale\.eu|lyra\.com)/i, mask: maskLyraSession },
|
|
1027
1121
|
];
|
|
1028
1122
|
/**
|
|
1029
1123
|
* A URL as it may appear in an error message: origin + path only. A paused
|
|
@@ -1039,6 +1133,11 @@ const PATH_CREDENTIALS = [
|
|
|
1039
1133
|
* On Rapyd's hosts the hosted page's id reads `{token}`: https://api.rapyd.net/v1/checkout/{token}.
|
|
1040
1134
|
* On api.stripe.com a Checkout Session id reads `{session}`:
|
|
1041
1135
|
* https://api.stripe.com/v1/payment_pages/{session}/confirm.
|
|
1136
|
+
* On Payfast's hosts the payment's uuid reads `{uuid}`:
|
|
1137
|
+
* https://payment.payfast.io/eng/method/CreditCard/{uuid}/pay.
|
|
1138
|
+
* On Redsys's hosts the session id after `;jsessionid=` reads `{session}`:
|
|
1139
|
+
* https://sis.redsys.es/sis/pagoTarjeta;jsessionid={session}.
|
|
1140
|
+
* On Lyra's API hosts the form token's session id reads `{session}`.
|
|
1042
1141
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1043
1142
|
* same segments masked.
|
|
1044
1143
|
*/
|
|
@@ -1341,7 +1440,7 @@ export class VaultClient {
|
|
|
1341
1440
|
// wspay_confirm, shiji_sts_pinned_key, amadeus_checkout_actions.v1,
|
|
1342
1441
|
// worldline_mycheckout_form, verifone_card_capture, ekashu_hosted_payment_page,
|
|
1343
1442
|
// cybersource_sa_token_create, gravy_session_fields_put,
|
|
1344
|
-
// moneris_hosted_tokenization) and
|
|
1443
|
+
// moneris_hosted_tokenization, paddle_vgs_cards, redsys_hosted_payment_page) and
|
|
1345
1444
|
// the caller's company is one Agentcard turned that processor on for, so this
|
|
1346
1445
|
// build arms Fiserv's card capture, GMO's getToken, CyberSource Flex's card
|
|
1347
1446
|
// fields, Juspay's card flow, Datatrans's Secure Fields card post, the
|
|
@@ -1720,7 +1819,10 @@ export class VaultClient {
|
|
|
1720
1819
|
...(fiservPin ? { merchant_profile: fiservPin.id } : {}),
|
|
1721
1820
|
...(verifone ? { merchant_profile: verifone.id } : {}),
|
|
1722
1821
|
...(declaration ? { sandbox_declaration: { endpoint: declaration.endpoint, client_key: declaration.clientKey, environment: 'test' } } : {}),
|
|
1723
|
-
|
|
1822
|
+
// CreditGuard: this build answers the hosted page's own 3-D Secure calls (pre-claim.ts), so the
|
|
1823
|
+
// cardholder's device can run 3-D Secure before the submit; the API admits such a transaction
|
|
1824
|
+
// only for a build that says so.
|
|
1825
|
+
...(input.psp === 'creditguard' ? { payment_page_url: input.paymentPageUrl, three_d_secure: 'device' } : {}),
|
|
1724
1826
|
...(eprotect ? { paypage_id: input.paypageId } : {}),
|
|
1725
1827
|
...(secureAcceptance ? { secure_acceptance_profile_id: secureAcceptanceProfileId } : {}),
|
|
1726
1828
|
...(flexContext ? { capture_context: flexContext.captureContext,
|
|
@@ -2099,9 +2201,10 @@ export class VaultClient {
|
|
|
2099
2201
|
// amount_required.
|
|
2100
2202
|
if (!hasAmount && rec && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
|
|
2101
2203
|
throw new AmountRequiredError(rec.psp);
|
|
2102
|
-
// A Moneris token is charged later for whatever the merchant's server decides,
|
|
2103
|
-
//
|
|
2104
|
-
|
|
2204
|
+
// A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay
|
|
2205
|
+
// charges the total its payment already holds, so a zero pair would show the cardholder no price
|
|
2206
|
+
// at all: it is refused like a missing one.
|
|
2207
|
+
if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP) && Number(input.amount) <= 0)
|
|
2105
2208
|
throw new AmountRequiredError(rec.psp);
|
|
2106
2209
|
// Nor is the agent's pair tied to the page by anything but the total the
|
|
2107
2210
|
// page shows: without it the cardholder could approve one figure while the
|
|
@@ -2420,10 +2523,14 @@ export class VaultClient {
|
|
|
2420
2523
|
// the page's reviewed getCvvId keys (no keys, no continuation);
|
|
2421
2524
|
// every other processor names a holder path, as before.
|
|
2422
2525
|
const shiji = rec?.psp === SHIJI_STS_PSP;
|
|
2526
|
+
// Paddle TokenEx carries the two ciphertexts at tokenex_card and the five facts beside them in
|
|
2527
|
+
// `paddle_tokenex_facts` (the expiry parts are numbers, so they cannot ride `fields`).
|
|
2528
|
+
const paddleTokenex = rec?.psp === PADDLE_TOKENEX_PSP;
|
|
2423
2529
|
const fieldsOk = shiji ? isShijiStsServedSubstitution(sub) && isShijiStsServedCompanion(s.shiji_sts_companion)
|
|
2424
|
-
:
|
|
2425
|
-
&& sub.
|
|
2426
|
-
|
|
2530
|
+
: paddleTokenex ? isPaddleTokenexServedSubstitution(sub) && isPaddleTokenexServedFacts(s.paddle_tokenex_facts)
|
|
2531
|
+
: sub && typeof sub === 'object' && sub.encoding === 'json' && typeof sub.at === 'string' && sub.at
|
|
2532
|
+
&& sub.fields && typeof sub.fields === 'object' && !Array.isArray(sub.fields)
|
|
2533
|
+
&& Object.values(sub.fields).every((v) => typeof v === 'string' && v.length > 0);
|
|
2427
2534
|
if (!fieldsOk)
|
|
2428
2535
|
throw new PaymentOutcomeUnknownError(authorizationId, 'cse_substitutions_malformed');
|
|
2429
2536
|
// A Fiserv approval carries exactly its envelope's four members at
|
|
@@ -2448,6 +2555,7 @@ export class VaultClient {
|
|
|
2448
2555
|
...(removeRaw ? { remove: [...removeRaw] } : {}),
|
|
2449
2556
|
},
|
|
2450
2557
|
...(shiji ? { shijiStsCompanion: Object.freeze({ keySequenceSha256: Object.freeze([...s.shiji_sts_companion.key_sequence_sha256]) }) } : {}),
|
|
2558
|
+
...(paddleTokenex ? { paddleTokenexFacts: Object.freeze({ ...s.paddle_tokenex_facts }) } : {}),
|
|
2451
2559
|
...amountAuthority,
|
|
2452
2560
|
...execution,
|
|
2453
2561
|
};
|
|
@@ -2641,6 +2749,9 @@ export class VaultClient {
|
|
|
2641
2749
|
chargedAmount: typeof s.charged_amount === 'number' ? s.charged_amount : null,
|
|
2642
2750
|
chargedCurrency: typeof s.charged_currency === 'string' ? s.charged_currency : null,
|
|
2643
2751
|
chargedKind: s.charged_kind === 'captured' || s.charged_kind === 'authorized' || s.charged_kind === 'none' ? s.charged_kind : null,
|
|
2752
|
+
// An EBANX token approval that covers the sends after it names until when (ebanx.ts EbanxFollowUps).
|
|
2753
|
+
...(rec?.psp === 'ebanx' && typeof s.follow_ups?.until === 'string' && Number.isFinite(Date.parse(s.follow_ups.until))
|
|
2754
|
+
? { followUpsUntil: s.follow_ups.until } : {}),
|
|
2644
2755
|
...amountAuthority,
|
|
2645
2756
|
...execution,
|
|
2646
2757
|
};
|
|
@@ -2838,6 +2949,79 @@ export class VaultClient {
|
|
|
2838
2949
|
}
|
|
2839
2950
|
return { setupIntentId: confirmed };
|
|
2840
2951
|
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Send one request an approved EBANX token request covers (ebanx.ts says which: EBANX's 3-D Secure calls and the
|
|
2954
|
+
* hosted page's confirm, bound to that approval) from the cardholder's device, and resolve with EBANX's answer for
|
|
2955
|
+
* the paused request: POST /v2/checkout/authorizations/:id/follow_ups with the request as the page sent it, the
|
|
2956
|
+
* agent's placeholder card in it, then the send read back every half second until the device answered
|
|
2957
|
+
* (EBANX_FOLLOW_UP_READ_MS, whatever `pollIntervalMs` says, so the reads stay inside the API's per-approval budget).
|
|
2958
|
+
* A read the API refuses as too many (429) waits longer each time, doubling from a second up to
|
|
2959
|
+
* EBANX_FOLLOW_UP_BACKOFF_MAX_MS, inside the same deadline, and the next answered read goes back to the half second.
|
|
2960
|
+
* No prompt: the cardholder approved the token request, and the Vault page that sent it sends these while it stays
|
|
2961
|
+
* open. Rejects with an EbanxFollowUpError naming why there is no answer and whether the card may have reached
|
|
2962
|
+
* EBANX: the API refused the send (nothing sent), no device started it in time, the device refused it or withheld
|
|
2963
|
+
* an answer that carried the card, or `options.until` (the approval's window, plus the API's own clocks) passed.
|
|
2964
|
+
*/
|
|
2965
|
+
async sendEbanxFollowUp(authorizationId, request, options = {}) {
|
|
2966
|
+
if (!/^cauth_[A-Za-z0-9_-]{1,128}$/.test(authorizationId))
|
|
2967
|
+
throw new Error('Invalid authorization ID.');
|
|
2968
|
+
const base = `/v2/checkout/authorizations/${authorizationId}/follow_ups`;
|
|
2969
|
+
// The API's own clocks: a minute for a device to start a send and a minute for its answer.
|
|
2970
|
+
const deadline = Math.min(Date.now() + 3 * 60_000, (options.until ?? Number.POSITIVE_INFINITY) + 2 * 60_000);
|
|
2971
|
+
const signal = options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(Math.max(1, deadline - Date.now()))]) : AbortSignal.timeout(Math.max(1, deadline - Date.now()));
|
|
2972
|
+
let created;
|
|
2973
|
+
try {
|
|
2974
|
+
created = await this.post(base, { request: { url: request.url, method: 'POST', headers: pickHeaders(request.headers, []), body: request.body } }, AbortSignal.any([signal, AbortSignal.timeout(15_000)]));
|
|
2975
|
+
}
|
|
2976
|
+
catch (error) {
|
|
2977
|
+
// A refusal (a 4xx answer) stored no send, so nothing left. Any other failure (a 5xx, a timeout, a dropped
|
|
2978
|
+
// connection, an unreadable 201) may have come after the API stored the send, which the device then sends:
|
|
2979
|
+
// the card may reach EBANX, and for a confirm that is a payment whose outcome is unknown.
|
|
2980
|
+
const refused = error instanceof CheckoutApiError && error.status >= 400 && error.status < 500;
|
|
2981
|
+
const reason = error instanceof CheckoutApiError
|
|
2982
|
+
? (typeof error.details.reason === 'string' && /^[a-z_]{1,64}$/.test(error.details.reason) ? error.details.reason : error.code ?? `http_${error.status}`)
|
|
2983
|
+
: 'follow_up_unavailable';
|
|
2984
|
+
throw new EbanxFollowUpError(reason, !refused, authorizationId);
|
|
2985
|
+
}
|
|
2986
|
+
const id = created?.id;
|
|
2987
|
+
if (typeof id !== 'string' || !/^cfu_[0-9a-f]{24}$/.test(id) || created.authorization !== authorizationId) {
|
|
2988
|
+
throw new EbanxFollowUpError('follow_up_unrecognized', true, authorizationId);
|
|
2989
|
+
}
|
|
2990
|
+
// Reads the API refused in a row as too many (429): each waits twice as long as the last before the next read.
|
|
2991
|
+
let limited = 0;
|
|
2992
|
+
for (;;) {
|
|
2993
|
+
let read = null;
|
|
2994
|
+
try {
|
|
2995
|
+
read = await this.get(`${base}/${id}`, AbortSignal.any([signal, AbortSignal.timeout(15_000)]));
|
|
2996
|
+
limited = 0;
|
|
2997
|
+
}
|
|
2998
|
+
catch (error) {
|
|
2999
|
+
if (signal.aborted)
|
|
3000
|
+
throw new EbanxFollowUpError('answer_late', true, authorizationId);
|
|
3001
|
+
if (error instanceof CheckoutApiError && error.status === 429)
|
|
3002
|
+
limited += 1;
|
|
3003
|
+
}
|
|
3004
|
+
if (read?.status === 'answered') {
|
|
3005
|
+
const r = read.response;
|
|
3006
|
+
if (!r || !Number.isInteger(r.status) || r.status < 200 || r.status > 599 || typeof r.body !== 'string' || !r.headers || typeof r.headers !== 'object') {
|
|
3007
|
+
throw new EbanxFollowUpError('follow_up_unrecognized', true, authorizationId);
|
|
3008
|
+
}
|
|
3009
|
+
const headers = {};
|
|
3010
|
+
for (const [name, value] of Object.entries(r.headers))
|
|
3011
|
+
if (name.toLowerCase() === 'content-type' && typeof value === 'string')
|
|
3012
|
+
headers['content-type'] = value;
|
|
3013
|
+
return { status: r.status, headers: headers['content-type'] ? headers : { 'content-type': 'application/json' }, body: r.body };
|
|
3014
|
+
}
|
|
3015
|
+
if (read?.status === 'failed' || read?.status === 'expired') {
|
|
3016
|
+
const reason = typeof read.failure_reason === 'string' && /^[a-z_]{1,64}$/.test(read.failure_reason) ? read.failure_reason : read.status;
|
|
3017
|
+
throw new EbanxFollowUpError(reason, read.card_sent !== false, authorizationId);
|
|
3018
|
+
}
|
|
3019
|
+
if (signal.aborted || Date.now() >= deadline)
|
|
3020
|
+
throw new EbanxFollowUpError('answer_late', true, authorizationId);
|
|
3021
|
+
const wait = limited === 0 ? EBANX_FOLLOW_UP_READ_MS : Math.min(EBANX_FOLLOW_UP_BACKOFF_MAX_MS, 1000 * 2 ** (limited - 1));
|
|
3022
|
+
await interruptibleSleep(Math.max(1, Math.min(wait, deadline - Date.now())), signal);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
2841
3025
|
/**
|
|
2842
3026
|
* Create a merchant-session probe, the phase-0 read of the deterministic merchant total:
|
|
2843
3027
|
* `user` is the org's connected user, who opens the returned Vault link and reads the
|
|
@@ -51,11 +51,13 @@ declare function parseCreditguardTransaction(body: any, txId: any): {
|
|
|
51
51
|
dccActive: boolean;
|
|
52
52
|
recaptcha: boolean;
|
|
53
53
|
jwePublicKey: string;
|
|
54
|
+
jwePublicKeyJson: any;
|
|
54
55
|
personalIdAlwaysEnabled: boolean;
|
|
55
56
|
inputs: any;
|
|
56
57
|
} | 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" | "
|
|
58
|
+
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" | "three_d_secure" | "transaction_unreadable" | "form_inputs" | "card_field_hidden" | "jwe_public_key_malformed" | null;
|
|
58
59
|
declare function creditguardSendWindowRefusal(timing: any): "timing_unknown" | "page_expired" | "recaptcha_expired" | null;
|
|
60
|
+
declare function creditguardBodyTransactionRefusal(body: any, txn: any): "jwe_mismatch" | null;
|
|
59
61
|
declare var CREDITGUARD_REFUSAL_CODES: readonly string[];
|
|
60
62
|
declare function creditguardResponseOutcome(status: any, body: any, options: any): {
|
|
61
63
|
outcome: string;
|
|
@@ -76,4 +78,100 @@ declare function creditguardFacts(status: any, body: any): {
|
|
|
76
78
|
errorCode: any;
|
|
77
79
|
refusalCode: any;
|
|
78
80
|
};
|
|
79
|
-
|
|
81
|
+
declare var CREDITGUARD_COMPANION_PATHS: Readonly<{
|
|
82
|
+
card_details: "/card/details";
|
|
83
|
+
ddc_init: "/cc/ddc/init";
|
|
84
|
+
threeds_init: "/cg3ds/process/init";
|
|
85
|
+
threeds_outcome: "/cg3ds/process/outcome";
|
|
86
|
+
}>;
|
|
87
|
+
declare var CREDITGUARD_DEVICE_STEPS: readonly string[];
|
|
88
|
+
declare var CREDITGUARD_CARDINAL_HOSTS: Readonly<{
|
|
89
|
+
production: readonly string[];
|
|
90
|
+
sandbox: readonly string[];
|
|
91
|
+
}>;
|
|
92
|
+
declare var CREDITGUARD_CARDINAL_COLLECT_URLS: Readonly<{
|
|
93
|
+
production: "https://client.cardinaltrusted.com/centinelapi/V2/Cruise/Collect";
|
|
94
|
+
sandbox: "https://cas.client.cardinaltrusted.com/centinelapi/V2/Cruise/Collect";
|
|
95
|
+
}>;
|
|
96
|
+
declare function creditguardCompanionStep(url: any): {
|
|
97
|
+
step: string;
|
|
98
|
+
txId: string;
|
|
99
|
+
} | null;
|
|
100
|
+
declare function creditguardDeviceStepUrl(processUrl: any, step: any): string | null;
|
|
101
|
+
declare function creditguardCompanionCall(url: any, method: any, body: any): {
|
|
102
|
+
step: null;
|
|
103
|
+
txId: null;
|
|
104
|
+
refusal: string;
|
|
105
|
+
} | {
|
|
106
|
+
refusal: string | null;
|
|
107
|
+
step: string;
|
|
108
|
+
txId: string;
|
|
109
|
+
} | null;
|
|
110
|
+
declare function creditguardCompanionAnswer(step: any): {
|
|
111
|
+
status: number;
|
|
112
|
+
headers: {
|
|
113
|
+
"content-type": string;
|
|
114
|
+
};
|
|
115
|
+
body: any;
|
|
116
|
+
} | null;
|
|
117
|
+
declare function creditguardDeviceDdcRequest(processUrl: any, card: any, options?: {}): Promise<{
|
|
118
|
+
url: string;
|
|
119
|
+
method: string;
|
|
120
|
+
headers: {
|
|
121
|
+
"content-type": "application/json";
|
|
122
|
+
accept: "application/json";
|
|
123
|
+
};
|
|
124
|
+
body: string;
|
|
125
|
+
}>;
|
|
126
|
+
declare function creditguardDeviceThreeDSInitRequest(processUrl: any, card: any, options?: {}): {
|
|
127
|
+
url: string;
|
|
128
|
+
method: string;
|
|
129
|
+
headers: {
|
|
130
|
+
"content-type": "application/json";
|
|
131
|
+
accept: "application/json";
|
|
132
|
+
};
|
|
133
|
+
body: string;
|
|
134
|
+
};
|
|
135
|
+
declare function creditguardDeviceThreeDSOutcomeRequest(processUrl: any): {
|
|
136
|
+
url: string;
|
|
137
|
+
method: string;
|
|
138
|
+
headers: {
|
|
139
|
+
"content-type": "application/json";
|
|
140
|
+
accept: "application/json";
|
|
141
|
+
};
|
|
142
|
+
body: string;
|
|
143
|
+
};
|
|
144
|
+
declare function creditguardDeviceStepRefusal(request: any, processUrl: any): "headers" | "malformed_body" | "wallet" | "unexpected_field" | "gift_card" | "dcc" | "txid_mismatch" | "card_fields" | "not_companion_request" | null;
|
|
145
|
+
declare function creditguardCardinalUrl(url: any, purpose: any, environment: any): string | null;
|
|
146
|
+
declare function isCreditguardCardinalOrigin(origin: any): boolean;
|
|
147
|
+
declare function creditguardCardinalMessage(data: any): {
|
|
148
|
+
sessionId: any;
|
|
149
|
+
} | null;
|
|
150
|
+
declare function creditguardDdcAnswer(status: any, body: any, environment: any): {
|
|
151
|
+
collectUrl: any;
|
|
152
|
+
ddcJwt: any;
|
|
153
|
+
} | null;
|
|
154
|
+
declare function creditguardThreeDSInitAnswer(status: any, body: any, environment: any): {
|
|
155
|
+
outcome: string;
|
|
156
|
+
reason: string;
|
|
157
|
+
stepUpUrl?: undefined;
|
|
158
|
+
stepUpJwt?: undefined;
|
|
159
|
+
} | {
|
|
160
|
+
outcome: string;
|
|
161
|
+
reason?: undefined;
|
|
162
|
+
stepUpUrl?: undefined;
|
|
163
|
+
stepUpJwt?: undefined;
|
|
164
|
+
} | {
|
|
165
|
+
outcome: string;
|
|
166
|
+
stepUpUrl: string;
|
|
167
|
+
stepUpJwt: any;
|
|
168
|
+
reason?: undefined;
|
|
169
|
+
};
|
|
170
|
+
declare function creditguardThreeDSOutcomeAnswer(status: any, body: any): {
|
|
171
|
+
outcome: string;
|
|
172
|
+
reason: string;
|
|
173
|
+
} | {
|
|
174
|
+
outcome: string;
|
|
175
|
+
reason?: undefined;
|
|
176
|
+
};
|
|
177
|
+
export { CREDITGUARD_CARDINAL_COLLECT_URLS, CREDITGUARD_CARDINAL_HOSTS, CREDITGUARD_COMPANION_PATHS, CREDITGUARD_DEVICE_STEPS, CREDITGUARD_JWE_HEADER, CREDITGUARD_OPERATION, CREDITGUARD_PAGE_WINDOW_MS, CREDITGUARD_PPS_HOSTS, CREDITGUARD_RECAPTCHA_WINDOW_MS, CREDITGUARD_REFUSAL_CODES, CREDITGUARD_SANDBOX_HOST, classifyCreditguardRequest, creditguardBodyRefusal, creditguardBodyTransactionRefusal, creditguardCardinalMessage, creditguardCardinalUrl, creditguardCompanionAnswer, creditguardCompanionCall, creditguardCompanionStep, creditguardDdcAnswer, creditguardDeviceDdcRequest, creditguardDeviceStepRefusal, creditguardDeviceStepUrl, creditguardDeviceThreeDSInitRequest, creditguardDeviceThreeDSOutcomeRequest, creditguardFacts, creditguardNuveiRefusal, creditguardPageProcessUrl, creditguardPpDomainHost, creditguardPreparationEnvironment, creditguardProcessRefusal, creditguardProcessTxId, creditguardRefusalMessage, creditguardReplayRefusal, creditguardResponseOutcome, creditguardSendWindowRefusal, creditguardThreeDSInitAnswer, creditguardThreeDSOutcomeAnswer, creditguardTransactionReadUrl, creditguardTransactionRefusal, encryptCreditguardJwe, injectCreditguardCard, injectCreditguardJweCard, isCreditguardCardinalOrigin, isCreditguardJweCompact, isCreditguardPpsDocument, isCreditguardProcessRequest, isCreditguardProcessUrl, parseCreditguardJwePublicKey, parseCreditguardTransaction, readCreditguardJson, sanitizeCreditguardResponse };
|