@agent-cards/sdk 0.23.18 → 0.23.20
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 +30 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +46 -7
- package/dist/aci.d.ts +0 -7
- package/dist/aci.js +0 -19
- package/dist/amadeus.d.ts +4 -2
- package/dist/amadeus.js +16 -3
- package/dist/buy.d.ts +84 -2
- package/dist/buy.js +16 -1
- package/dist/card-endpoint-facts.d.ts +2 -1
- package/dist/card-endpoint-facts.js +18 -9
- package/dist/card-endpoint.d.ts +20 -4
- package/dist/card-endpoint.js +38 -4
- package/dist/card-fields.generated.js +974 -45
- package/dist/cdp.d.ts +29 -0
- package/dist/cdp.js +302 -18
- package/dist/client.d.ts +43 -5
- package/dist/client.js +237 -43
- package/dist/datatrans.d.ts +2 -2
- package/dist/datatrans.generated.js +16 -11
- package/dist/datatrans.js +2 -2
- package/dist/hipay.generated.d.ts +56 -0
- package/dist/hipay.generated.js +389 -0
- package/dist/hold.d.ts +180 -0
- package/dist/hold.js +264 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.js +21 -0
- package/dist/merchant-card-endpoints.generated.js +102 -11
- package/dist/paddle-vgs.generated.d.ts +3 -2
- package/dist/paddle-vgs.generated.js +9 -5
- package/dist/pelecard.generated.d.ts +2 -1
- package/dist/pelecard.generated.js +14 -5
- package/dist/pre-claim.d.ts +14 -0
- package/dist/pre-claim.js +76 -3
- package/dist/prepared-processor.d.ts +6 -2
- package/dist/prepared-processor.js +18 -2
- package/dist/primer.generated.js +998 -69
- package/dist/quickpay.d.ts +44 -0
- package/dist/quickpay.js +65 -0
- package/dist/stripe-checkout.generated.js +320 -2
- package/dist/twoc2p.d.ts +54 -0
- package/dist/twoc2p.generated.d.ts +33 -0
- package/dist/twoc2p.generated.js +1145 -0
- package/dist/twoc2p.js +85 -0
- package/dist/voice.d.ts +16 -13
- package/dist/voice.js +13 -10
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -257,6 +257,28 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
|
|
|
257
257
|
* that the page then runs in the agent's browser. The cardholder's device sends the card through
|
|
258
258
|
* the checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
259
259
|
* Agentcard turned PayMongo on for.
|
|
260
|
+
*
|
|
261
|
+
* `hipay_vault_token`: the token create HiPay's hosted fields send to HiPay's Secure Vault
|
|
262
|
+
* (secure2-vault.hipay-tpp.com, stage-secure2-vault for a test row), only through a prepared
|
|
263
|
+
* checkout: the merchant's page gives the frame 20 seconds after Pay. The cardholder's device
|
|
264
|
+
* sends the card to HiPay directly and the frame gets back HiPay's token. The number-only post
|
|
265
|
+
* the frame sends while the number is typed, and its /update once it holds a token, fail in the
|
|
266
|
+
* browser (pre-claim.ts hipayPreClaim). Served on the same terms: only to a build that lists it,
|
|
267
|
+
* for a company Agentcard turned HiPay on for.
|
|
268
|
+
*
|
|
269
|
+
* `twoc2p_secure_pay`: the Payment 2C2P's hosted payment page posts to its payment API
|
|
270
|
+
* (pgwv4.2c2p.com), carrying the card only as one field the page encrypts under 2C2P's static
|
|
271
|
+
* key. This build pauses it and writes over the placeholder's field the one the cardholder's
|
|
272
|
+
* device wrote under the key payment-core pins (twoc2p.ts), into that Payment alone; 3-D Secure
|
|
273
|
+
* then runs in the agent's browser. Served only to a build that lists it, and only for Agentcard's
|
|
274
|
+
* proof company, whatever the API's processor lists name: the field is the card under 2C2P's one
|
|
275
|
+
* static key, which any 2C2P merchant could charge.
|
|
276
|
+
*
|
|
277
|
+
* `quickpay_hosted_payment_page`: QuickPay's hosted payment window, whose card-free signed entry
|
|
278
|
+
* this build pauses for the cardholder's device and whose card post, fee lookup, wallet post, 3-D
|
|
279
|
+
* Secure step and card lookup this build never lets the agent's browser send (pre-claim.ts,
|
|
280
|
+
* quickpay.ts). Served on the same terms as NMI's and Redsys's: only to a build that lists it, for a
|
|
281
|
+
* company Agentcard turned QuickPay on for.
|
|
260
282
|
*/
|
|
261
283
|
export declare const SUPPORTED_CAPABILITIES: readonly string[];
|
|
262
284
|
/**
|
|
@@ -1187,8 +1209,24 @@ export declare class CheckoutAuthError extends Error {
|
|
|
1187
1209
|
* On komoju.com a session id reads `{session}`: https://komoju.com/api/v1/sessions/{session}/pay.
|
|
1188
1210
|
* On Przelewy24's hosts the transaction's token reads `{token}`:
|
|
1189
1211
|
* https://secure.przelewy24.pl/trnRequest/{token}.
|
|
1212
|
+
* On Revolut's card payment and hosted checkout hosts an order or payment token reads `{token}`:
|
|
1213
|
+
* https://merchant.revolut.com/api/public/orders/{token}/payments.
|
|
1214
|
+
* On SumUp's API host a checkout id reads `{checkout}`: https://api.sumup.com/v0.2/checkouts/{checkout}.
|
|
1215
|
+
* On Paytrail's hosts a payment's id reads `{transaction}`:
|
|
1216
|
+
* https://services.paytrail.com/payments/{transaction}/click-to-pay/loading-and-redirect, and on Payment
|
|
1217
|
+
* Highway's a card page's session reads `{session}`.
|
|
1218
|
+
* On 2C2P's page hosts everything after the build's folder reads `{token}`:
|
|
1219
|
+
* https://pgw-ui.2c2p.com/payment/4.1/{token}.
|
|
1220
|
+
* On QuickPay's host the window's session reads `{session}`:
|
|
1221
|
+
* https://payment.quickpay.net/{session}/3ds_auth.
|
|
1190
1222
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1191
|
-
* same segments masked.
|
|
1223
|
+
* same segments masked for every processor whose host it names.
|
|
1224
|
+
* A URL is read by its host only when the parser finds one. Text before a URL
|
|
1225
|
+
* can read as a scheme: `see:https://api.stripe.com/...` parses as a URL with
|
|
1226
|
+
* the scheme `see`, no host, and the whole Stripe URL as its path, so a parse
|
|
1227
|
+
* with no host is read as a string that is not a URL. A path can carry a URL of
|
|
1228
|
+
* its own, as `http:https://api.stripe.com/...` does once `https` reads as its
|
|
1229
|
+
* host, so every processor whose host the path names masks the URL too.
|
|
1192
1230
|
*/
|
|
1193
1231
|
export declare function redactUrl(raw: string): string;
|
|
1194
1232
|
export interface VaultClientOptions {
|
|
@@ -1231,8 +1269,8 @@ export interface VaultClientOptions {
|
|
|
1231
1269
|
/**
|
|
1232
1270
|
* The voice module: how this product approves purchases by voice, named
|
|
1233
1271
|
* once per business, with the Vault return link and the verifier address
|
|
1234
|
-
* beside it. Leave it out
|
|
1235
|
-
* `
|
|
1272
|
+
* beside it. Leave it out and the client approves on Agentcard's page
|
|
1273
|
+
* (`approval: 'sheet'`). See `VoiceOptions`.
|
|
1236
1274
|
*/
|
|
1237
1275
|
voice?: VoiceOptions;
|
|
1238
1276
|
/**
|
|
@@ -1297,8 +1335,8 @@ export declare class VaultClient {
|
|
|
1297
1335
|
/**
|
|
1298
1336
|
* Approve purchases by voice inside your own product: connect a person once,
|
|
1299
1337
|
* create the purchase, ask for the key word, read the result. Same
|
|
1300
|
-
* credentials, same token as the checkout calls.
|
|
1301
|
-
*
|
|
1338
|
+
* credentials, same token as the checkout calls. Approves on Agentcard's
|
|
1339
|
+
* page unless `voice: { approval: 'in_app' }` was named at construction.
|
|
1302
1340
|
*/
|
|
1303
1341
|
readonly voice: VoiceModule;
|
|
1304
1342
|
/**
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BUILTIN_REGISTRY, cardUrlPatterns as deriveCardUrlPatterns, LOOPBACK_URL_PATTERNS, findRecognizer, isRecognizerRequest, recognizerRequestMethod, recognizerRequiresPreparation, urlsOnProcessorHosts, } from './registry.js';
|
|
2
2
|
import { armServedProfiles, classifyProfileRequest, declareSandboxMerchant, merchantProfileEnvironment, merchantProfileFor, merchantProfileUrlPatterns, } from './merchant-hosted.js';
|
|
3
3
|
import { merchantAmountExchanges, merchantAmountFromResponses, merchantProfilePricedBySource, merchantTotalWire, MerchantTotalWait, } from './merchant-total.js';
|
|
4
|
-
import { ACI_CAPABILITY, ACI_PSP, aciCardLegAnswer
|
|
4
|
+
import { ACI_CAPABILITY, ACI_PSP, aciCardLegAnswer } from './aci.js';
|
|
5
5
|
import { EPROTECT_CAPABILITY, EPROTECT_PSP, eprotectCheckoutRefusal, eprotectScriptAnswer, isEprotectRegistration } from './eprotect.js';
|
|
6
6
|
import { isMercadoTokenRequest, parseMercadoCheckoutContext, validateMercadoProcessorContext } from './mercado-checkout.generated.js';
|
|
7
7
|
import { matchesPreparation, preparationMode, validPreparationEnvironment } from './prepared-processor.js';
|
|
@@ -16,7 +16,9 @@ import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShiji
|
|
|
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
18
|
import { PADDLE_TOKENEX_CAPABILITY, PADDLE_TOKENEX_PSP, isPaddleTokenexServedFacts, isPaddleTokenexServedSubstitution } from './paddle-tokenex.js';
|
|
19
|
+
import { TWOC2P_CAPABILITY, TWOC2P_PSP } from './twoc2p.js';
|
|
19
20
|
import { WIX_PAYMENTS_CAPABILITY, WIX_PAYMENTS_PSP } from './wix-payments.generated.js';
|
|
21
|
+
import { HIPAY_CAPABILITY, HIPAY_PSP } from './hipay.generated.js';
|
|
20
22
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
21
23
|
import { VoiceModule } from './voice.js';
|
|
22
24
|
import { BuyModule } from './buy.js';
|
|
@@ -241,9 +243,31 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
241
243
|
* that the page then runs in the agent's browser. The cardholder's device sends the card through
|
|
242
244
|
* the checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
243
245
|
* Agentcard turned PayMongo on for.
|
|
246
|
+
*
|
|
247
|
+
* `hipay_vault_token`: the token create HiPay's hosted fields send to HiPay's Secure Vault
|
|
248
|
+
* (secure2-vault.hipay-tpp.com, stage-secure2-vault for a test row), only through a prepared
|
|
249
|
+
* checkout: the merchant's page gives the frame 20 seconds after Pay. The cardholder's device
|
|
250
|
+
* sends the card to HiPay directly and the frame gets back HiPay's token. The number-only post
|
|
251
|
+
* the frame sends while the number is typed, and its /update once it holds a token, fail in the
|
|
252
|
+
* browser (pre-claim.ts hipayPreClaim). Served on the same terms: only to a build that lists it,
|
|
253
|
+
* for a company Agentcard turned HiPay on for.
|
|
254
|
+
*
|
|
255
|
+
* `twoc2p_secure_pay`: the Payment 2C2P's hosted payment page posts to its payment API
|
|
256
|
+
* (pgwv4.2c2p.com), carrying the card only as one field the page encrypts under 2C2P's static
|
|
257
|
+
* key. This build pauses it and writes over the placeholder's field the one the cardholder's
|
|
258
|
+
* device wrote under the key payment-core pins (twoc2p.ts), into that Payment alone; 3-D Secure
|
|
259
|
+
* then runs in the agent's browser. Served only to a build that lists it, and only for Agentcard's
|
|
260
|
+
* proof company, whatever the API's processor lists name: the field is the card under 2C2P's one
|
|
261
|
+
* static key, which any 2C2P merchant could charge.
|
|
262
|
+
*
|
|
263
|
+
* `quickpay_hosted_payment_page`: QuickPay's hosted payment window, whose card-free signed entry
|
|
264
|
+
* this build pauses for the cardholder's device and whose card post, fee lookup, wallet post, 3-D
|
|
265
|
+
* Secure step and card lookup this build never lets the agent's browser send (pre-claim.ts,
|
|
266
|
+
* quickpay.ts). Served on the same terms as NMI's and Redsys's: only to a build that lists it, for a
|
|
267
|
+
* company Agentcard turned QuickPay on for.
|
|
244
268
|
*/
|
|
245
269
|
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,
|
|
246
|
-
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay'];
|
|
270
|
+
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay', HIPAY_CAPABILITY, TWOC2P_CAPABILITY, 'quickpay_hosted_payment_page'];
|
|
247
271
|
/**
|
|
248
272
|
* Processors whose paused request names no amount at all, so the pair the
|
|
249
273
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -275,15 +299,20 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
|
|
|
275
299
|
* PayMongo's card attach carries none either: the merchant bound the total, always in
|
|
276
300
|
* Philippine pesos, to the checkout session when it created it, so the agent sends that
|
|
277
301
|
* total, above zero (PAYMONGO_PSP below), and the API holds it to the session PayMongo's
|
|
278
|
-
* own page shows.
|
|
302
|
+
* own page shows. 2C2P's Payment carries none either: the merchant bound the total to the
|
|
303
|
+
* payment token when it created it, so the agent sends the total 2C2P's page shows, above
|
|
304
|
+
* zero (TWOC2P_PSP), and the API holds it to the amount 2C2P's own PaymentOption answer names.
|
|
305
|
+
* HiPay's token create carries none either: the merchant's server charges
|
|
306
|
+
* HiPay's token for its own order, so the agent's pair, which the prepared checkout carries,
|
|
307
|
+
* is the total the cardholder approves, above zero as Moneris's is.
|
|
279
308
|
*/
|
|
280
309
|
const MONERIS_PSP = 'moneris';
|
|
281
310
|
const PAYFAST_PSP = 'payfast';
|
|
282
311
|
const PAYMONGO_PSP = 'paymongo';
|
|
283
|
-
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP, WIX_PAYMENTS_PSP, PAYMONGO_PSP];
|
|
312
|
+
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP, WIX_PAYMENTS_PSP, PAYMONGO_PSP, HIPAY_PSP, TWOC2P_PSP];
|
|
284
313
|
/** How AmountRequiredError names a processor. */
|
|
285
314
|
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
|
|
286
|
-
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo' };
|
|
315
|
+
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo', [HIPAY_PSP]: 'HiPay', [TWOC2P_PSP]: '2C2P' };
|
|
287
316
|
/**
|
|
288
317
|
* Processors whose request and answer name no amount at all, so the total the
|
|
289
318
|
* payment page shows (`pageAmount`) is the one figure that ties the agent's pair
|
|
@@ -1041,9 +1070,12 @@ function maskCreditguardTxId(path) {
|
|
|
1041
1070
|
* /v2/payment/{client_secret}/confirm (and /show, /status), and with the page's
|
|
1042
1071
|
* public token that secret confirms the payment and reads its status.
|
|
1043
1072
|
*/
|
|
1044
|
-
const
|
|
1073
|
+
const PATH_SECRET_HOST = /^(?:proxy(?:-test)?\.wspay\.biz|ipg(?:test)?\.monri\.com)\.?$/i;
|
|
1045
1074
|
/** The segment after a `payment` segment, in any case (the WSPay proxy is IIS) and any spelling of its slashes. */
|
|
1046
1075
|
const PATH_SECRET = new RegExp(`(${SLASH}payment${SLASH}+)[^/]*`, 'gi');
|
|
1076
|
+
function maskPathSecret(path) {
|
|
1077
|
+
return path.replace(PATH_SECRET, '$1{client_secret}');
|
|
1078
|
+
}
|
|
1047
1079
|
/**
|
|
1048
1080
|
* Gravy (Gr4vy) gives every merchant hosts of its own under gr4vy.app, and Secure Fields sends the
|
|
1049
1081
|
* card to /checkout/sessions/<session id>/fields with no auth header, so the session id is that
|
|
@@ -1084,10 +1116,11 @@ function maskStripeSession(path) {
|
|
|
1084
1116
|
return path.replace(STRIPE_PAYMENT_PAGE, '$1{session}');
|
|
1085
1117
|
}
|
|
1086
1118
|
/**
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
1090
|
-
* ACI's shape
|
|
1119
|
+
* ACI's checkout id is that checkout's only capability: once the card-number request carried the real
|
|
1120
|
+
* card, anyone holding it can post /payment against that card for the rest of the checkout's 30
|
|
1121
|
+
* minutes. On ACI's hosts, with a trailing dot, an escaped or doubled slash, in another case or in a
|
|
1122
|
+
* string that is not a URL, the segment after /v1/checkouts/ and any id of ACI's shape
|
|
1123
|
+
* (<32 hex>.<cluster>-vm-tx<nn>) read {checkoutId}.
|
|
1091
1124
|
*/
|
|
1092
1125
|
const ACI_HOST = /^(?:eu-prod\.|eu-test\.|test\.)?oppwa\.com\.?$/i;
|
|
1093
1126
|
const ACI_CHECKOUT = new RegExp(`(${SLASH}v1${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
|
|
@@ -1162,21 +1195,36 @@ function maskGopayToken(path) {
|
|
|
1162
1195
|
* the merchant's publishable key, which is public (payment-core komoju.js): with the session id, anyone
|
|
1163
1196
|
* can read that session's amount and buyer email and pay it. On komoju.com the segment after /sessions,
|
|
1164
1197
|
* in any case and after any run of slashes, each written plain or escaped once or more, reads {session}:
|
|
1165
|
-
* https://komoju.com/api/v1/sessions/{session}/pay. Then
|
|
1166
|
-
* {session}
|
|
1167
|
-
* masked as well;
|
|
1198
|
+
* https://komoju.com/api/v1/sessions/{session}/pay. Then every run of 25 or more letters and digits left
|
|
1199
|
+
* reads {session}, so an id after a dot segment, a backslash or an escape KOMOJU's frame never writes is
|
|
1200
|
+
* masked as well; a percent escape right before the run stays when 25 or more remain after it. No other
|
|
1201
|
+
* path KOMOJU's card fields use holds such a run. The whole run reads {session}, never only its last 25
|
|
1202
|
+
* characters, so a longer id of another processor in the same text is never cut in two before that
|
|
1203
|
+
* processor's mask reads it.
|
|
1168
1204
|
*/
|
|
1169
1205
|
const KOMOJU_HOST = /^komoju\.com\.?$/i;
|
|
1170
1206
|
const KOMOJU_SESSION = new RegExp(`(${SLASH}sessions)(${SLASH}+)[^/]*`, 'gi');
|
|
1171
|
-
const KOMOJU_SESSION_ID = /[
|
|
1207
|
+
const KOMOJU_SESSION_ID = /(%(?:25)*[0-9a-f]{2})?[a-z0-9]{25,}/gi;
|
|
1172
1208
|
function maskKomojuSession(path) {
|
|
1173
|
-
return path.replace(KOMOJU_SESSION, '$1$2{session}').replace(KOMOJU_SESSION_ID, '{session}
|
|
1209
|
+
return path.replace(KOMOJU_SESSION, '$1$2{session}').replace(KOMOJU_SESSION_ID, (_run, escape) => `${escape ?? ''}{session}`);
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* SumUp's Payment Widget sends the card to https://api.sumup.com/v0.2/checkouts/<checkout id> with no cookie and
|
|
1213
|
+
* no auth header (payment-core sumup.js), so the checkout id is that request's only credential: with it anyone can
|
|
1214
|
+
* read the checkout's amount and merchant and pay it. On api.sumup.com the segment after /checkouts/, in any case
|
|
1215
|
+
* and any spelling of the slashes before it, reads {checkout}, as does any other id shaped like SumUp's UUID
|
|
1216
|
+
* checkout ids, in every spelling maskUuids reads.
|
|
1217
|
+
*/
|
|
1218
|
+
const SUMUP_API_HOST = /^api\.sumup\.com\.?$/i;
|
|
1219
|
+
const SUMUP_CHECKOUT = new RegExp(`(${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
|
|
1220
|
+
function maskSumupCheckout(path) {
|
|
1221
|
+
return maskUuids(path.replace(SUMUP_CHECKOUT, '$1{checkout}'), '{checkout}');
|
|
1174
1222
|
}
|
|
1175
1223
|
/**
|
|
1176
1224
|
* The processors whose card request carries its own credential in the URL path, with the hosts
|
|
1177
|
-
* that send it, the text that names those hosts
|
|
1178
|
-
* guard in redact-url.test.mjs fails for a
|
|
1179
|
-
* this list or out of it.
|
|
1225
|
+
* that send it, the text that names those hosts anywhere else (a string that is not a URL, or a
|
|
1226
|
+
* path that carries a URL of its own), and the mask. The guard in redact-url.test.mjs fails for a
|
|
1227
|
+
* payment-core recognizer that no one has sorted into this list or out of it.
|
|
1180
1228
|
*/
|
|
1181
1229
|
/**
|
|
1182
1230
|
* Redsys's hosted payment page names its session in the path of every page and post after the entry
|
|
@@ -1212,7 +1260,107 @@ const PRZELEWY24_TOKEN = new RegExp(`[0-9a-f]{10}${HYPHEN}[0-9a-f]{6}${HYPHEN}[0
|
|
|
1212
1260
|
function maskPrzelewy24Token(path) {
|
|
1213
1261
|
return path.replace(PRZELEWY24_TOKEN, '{token}');
|
|
1214
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Revolut's card field and card popup send the card payment to /api/public/orders/<order token>/payments on
|
|
1265
|
+
* merchant.revolut.com (sandbox-merchant.revolut.com in test), and the frame polls /api/public/payments/<payment
|
|
1266
|
+
* token> (payment-core revolut.js). Neither carries a header or cookie that binds it: the order token alone
|
|
1267
|
+
* opens that order's checkout, its amount and its merchant, and pays it, and the payment token reads the payment.
|
|
1268
|
+
* Revolut's hosted checkout page names the same order token in its own paths (/payment-link/<order token>), and the
|
|
1269
|
+
* frame reads the order at merchant-mgmt.revolut.com/orders/token/<order token>. On those hosts every id of their
|
|
1270
|
+
* uuid shape, in every spelling maskUuids reads, reads {token}.
|
|
1271
|
+
*/
|
|
1272
|
+
const REVOLUT_HOST = /^(?:sandbox-)?(?:merchant|checkout|merchant-mgmt)\.revolut\.com\.?$/i;
|
|
1273
|
+
function maskRevolutToken(path) {
|
|
1274
|
+
return maskUuids(path, '{token}');
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Paytrail names its payment in the path of every route of its card method
|
|
1278
|
+
* (/payments/<transaction>/click-to-pay/loading-and-redirect, and its success and cancel routes), and the same id
|
|
1279
|
+
* opens Paytrail's own payment wall for that payment (pay.paytrail.com/pay/<transaction>), its amount, its
|
|
1280
|
+
* merchant and every method's form, to anyone who holds it (payment-core processors/paytrail.js). On every host
|
|
1281
|
+
* Paytrail owns, any id of its shape, with or without its hyphens, in any case, after an escaped slash or with its
|
|
1282
|
+
* hyphens escaped, reads {transaction}. Payment Highway, Paytrail's card platform, names its card page's session
|
|
1283
|
+
* the same way (/form/<session>/pay_with_click_to_pay), and that path is what carries a card post into the payment,
|
|
1284
|
+
* so on its hosts the same shape reads {session}. The pattern reads a percent escape as one unit and keeps it, so
|
|
1285
|
+
* the hex digits of an escaped separator (%2F, %252F, %3D) never start an id and leave part of it showing.
|
|
1286
|
+
*/
|
|
1287
|
+
const PAYTRAIL_HOST = /(?:^|\.)paytrail\.com\.?$/i;
|
|
1288
|
+
const PAYMENT_HIGHWAY_HOST = /(?:^|\.)(?:paymenthighway\.io|sph-test-solinor\.com)\.?$/i;
|
|
1289
|
+
const PAYTRAIL_ID_HYPHEN = '(?:-|%(?:25)*2d)?';
|
|
1290
|
+
const PAYTRAIL_ID = new RegExp(`%(?:25)*[0-9a-f]{2}|[0-9a-f]{8}${PAYTRAIL_ID_HYPHEN}[0-9a-f]{4}${PAYTRAIL_ID_HYPHEN}[0-9a-f]{4}${PAYTRAIL_ID_HYPHEN}[0-9a-f]{4}${PAYTRAIL_ID_HYPHEN}[0-9a-f]{12}`, 'gi');
|
|
1291
|
+
function maskPaytrailId(path, label) {
|
|
1292
|
+
return path.replace(PAYTRAIL_ID, (token) => (token.startsWith('%') ? token : label));
|
|
1293
|
+
}
|
|
1294
|
+
function maskPaytrailTransaction(path) {
|
|
1295
|
+
return maskPaytrailId(path, '{transaction}');
|
|
1296
|
+
}
|
|
1297
|
+
function maskPaymentHighwaySession(path) {
|
|
1298
|
+
return maskPaytrailId(path, '{session}');
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* 2C2P's hosted payment page names its payment token in its hash routes (pgw-ui.2c2p.com/payment/<build>/#/token/
|
|
1302
|
+
* <token>, #/info/<token>, #/mobile/process/<token>), and the token is all a browser needs to open that payment's page
|
|
1303
|
+
* (payment-core twoc2p.js). redactUrl drops a fragment, but a URL whose `#` was escaped carries the route in its path,
|
|
1304
|
+
* and then in any spelling: escaped or doubled separators, escaped letters, parameters. Nothing past the build's folder
|
|
1305
|
+
* on 2C2P's page hosts is worth a log line, so everything after `/payment/<build>/`, its separators in any spelling
|
|
1306
|
+
* SLASH reads, reads {token}, and on any other path everything after its first separator. A string that is not a URL
|
|
1307
|
+
* is cut the same way after a whole page-host authority it names (at the start, or after a space, a quote, a bracket,
|
|
1308
|
+
* a colon such as a `see:` before it, a comma, a semicolon, an equals sign or `//`, and followed by a path, a port or
|
|
1309
|
+
* the end), and only up to the end of that URL, so the message's own words,
|
|
1310
|
+
* another site's path naming the host and a lookalike host stay; a text naming several such URLs has each one cut.
|
|
1311
|
+
* Anchored matches and cuts in one pass: no input makes it backtrack.
|
|
1312
|
+
*/
|
|
1313
|
+
const TWOC2P_PAGE_HOST = /^(?:sandbox-)?pgw-ui\.2c2p\.com\.?$/i;
|
|
1314
|
+
const TWOC2P_PAGE_FOLDER = new RegExp(`^${SLASH}payment${SLASH}4\\.[0-9]{1,2}${SLASH}`, 'i');
|
|
1315
|
+
const TWOC2P_FIRST_SEPARATOR = new RegExp(`^${SLASH}`, 'i');
|
|
1316
|
+
const TWOC2P_PAGE_AUTHORITY = `(?:^|[\\s"'(<\\[:,;=]|//)(?:sandbox-)?pgw-ui\\.2c2p\\.com\\.?(?::[0-9]{1,5})?(?=${SLASH}|$)`;
|
|
1317
|
+
/** One page path, from its first separator: the build's folder stays and the rest reads {token}. */
|
|
1318
|
+
function maskTwoC2pPath(path) {
|
|
1319
|
+
const kept = TWOC2P_PAGE_FOLDER.exec(path) ?? TWOC2P_FIRST_SEPARATOR.exec(path);
|
|
1320
|
+
const end = kept ? kept[0].length : 0;
|
|
1321
|
+
return path.length > end ? `${path.slice(0, end)}{token}` : path;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Text that is not a URL: every whole page-host authority it names, each URL cut only to its own end (the first
|
|
1325
|
+
* space, quote or bracket), so the words between and after them stay. A text that starts with a slash is read the
|
|
1326
|
+
* same way, so another site's protocol-relative URL that names the host keeps its own host and path.
|
|
1327
|
+
*/
|
|
1328
|
+
function maskTwoC2pToken(path) {
|
|
1329
|
+
const authority = new RegExp(TWOC2P_PAGE_AUTHORITY, 'gi');
|
|
1330
|
+
// A closing bracket or parenthesis ends the URL only when no path or token character follows it, so a URL
|
|
1331
|
+
// a message wraps in [] or () keeps the words after it, and a bracket inside the path still cannot end the cut early.
|
|
1332
|
+
const urlEnd = /[\s"'<>]|[\])](?![A-Za-z0-9+/=%\\])/g;
|
|
1333
|
+
let out = '';
|
|
1334
|
+
let done = 0;
|
|
1335
|
+
for (let host = authority.exec(path); host !== null; host = authority.exec(path)) {
|
|
1336
|
+
const start = host.index + host[0].length;
|
|
1337
|
+
urlEnd.lastIndex = start;
|
|
1338
|
+
const stop = urlEnd.exec(path);
|
|
1339
|
+
const end = stop ? stop.index : path.length;
|
|
1340
|
+
out += path.slice(done, start) + maskTwoC2pPath(path.slice(start, end));
|
|
1341
|
+
done = end;
|
|
1342
|
+
authority.lastIndex = end;
|
|
1343
|
+
}
|
|
1344
|
+
return out + path.slice(done);
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* QuickPay's window names its session as the first segment of its own path (/<32 hex>, then /3ds_auth or
|
|
1348
|
+
* /poll-response), and that id is what carries a card post into the payment the merchant's signed entry
|
|
1349
|
+
* opened; a payment link's id (/payments/<64 hex>) opens one the same way. On QuickPay's host a run of 32 hex
|
|
1350
|
+
* digits or more after a slash, in any case or escaped, reads {session} whatever follows it: a URL parser drops a
|
|
1351
|
+
* newline after the URL, so words after it can run on into the id. The entry, the host's root, carries none.
|
|
1352
|
+
*/
|
|
1353
|
+
const QUICKPAY_HOST = /^payment\.quickpay\.net\.?$/i;
|
|
1354
|
+
const QUICKPAY_SESSION = /(\/|%2f)[0-9a-f]{32,}/gi;
|
|
1355
|
+
function maskQuickpaySession(path) {
|
|
1356
|
+
return path.replace(QUICKPAY_SESSION, '$1{session}');
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* `mask` reads a text that names the processor's host; `pathMask`, where a processor has one, reads the path of a
|
|
1360
|
+
* URL already parsed on that host, which `mask` reads otherwise.
|
|
1361
|
+
*/
|
|
1215
1362
|
const PATH_CREDENTIALS = [
|
|
1363
|
+
{ host: PATH_SECRET_HOST, named: /(?:wspay\.biz|monri\.com)/i, mask: maskPathSecret },
|
|
1216
1364
|
{ host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
|
|
1217
1365
|
{ host: GRAVY_HOST, named: /gr4vy\.app/i, mask: maskGravySession },
|
|
1218
1366
|
{ host: RAPYD_HOST, named: /rapyd\.net/i, mask: maskRapydToken },
|
|
@@ -1227,6 +1375,12 @@ const PATH_CREDENTIALS = [
|
|
|
1227
1375
|
{ host: GOPAY_CARD_FORM_HOST, named: /secure(?:\.sandbox)?\.gopay\.com/i, mask: maskGopayToken },
|
|
1228
1376
|
{ host: KOMOJU_HOST, named: /komoju\.com/i, mask: maskKomojuSession },
|
|
1229
1377
|
{ host: PRZELEWY24_HOST, named: /przelewy24\.pl/i, mask: maskPrzelewy24Token },
|
|
1378
|
+
{ host: REVOLUT_HOST, named: /(?:merchant|checkout|merchant-mgmt)\.revolut\.com/i, mask: maskRevolutToken },
|
|
1379
|
+
{ host: SUMUP_API_HOST, named: /api\.sumup\.com/i, mask: maskSumupCheckout },
|
|
1380
|
+
{ host: PAYTRAIL_HOST, named: /paytrail\.com/i, mask: maskPaytrailTransaction },
|
|
1381
|
+
{ host: PAYMENT_HIGHWAY_HOST, named: /(?:paymenthighway\.io|sph-test-solinor\.com)/i, mask: maskPaymentHighwaySession },
|
|
1382
|
+
{ host: TWOC2P_PAGE_HOST, named: /pgw-ui\.2c2p\.com/i, mask: maskTwoC2pToken, pathMask: maskTwoC2pPath },
|
|
1383
|
+
{ host: QUICKPAY_HOST, named: /quickpay\.net/i, mask: maskQuickpaySession },
|
|
1230
1384
|
];
|
|
1231
1385
|
/**
|
|
1232
1386
|
* A URL as it may appear in an error message: origin + path only. A paused
|
|
@@ -1254,30 +1408,52 @@ const PATH_CREDENTIALS = [
|
|
|
1254
1408
|
* On komoju.com a session id reads `{session}`: https://komoju.com/api/v1/sessions/{session}/pay.
|
|
1255
1409
|
* On Przelewy24's hosts the transaction's token reads `{token}`:
|
|
1256
1410
|
* https://secure.przelewy24.pl/trnRequest/{token}.
|
|
1411
|
+
* On Revolut's card payment and hosted checkout hosts an order or payment token reads `{token}`:
|
|
1412
|
+
* https://merchant.revolut.com/api/public/orders/{token}/payments.
|
|
1413
|
+
* On SumUp's API host a checkout id reads `{checkout}`: https://api.sumup.com/v0.2/checkouts/{checkout}.
|
|
1414
|
+
* On Paytrail's hosts a payment's id reads `{transaction}`:
|
|
1415
|
+
* https://services.paytrail.com/payments/{transaction}/click-to-pay/loading-and-redirect, and on Payment
|
|
1416
|
+
* Highway's a card page's session reads `{session}`.
|
|
1417
|
+
* On 2C2P's page hosts everything after the build's folder reads `{token}`:
|
|
1418
|
+
* https://pgw-ui.2c2p.com/payment/4.1/{token}.
|
|
1419
|
+
* On QuickPay's host the window's session reads `{session}`:
|
|
1420
|
+
* https://payment.quickpay.net/{session}/3ds_auth.
|
|
1257
1421
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1258
|
-
* same segments masked.
|
|
1422
|
+
* same segments masked for every processor whose host it names.
|
|
1423
|
+
* A URL is read by its host only when the parser finds one. Text before a URL
|
|
1424
|
+
* can read as a scheme: `see:https://api.stripe.com/...` parses as a URL with
|
|
1425
|
+
* the scheme `see`, no host, and the whole Stripe URL as its path, so a parse
|
|
1426
|
+
* with no host is read as a string that is not a URL. A path can carry a URL of
|
|
1427
|
+
* its own, as `http:https://api.stripe.com/...` does once `https` reads as its
|
|
1428
|
+
* host, so every processor whose host the path names masks the URL too.
|
|
1259
1429
|
*/
|
|
1260
1430
|
export function redactUrl(raw) {
|
|
1261
|
-
|
|
1262
|
-
const aci = redactAciCheckoutId(raw);
|
|
1263
|
-
if (aci !== null)
|
|
1264
|
-
return aci;
|
|
1431
|
+
let url;
|
|
1265
1432
|
try {
|
|
1266
|
-
|
|
1267
|
-
// A trailing dot names the same host, so it cannot let the secret through.
|
|
1268
|
-
let path = PATH_SECRET_HOSTS.includes(u.hostname.replace(/\.$/, '')) ? u.pathname.replace(PATH_SECRET, '$1{client_secret}') : u.pathname;
|
|
1269
|
-
for (const rule of PATH_CREDENTIALS)
|
|
1270
|
-
if (rule.host.test(u.hostname))
|
|
1271
|
-
path = rule.mask(path);
|
|
1272
|
-
return `${u.origin}${path}`;
|
|
1433
|
+
url = new URL(raw);
|
|
1273
1434
|
}
|
|
1274
1435
|
catch {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
return
|
|
1436
|
+
url = null;
|
|
1437
|
+
}
|
|
1438
|
+
if (!url?.hostname) {
|
|
1439
|
+
const text = raw.split(/[?#]/)[0];
|
|
1440
|
+
return maskNamed(maskPathSecret(text), text);
|
|
1280
1441
|
}
|
|
1442
|
+
let path = url.pathname;
|
|
1443
|
+
for (const rule of PATH_CREDENTIALS)
|
|
1444
|
+
if (rule.host.test(url.hostname))
|
|
1445
|
+
path = (rule.pathMask ?? rule.mask)(path);
|
|
1446
|
+
return maskNamed(`${url.origin}${path}`, url.pathname);
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* `text` with the mask of every processor whose host `names` names. `names` is read before any mask
|
|
1450
|
+
* runs, so a mask that rewrites another processor's host cannot keep that processor's mask from running.
|
|
1451
|
+
*/
|
|
1452
|
+
function maskNamed(text, names) {
|
|
1453
|
+
for (const rule of PATH_CREDENTIALS)
|
|
1454
|
+
if (rule.named.test(names))
|
|
1455
|
+
text = rule.mask(text);
|
|
1456
|
+
return text;
|
|
1281
1457
|
}
|
|
1282
1458
|
/**
|
|
1283
1459
|
* Refusals a preparation create answers before any preparation exists, surfaced
|
|
@@ -1350,6 +1526,16 @@ const PADDLE_VGS_PREPARATION_REFUSALS = [
|
|
|
1350
1526
|
* `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout. Only for a Wix checkout.
|
|
1351
1527
|
*/
|
|
1352
1528
|
const WIX_PAYMENTS_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'merchant_profile_mismatch'];
|
|
1529
|
+
/**
|
|
1530
|
+
* The API's refusals of a HiPay preparation, before any preparation exists, that prepareCheckout surfaces by name:
|
|
1531
|
+
* the company is not turned on for HiPay (`processor_unavailable`), the environment is not the caller's key mode's
|
|
1532
|
+
* (`live_host_on_sandbox_row`, `sandbox_host_on_live_row`), a checkout's merchant is not the name the reviewed pin
|
|
1533
|
+
* carries (`merchant_profile_mismatch`), and the price is zero (`amount_required`), since every create after it would be
|
|
1534
|
+
* refused. An unreviewed origin answers `unsupported_checkout`, which PREPARATION_REFUSALS names for every checkout.
|
|
1535
|
+
* Only for a HiPay checkout.
|
|
1536
|
+
*/
|
|
1537
|
+
const HIPAY_PREPARATION_REFUSALS = ['processor_unavailable', 'live_host_on_sandbox_row', 'sandbox_host_on_live_row', 'merchant_profile_mismatch',
|
|
1538
|
+
'amount_required'];
|
|
1353
1539
|
/**
|
|
1354
1540
|
* The reason the cardholder's device declines a bound prepared row with, per processor, when its own
|
|
1355
1541
|
* send boundary passed before the card left it (the Vault's sendWindow, 12 seconds after it last saw
|
|
@@ -1362,6 +1548,7 @@ const PREPARED_SEND_WINDOW_DECLINES = Object.freeze({
|
|
|
1362
1548
|
amadeus_checkout: 'amadeus_send_window_closed',
|
|
1363
1549
|
[SECURE_ACCEPTANCE_PSP]: 'secure_acceptance_send_window_closed',
|
|
1364
1550
|
[WIX_PAYMENTS_PSP]: 'wix_payments_send_window_closed',
|
|
1551
|
+
[HIPAY_PSP]: 'hipay_send_window_closed',
|
|
1365
1552
|
});
|
|
1366
1553
|
/**
|
|
1367
1554
|
* Refusals a CyberSource Flex preparation that tokenizes before Pay answers (4xx) before any
|
|
@@ -1450,8 +1637,8 @@ export class VaultClient {
|
|
|
1450
1637
|
/**
|
|
1451
1638
|
* Approve purchases by voice inside your own product: connect a person once,
|
|
1452
1639
|
* create the purchase, ask for the key word, read the result. Same
|
|
1453
|
-
* credentials, same token as the checkout calls.
|
|
1454
|
-
*
|
|
1640
|
+
* credentials, same token as the checkout calls. Approves on Agentcard's
|
|
1641
|
+
* page unless `voice: { approval: 'in_app' }` was named at construction.
|
|
1455
1642
|
*/
|
|
1456
1643
|
voice;
|
|
1457
1644
|
/**
|
|
@@ -1565,7 +1752,9 @@ export class VaultClient {
|
|
|
1565
1752
|
// wspay_confirm, shiji_sts_pinned_key, amadeus_checkout_actions.v1,
|
|
1566
1753
|
// worldline_mycheckout_form, verifone_card_capture, ekashu_hosted_payment_page,
|
|
1567
1754
|
// cybersource_sa_token_create, gravy_session_fields_put,
|
|
1568
|
-
// moneris_hosted_tokenization, paddle_vgs_cards,
|
|
1755
|
+
// moneris_hosted_tokenization, paddle_vgs_cards, paddle_tokenex_pay,
|
|
1756
|
+
// redsys_hosted_payment_page, lyra_krypton_payment, payfast_card_pay,
|
|
1757
|
+
// quickpay_hosted_payment_page) and
|
|
1569
1758
|
// the caller's company is one Agentcard turned that processor on for, so this
|
|
1570
1759
|
// build arms Fiserv's card capture, GMO's getToken, CyberSource Flex's card
|
|
1571
1760
|
// fields, Juspay's card flow, Datatrans's Secure Fields card post, the
|
|
@@ -2044,6 +2233,7 @@ export class VaultClient {
|
|
|
2044
2233
|
|| (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
|
|
2045
2234
|
|| (input.psp === PADDLE_VGS_PSP && PADDLE_VGS_PREPARATION_REFUSALS.includes(error.code))
|
|
2046
2235
|
|| (input.psp === WIX_PAYMENTS_PSP && WIX_PAYMENTS_PREPARATION_REFUSALS.includes(error.code))
|
|
2236
|
+
|| (input.psp === HIPAY_PSP && HIPAY_PREPARATION_REFUSALS.includes(error.code))
|
|
2047
2237
|
|| (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
|
|
2048
2238
|
|| (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
|
|
2049
2239
|
throw fail(error.code);
|
|
@@ -2327,10 +2517,11 @@ export class VaultClient {
|
|
|
2327
2517
|
// amount_required.
|
|
2328
2518
|
if (!hasAmount && rec && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
|
|
2329
2519
|
throw new AmountRequiredError(rec.psp);
|
|
2330
|
-
// A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay
|
|
2331
|
-
// PayMongo attach charges the total its payment
|
|
2332
|
-
// would show the cardholder no price at all: it is refused like a missing one.
|
|
2333
|
-
if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP || rec.psp === PAYMONGO_PSP
|
|
2520
|
+
// A Moneris or a HiPay token is charged later for whatever the merchant's server decides, and a Payfast pay, a
|
|
2521
|
+
// PayMongo attach or a 2C2P Payment charges the total its payment, checkout session or payment token
|
|
2522
|
+
// already holds, so a zero pair would show the cardholder no price at all: it is refused like a missing one.
|
|
2523
|
+
if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP || rec.psp === PAYMONGO_PSP || rec.psp === HIPAY_PSP || rec.psp === TWOC2P_PSP)
|
|
2524
|
+
&& Number(input.amount) <= 0)
|
|
2334
2525
|
throw new AmountRequiredError(rec.psp);
|
|
2335
2526
|
// Nor is the agent's pair tied to the page by anything but the total the
|
|
2336
2527
|
// page shows: without it the cardholder could approve one figure while the
|
|
@@ -2476,8 +2667,11 @@ export class VaultClient {
|
|
|
2476
2667
|
// and the API names 500 ms as such a row's poll interval.
|
|
2477
2668
|
// A prepared Paddle VGS card request too, for 20 seconds: Paddle's frame aborts it 10 seconds after
|
|
2478
2669
|
// Pay and sends it once more, and the API names 500 ms as such a row's poll interval.
|
|
2670
|
+
// A prepared HiPay token create too, for 20 seconds: HiPay's hosted fields give it 20 seconds from Pay, so
|
|
2671
|
+
// an answer the cardholder's device records late in that span still reaches the frame before it gives up,
|
|
2672
|
+
// and the API names 500 ms as such a row's poll interval.
|
|
2479
2673
|
const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
|
|
2480
|
-
: preparation?.psp === PADDLE_VGS_PSP ? Date.now() + 20_000
|
|
2674
|
+
: preparation?.psp === PADDLE_VGS_PSP || preparation?.psp === HIPAY_PSP ? Date.now() + 20_000
|
|
2481
2675
|
: preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
|
|
2482
2676
|
: preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
|
|
2483
2677
|
// A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
|
package/dist/datatrans.d.ts
CHANGED
|
@@ -69,8 +69,8 @@ export interface DatatransRepeatAnswer {
|
|
|
69
69
|
* The approved Datatrans answers one attachment handed to a card post, per frame. `frame` is the
|
|
70
70
|
* adapter's identity for the request's frame (the CDP frameId, the Playwright Frame); a request
|
|
71
71
|
* whose frame is unknown is never remembered and never answered. Only an answer payment-core reads
|
|
72
|
-
* as approved (
|
|
73
|
-
* question, so the page can try again.
|
|
72
|
+
* as approved (the token, with cardInfo when Datatrans sends one) is kept: an error answer leaves
|
|
73
|
+
* the next post a new question, so the page can try again.
|
|
74
74
|
*/
|
|
75
75
|
export declare class DatatransRepeats {
|
|
76
76
|
private readonly windowMs;
|
|
@@ -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: 829167e03a7ffe9fc109ababe9755f1263a840fd5294ebec970e08d41aa9741b
|
|
4
4
|
// src/datatrans.js
|
|
5
5
|
var DATATRANS_HOSTS = Object.freeze(["pay.datatrans.com", "pay.sandbox.datatrans.com"]);
|
|
6
6
|
var DATATRANS_TOKENIZE_PATH = "/upp/payment/SecureFields/paymentField";
|
|
@@ -346,7 +346,7 @@ function injectDatatransCard(body, card, policy = DATATRANS_REGISTRATION_POLICY)
|
|
|
346
346
|
var TRANSACTION_ID = /^[0-9]{18}$/;
|
|
347
347
|
var CARD_WORD = /^[A-Za-z][A-Za-z _-]{0,31}$/;
|
|
348
348
|
var COUNTRY = /^[A-Z]{2}$/;
|
|
349
|
-
var CARD_INFO_KEYS = ["brand", "type", "usage", "country", "issuer", "coBrand"];
|
|
349
|
+
var CARD_INFO_KEYS = ["brand", "type", "usage", "country", "issuer", "coBrand", "accountType", "binLength"];
|
|
350
350
|
var UNFOLLOWED_KEYS = ["tds2EnrollmentResult", "redirect", "dcc", "mcp"];
|
|
351
351
|
var ERROR_CODES = [
|
|
352
352
|
[/^Invalid merchantId$/, "invalid_merchant_id"],
|
|
@@ -368,10 +368,11 @@ function readAnswer(body) {
|
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
370
|
var optionalText = (value, pattern) => value === null || exact(pattern, value);
|
|
371
|
+
var optionalBinLength = (value) => value === null || Number.isInteger(value) && value >= 1 && value <= 19;
|
|
371
372
|
function rebuildCardInfo(info) {
|
|
372
|
-
if (!record(info) || !Object.keys(info).every((key) => CARD_INFO_KEYS.includes(key))
|
|
373
|
+
if (!record(info) || !Object.keys(info).every((key) => CARD_INFO_KEYS.includes(key)))
|
|
373
374
|
return null;
|
|
374
|
-
if (owns(info, "type") && !optionalText(info.type, CARD_WORD) || owns(info, "usage") && !optionalText(info.usage, CARD_WORD) || owns(info, "country") && !optionalText(info.country, COUNTRY) || owns(info, "issuer") && info.issuer !== null && !plain(info.issuer, 128) || owns(info, "coBrand") && info.coBrand !== null && !plain(info.coBrand, 64))
|
|
375
|
+
if (owns(info, "brand") && !optionalText(info.brand, CARD_WORD) || owns(info, "type") && !optionalText(info.type, CARD_WORD) || owns(info, "usage") && !optionalText(info.usage, CARD_WORD) || owns(info, "country") && !optionalText(info.country, COUNTRY) || owns(info, "issuer") && info.issuer !== null && !plain(info.issuer, 128) || owns(info, "coBrand") && info.coBrand !== null && !plain(info.coBrand, 64) || owns(info, "accountType") && !optionalText(info.accountType, CARD_WORD) || owns(info, "binLength") && !optionalBinLength(info.binLength))
|
|
375
376
|
return null;
|
|
376
377
|
const safe = {};
|
|
377
378
|
for (const key of CARD_INFO_KEYS)
|
|
@@ -380,10 +381,14 @@ function rebuildCardInfo(info) {
|
|
|
380
381
|
return safe;
|
|
381
382
|
}
|
|
382
383
|
function rebuildSuccess(value) {
|
|
383
|
-
if (Object.keys(value).
|
|
384
|
-
return
|
|
384
|
+
if (!Object.keys(value).every((key) => key === "transactionId" || key === "cardInfo"))
|
|
385
|
+
return { reason: "datatrans_unexpected_key" };
|
|
386
|
+
if (!exact(TRANSACTION_ID, value.transactionId))
|
|
387
|
+
return { reason: "datatrans_unexpected_token" };
|
|
388
|
+
if (!owns(value, "cardInfo") || value.cardInfo === null)
|
|
389
|
+
return { answer: { transactionId: value.transactionId } };
|
|
385
390
|
const cardInfo = rebuildCardInfo(value.cardInfo);
|
|
386
|
-
return cardInfo ? { transactionId: value.transactionId, cardInfo } :
|
|
391
|
+
return cardInfo ? { answer: { transactionId: value.transactionId, cardInfo } } : { reason: "datatrans_unexpected_card_info" };
|
|
387
392
|
}
|
|
388
393
|
function rebuildError(value) {
|
|
389
394
|
return Object.keys(value).length === 1 && plain(value.error, 256) && value.error.trim() !== "" ? { error: value.error } : null;
|
|
@@ -407,15 +412,15 @@ function datatransResponseOutcome(status, body, card) {
|
|
|
407
412
|
if (owns(value, "error")) {
|
|
408
413
|
const error = rebuildError(value);
|
|
409
414
|
if (!error)
|
|
410
|
-
return { outcome: "unknown", reason: "
|
|
415
|
+
return { outcome: "unknown", reason: "datatrans_unexpected_error" };
|
|
411
416
|
const code = datatransErrorCode(error.error);
|
|
412
417
|
return code === "too_many_requests" ? { outcome: "unknown", reason: "datatrans_repeat_request" } : { outcome: "refused", code };
|
|
413
418
|
}
|
|
414
419
|
if (UNFOLLOWED_KEYS.some((key) => owns(value, key)))
|
|
415
420
|
return { outcome: "unknown", reason: "datatrans_unsupported_flow" };
|
|
416
|
-
const safe = rebuildSuccess(value);
|
|
421
|
+
const { answer: safe, reason } = rebuildSuccess(value);
|
|
417
422
|
if (!safe)
|
|
418
|
-
return { outcome: "unknown", reason
|
|
423
|
+
return { outcome: "unknown", reason };
|
|
419
424
|
if (card && digits(card.number, 12, 19) && safe.transactionId.includes(card.number)) {
|
|
420
425
|
return { outcome: "unknown", reason: "datatrans_token_mismatch" };
|
|
421
426
|
}
|
|
@@ -432,7 +437,7 @@ function sanitizeDatatransTokenResponse(body) {
|
|
|
432
437
|
return JSON.stringify(rebuildError(value) ?? fail());
|
|
433
438
|
if (UNFOLLOWED_KEYS.some((key) => owns(value, key)))
|
|
434
439
|
return fail();
|
|
435
|
-
return JSON.stringify(rebuildSuccess(value) ?? fail());
|
|
440
|
+
return JSON.stringify(rebuildSuccess(value).answer ?? fail());
|
|
436
441
|
}
|
|
437
442
|
function datatransFacts(status, body) {
|
|
438
443
|
const verdict = datatransResponseOutcome(status, body);
|
package/dist/datatrans.js
CHANGED
|
@@ -112,8 +112,8 @@ function sameKey(a, b) {
|
|
|
112
112
|
* The approved Datatrans answers one attachment handed to a card post, per frame. `frame` is the
|
|
113
113
|
* adapter's identity for the request's frame (the CDP frameId, the Playwright Frame); a request
|
|
114
114
|
* whose frame is unknown is never remembered and never answered. Only an answer payment-core reads
|
|
115
|
-
* as approved (
|
|
116
|
-
* question, so the page can try again.
|
|
115
|
+
* as approved (the token, with cardInfo when Datatrans sends one) is kept: an error answer leaves
|
|
116
|
+
* the next post a new question, so the page can try again.
|
|
117
117
|
*/
|
|
118
118
|
export class DatatransRepeats {
|
|
119
119
|
windowMs;
|