@agent-cards/sdk 0.23.17 → 0.23.19
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 +19 -1
- package/README.md +17 -4
- package/dist/aci.d.ts +0 -7
- package/dist/aci.js +0 -19
- package/dist/card-fields.generated.js +963 -33
- package/dist/client.d.ts +24 -1
- package/dist/client.js +198 -57
- package/dist/datatrans.d.ts +2 -2
- package/dist/datatrans.generated.js +16 -11
- package/dist/datatrans.js +2 -2
- package/dist/pelecard.generated.d.ts +2 -1
- package/dist/pelecard.generated.js +14 -5
- package/dist/primer.generated.js +987 -57
- package/dist/stripe-checkout.generated.js +491 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -249,6 +249,14 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
|
|
|
249
249
|
* Wix's token. A second card request from the frame, which Wix's frame sends after a 401
|
|
250
250
|
* or 403 under a fresh instance, is never answered from the same approval. Served on the
|
|
251
251
|
* same terms: only to a build that lists it, for a company Agentcard turned Wix on for.
|
|
252
|
+
*
|
|
253
|
+
* `paymongo_checkout_pay`: the card attach PayMongo's hosted checkout sends to its own page
|
|
254
|
+
* URL (checkout.paymongo.com/<session>) as a Next.js Server Action. This build pauses only the
|
|
255
|
+
* page's own card attach and answers it with PayMongo's answer as the API recorded it, under the
|
|
256
|
+
* RSC type the page reads: a paid payment intent, or the step toward PayMongo's 3-D Secure page
|
|
257
|
+
* that the page then runs in the agent's browser. The cardholder's device sends the card through
|
|
258
|
+
* the checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
259
|
+
* Agentcard turned PayMongo on for.
|
|
252
260
|
*/
|
|
253
261
|
export declare const SUPPORTED_CAPABILITIES: readonly string[];
|
|
254
262
|
/**
|
|
@@ -1176,8 +1184,23 @@ export declare class CheckoutAuthError extends Error {
|
|
|
1176
1184
|
* https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
|
|
1177
1185
|
* On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
|
|
1178
1186
|
* On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1187
|
+
* On komoju.com a session id reads `{session}`: https://komoju.com/api/v1/sessions/{session}/pay.
|
|
1188
|
+
* On Przelewy24's hosts the transaction's token reads `{token}`:
|
|
1189
|
+
* https://secure.przelewy24.pl/trnRequest/{token}.
|
|
1190
|
+
* On Revolut's card payment and hosted checkout hosts an order or payment token reads `{token}`:
|
|
1191
|
+
* https://merchant.revolut.com/api/public/orders/{token}/payments.
|
|
1192
|
+
* On SumUp's API host a checkout id reads `{checkout}`: https://api.sumup.com/v0.2/checkouts/{checkout}.
|
|
1193
|
+
* On Paytrail's hosts a payment's id reads `{transaction}`:
|
|
1194
|
+
* https://services.paytrail.com/payments/{transaction}/click-to-pay/loading-and-redirect, and on Payment
|
|
1195
|
+
* Highway's a card page's session reads `{session}`.
|
|
1179
1196
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1180
|
-
* same segments masked.
|
|
1197
|
+
* same segments masked for every processor whose host it names.
|
|
1198
|
+
* A URL is read by its host only when the parser finds one. Text before a URL
|
|
1199
|
+
* can read as a scheme: `see:https://api.stripe.com/...` parses as a URL with
|
|
1200
|
+
* the scheme `see`, no host, and the whole Stripe URL as its path, so a parse
|
|
1201
|
+
* with no host is read as a string that is not a URL. A path can carry a URL of
|
|
1202
|
+
* its own, as `http:https://api.stripe.com/...` does once `https` reads as its
|
|
1203
|
+
* host, so every processor whose host the path names masks the URL too.
|
|
1181
1204
|
*/
|
|
1182
1205
|
export declare function redactUrl(raw: string): string;
|
|
1183
1206
|
export interface VaultClientOptions {
|
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';
|
|
@@ -233,9 +233,17 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
233
233
|
* Wix's token. A second card request from the frame, which Wix's frame sends after a 401
|
|
234
234
|
* or 403 under a fresh instance, is never answered from the same approval. Served on the
|
|
235
235
|
* same terms: only to a build that lists it, for a company Agentcard turned Wix on for.
|
|
236
|
+
*
|
|
237
|
+
* `paymongo_checkout_pay`: the card attach PayMongo's hosted checkout sends to its own page
|
|
238
|
+
* URL (checkout.paymongo.com/<session>) as a Next.js Server Action. This build pauses only the
|
|
239
|
+
* page's own card attach and answers it with PayMongo's answer as the API recorded it, under the
|
|
240
|
+
* RSC type the page reads: a paid payment intent, or the step toward PayMongo's 3-D Secure page
|
|
241
|
+
* that the page then runs in the agent's browser. The cardholder's device sends the card through
|
|
242
|
+
* the checkout relay. Served on the same terms: only to a build that lists it, for a company
|
|
243
|
+
* Agentcard turned PayMongo on for.
|
|
236
244
|
*/
|
|
237
245
|
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,
|
|
238
|
-
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY];
|
|
246
|
+
'redsys_hosted_payment_page', 'lyra_krypton_payment', 'payfast_card_pay', WIX_PAYMENTS_CAPABILITY, 'paymongo_checkout_pay'];
|
|
239
247
|
/**
|
|
240
248
|
* Processors whose paused request names no amount at all, so the pair the
|
|
241
249
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -264,13 +272,18 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
|
|
|
264
272
|
* and the API holds it to the payment Payfast itself reports. Wix's tokenize request
|
|
265
273
|
* carries none either: the merchant's page charges Wix's token for its own order, so the
|
|
266
274
|
* agent's pair, which the prepared checkout carries, is the total the cardholder approves.
|
|
275
|
+
* PayMongo's card attach carries none either: the merchant bound the total, always in
|
|
276
|
+
* Philippine pesos, to the checkout session when it created it, so the agent sends that
|
|
277
|
+
* total, above zero (PAYMONGO_PSP below), and the API holds it to the session PayMongo's
|
|
278
|
+
* own page shows.
|
|
267
279
|
*/
|
|
268
280
|
const MONERIS_PSP = 'moneris';
|
|
269
281
|
const PAYFAST_PSP = 'payfast';
|
|
270
|
-
const
|
|
282
|
+
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];
|
|
271
284
|
/** How AmountRequiredError names a processor. */
|
|
272
285
|
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
|
|
273
|
-
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix' };
|
|
286
|
+
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle', [PAYFAST_PSP]: 'Payfast', [WIX_PAYMENTS_PSP]: 'Wix', [PAYMONGO_PSP]: 'PayMongo' };
|
|
274
287
|
/**
|
|
275
288
|
* Processors whose request and answer name no amount at all, so the total the
|
|
276
289
|
* payment page shows (`pageAmount`) is the one figure that ties the agent's pair
|
|
@@ -1000,10 +1013,27 @@ function parseErrorEnvelope(bodyText) {
|
|
|
1000
1013
|
}
|
|
1001
1014
|
return { code: null, details: {} };
|
|
1002
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* A slash as the masks below read one: plain, or escaped once or more in either case (%2F, %2f, %252F). A path keeps
|
|
1018
|
+
* an escape as it was written, and the id behind it is the same id, so every slash a mask names reads in any of these
|
|
1019
|
+
* spellings. The slashes right before an id read as a run, and a doubled slash hides nothing either.
|
|
1020
|
+
*/
|
|
1021
|
+
const SLASH = '(?:/|%(?:25)*2f)';
|
|
1022
|
+
/** A hyphen inside an id: plain, or escaped once or more in either case (%2D, %252d). */
|
|
1023
|
+
const HYPHEN = '(?:-|%(?:25)*2d)';
|
|
1024
|
+
/**
|
|
1025
|
+
* The masks that find a uuid by its shape take it in any case, with or without its hyphens, each hyphen in any
|
|
1026
|
+
* spelling HYPHEN reads. The pattern reads a percent escape as one unit and keeps it, so the hex digits of an escaped
|
|
1027
|
+
* separator before the id (%2F, %252F, %3D) never start a match and leave the id's last digits showing.
|
|
1028
|
+
*/
|
|
1029
|
+
const UUID_OR_ESCAPE = new RegExp(`%(?:25)*[0-9a-f]{2}|[0-9a-f]{8}${HYPHEN}?[0-9a-f]{4}${HYPHEN}?[0-9a-f]{4}${HYPHEN}?[0-9a-f]{4}${HYPHEN}?[0-9a-f]{12}`, 'gi');
|
|
1030
|
+
function maskUuids(path, label) {
|
|
1031
|
+
return path.replace(UUID_OR_ESCAPE, (token) => (token.startsWith('%') ? token : label));
|
|
1032
|
+
}
|
|
1003
1033
|
const CREDITGUARD_HOST = /(?:^|\.)creditguard\.co\.il\.?$/i;
|
|
1034
|
+
const CREDITGUARD_TXN = new RegExp(`(${SLASH}txn${SLASH}+)[^/]+`, 'gi');
|
|
1004
1035
|
function maskCreditguardTxId(path) {
|
|
1005
|
-
return path.replace(
|
|
1006
|
-
.replace(/[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi, '{txId}');
|
|
1036
|
+
return maskUuids(path.replace(CREDITGUARD_TXN, '$1{txId}'), '{txId}');
|
|
1007
1037
|
}
|
|
1008
1038
|
/**
|
|
1009
1039
|
* Hosts that put a per-payment secret in the PATH: Monri Components and
|
|
@@ -1011,9 +1041,12 @@ function maskCreditguardTxId(path) {
|
|
|
1011
1041
|
* /v2/payment/{client_secret}/confirm (and /show, /status), and with the page's
|
|
1012
1042
|
* public token that secret confirms the payment and reads its status.
|
|
1013
1043
|
*/
|
|
1014
|
-
const
|
|
1015
|
-
/** The segment after a `payment` segment, in any case (the WSPay proxy is IIS)
|
|
1016
|
-
const PATH_SECRET =
|
|
1044
|
+
const PATH_SECRET_HOST = /^(?:proxy(?:-test)?\.wspay\.biz|ipg(?:test)?\.monri\.com)\.?$/i;
|
|
1045
|
+
/** The segment after a `payment` segment, in any case (the WSPay proxy is IIS) and any spelling of its slashes. */
|
|
1046
|
+
const PATH_SECRET = new RegExp(`(${SLASH}payment${SLASH}+)[^/]*`, 'gi');
|
|
1047
|
+
function maskPathSecret(path) {
|
|
1048
|
+
return path.replace(PATH_SECRET, '$1{client_secret}');
|
|
1049
|
+
}
|
|
1017
1050
|
/**
|
|
1018
1051
|
* Gravy (Gr4vy) gives every merchant hosts of its own under gr4vy.app, and Secure Fields sends the
|
|
1019
1052
|
* card to /checkout/sessions/<session id>/fields with no auth header, so the session id is that
|
|
@@ -1023,10 +1056,9 @@ const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
|
|
|
1023
1056
|
* session ids.
|
|
1024
1057
|
*/
|
|
1025
1058
|
const GRAVY_HOST = /(?:^|\.)gr4vy\.app\.?$/i;
|
|
1026
|
-
const GRAVY_SESSION =
|
|
1027
|
-
const GRAVY_SESSION_ID = /[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi;
|
|
1059
|
+
const GRAVY_SESSION = new RegExp(`(${SLASH}checkout${SLASH}sessions${SLASH}+)[^/]*`, 'gi');
|
|
1028
1060
|
function maskGravySession(path) {
|
|
1029
|
-
return path.replace(GRAVY_SESSION, '$1{session}')
|
|
1061
|
+
return maskUuids(path.replace(GRAVY_SESSION, '$1{session}'), '{session}');
|
|
1030
1062
|
}
|
|
1031
1063
|
/**
|
|
1032
1064
|
* Rapyd's hosted pages send the card to api.rapyd.net (sandboxapi.rapyd.net in test) with no auth
|
|
@@ -1037,7 +1069,7 @@ function maskGravySession(path) {
|
|
|
1037
1069
|
* after an escaped slash, read {token}.
|
|
1038
1070
|
*/
|
|
1039
1071
|
const RAPYD_HOST = /(?:^|\.)rapyd\.net\.?$/i;
|
|
1040
|
-
const RAPYD_CARD_PAGE =
|
|
1072
|
+
const RAPYD_CARD_PAGE = new RegExp(`(${SLASH}hosted${SLASH}collect${SLASH}card${SLASH}+)[^/]*`, 'gi');
|
|
1041
1073
|
const RAPYD_PAGE_ID = /(?:checkout|hp_[a-z]+)_[a-z0-9]+/gi;
|
|
1042
1074
|
function maskRapydToken(path) {
|
|
1043
1075
|
return path.replace(RAPYD_CARD_PAGE, '$1{token}').replace(RAPYD_PAGE_ID, '{token}');
|
|
@@ -1050,18 +1082,19 @@ function maskRapydToken(path) {
|
|
|
1050
1082
|
* SetupIntent id stays: confirming one also needs its client_secret, which rides in the body.
|
|
1051
1083
|
*/
|
|
1052
1084
|
const STRIPE_API_HOST = /^api\.stripe\.com\.?$/i;
|
|
1053
|
-
const STRIPE_PAYMENT_PAGE =
|
|
1085
|
+
const STRIPE_PAYMENT_PAGE = new RegExp(`(${SLASH}payment_pages${SLASH}+)[^/]*`, 'gi');
|
|
1054
1086
|
function maskStripeSession(path) {
|
|
1055
1087
|
return path.replace(STRIPE_PAYMENT_PAGE, '$1{session}');
|
|
1056
1088
|
}
|
|
1057
1089
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
* ACI's shape
|
|
1090
|
+
* ACI's checkout id is that checkout's only capability: once the card-number request carried the real
|
|
1091
|
+
* card, anyone holding it can post /payment against that card for the rest of the checkout's 30
|
|
1092
|
+
* minutes. On ACI's hosts, with a trailing dot, an escaped or doubled slash, in another case or in a
|
|
1093
|
+
* string that is not a URL, the segment after /v1/checkouts/ and any id of ACI's shape
|
|
1094
|
+
* (<32 hex>.<cluster>-vm-tx<nn>) read {checkoutId}.
|
|
1062
1095
|
*/
|
|
1063
1096
|
const ACI_HOST = /^(?:eu-prod\.|eu-test\.|test\.)?oppwa\.com\.?$/i;
|
|
1064
|
-
const ACI_CHECKOUT =
|
|
1097
|
+
const ACI_CHECKOUT = new RegExp(`(${SLASH}v1${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
|
|
1065
1098
|
const ACI_CHECKOUT_ID = /[0-9a-f]{32}\.[a-z0-9]{1,16}-vm-tx[0-9]{1,3}/gi;
|
|
1066
1099
|
function maskAciCheckout(path) {
|
|
1067
1100
|
return path.replace(ACI_CHECKOUT, '$1{checkoutId}').replace(ACI_CHECKOUT_ID, '{checkoutId}');
|
|
@@ -1071,34 +1104,35 @@ function maskAciCheckout(path) {
|
|
|
1071
1104
|
* with no auth header and no CSRF token (payment-core payfast.js): the payment's uuid is the
|
|
1072
1105
|
* request's only credential, and with it anyone can read that payment page, its amount and its
|
|
1073
1106
|
* merchant. Every route of the page carries the same uuid, so on every host Payfast owns (payfast.io and
|
|
1074
|
-
* payfast.co.za and any name under them) any id of its shape, in
|
|
1075
|
-
* reads {uuid}.
|
|
1107
|
+
* payfast.co.za and any name under them) any id of its shape, in every spelling maskUuids reads, reads {uuid}.
|
|
1076
1108
|
*/
|
|
1077
1109
|
const PAYFAST_HOST = /(?:^|\.)payfast\.(?:io|co\.za)\.?$/i;
|
|
1078
|
-
const PAYFAST_UUID = /[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}/gi;
|
|
1079
1110
|
function maskPayfastUuid(path) {
|
|
1080
|
-
return path
|
|
1111
|
+
return maskUuids(path, '{uuid}');
|
|
1081
1112
|
}
|
|
1082
1113
|
/**
|
|
1083
1114
|
* Paddle's checkout frame pays a TokenEx checkout with its own /pay on checkout-service.paddle.com
|
|
1084
1115
|
* (sandbox-checkout-service in test), and the checkout id in that path is the checkout's only
|
|
1085
1116
|
* credential there, no cookies (payment-core paddle-tokenex.js): with it, and the card in the body,
|
|
1086
1117
|
* a payment reaches that checkout. On those hosts the segment between /checkout/ or
|
|
1087
|
-
* /transaction-checkout/ and /pay, in any case
|
|
1118
|
+
* /transaction-checkout/ and /pay, in any case and any spelling of the slashes around it, reads {checkout}.
|
|
1119
|
+
* The segment ends at the first slash in any spelling, so a long run of them cannot make the pattern try
|
|
1120
|
+
* every way of splitting the run between the segment and the slashes.
|
|
1088
1121
|
*/
|
|
1089
1122
|
const PADDLE_CHECKOUT_HOST = /^(?:sandbox-)?checkout-service\.paddle\.com\.?$/i;
|
|
1090
|
-
const PADDLE_PAY =
|
|
1123
|
+
const PADDLE_PAY = new RegExp(`(${SLASH}(?:checkout|transaction-checkout)${SLASH}+)(?:[^/%]|%(?!(?:25)*2f))+(${SLASH}+pay)`, 'gi');
|
|
1091
1124
|
function maskPaddleCheckout(path) {
|
|
1092
1125
|
return path.replace(PADDLE_PAY, '$1{checkout}$2');
|
|
1093
1126
|
}
|
|
1094
1127
|
/**
|
|
1095
1128
|
* Lyra's Krypton frame sends its payment to ProcessPayment/2 on the brand's API host with the form
|
|
1096
1129
|
* token's session id as a `;jsessionid=` path parameter (payment-core lyra.js). With the form token the
|
|
1097
|
-
* body carries, that id continues the payment on Lyra's side, so on Lyra's hosts it reads `{session}`,
|
|
1098
|
-
*
|
|
1130
|
+
* body carries, that id continues the payment on Lyra's side, so on Lyra's hosts it reads `{session}`, in any
|
|
1131
|
+
* case and with its `;` or `=` escaped once or more:
|
|
1132
|
+
* https://api.systempay.fr/api-payment/V4/Charge/Public/ProcessPayment/2;jsessionid={session}.
|
|
1099
1133
|
*/
|
|
1100
1134
|
const LYRA_HOST = /^(?:api\.payzen\.eu|api\.systempay\.fr|api-sogecommerce\.societegenerale\.eu|api\.lyra\.com)\.?$/i;
|
|
1101
|
-
const LYRA_SESSION =
|
|
1135
|
+
const LYRA_SESSION = /(?:;|%(?:25)*3b)jsessionid(?:=|%(?:25)*3d)[^/;?#]*/gi;
|
|
1102
1136
|
function maskLyraSession(path) {
|
|
1103
1137
|
return path.replace(LYRA_SESSION, ';jsessionid={session}');
|
|
1104
1138
|
}
|
|
@@ -1123,24 +1157,54 @@ function maskPaymongoSession(path) {
|
|
|
1123
1157
|
* case or after an escaped slash, reads {token}: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1124
1158
|
*/
|
|
1125
1159
|
const GOPAY_CARD_FORM_HOST = /^secure(?:\.sandbox)?\.gopay\.com\.?$/i;
|
|
1126
|
-
const GOPAY_CARD_FORM_TOKEN =
|
|
1160
|
+
const GOPAY_CARD_FORM_TOKEN = new RegExp(`(${SLASH}h${SLASH}+)[^/?#]*`, 'gi');
|
|
1127
1161
|
function maskGopayToken(path) {
|
|
1128
1162
|
return path.replace(GOPAY_CARD_FORM_TOKEN, '$1{token}');
|
|
1129
1163
|
}
|
|
1164
|
+
/**
|
|
1165
|
+
* KOMOJU's Hosted Fields frame pays a session with POST /api/v1/sessions/<id>/pay on komoju.com under
|
|
1166
|
+
* the merchant's publishable key, which is public (payment-core komoju.js): with the session id, anyone
|
|
1167
|
+
* can read that session's amount and buyer email and pay it. On komoju.com the segment after /sessions,
|
|
1168
|
+
* in any case and after any run of slashes, each written plain or escaped once or more, reads {session}:
|
|
1169
|
+
* https://komoju.com/api/v1/sessions/{session}/pay. Then every run of 25 or more letters and digits left
|
|
1170
|
+
* reads {session}, so an id after a dot segment, a backslash or an escape KOMOJU's frame never writes is
|
|
1171
|
+
* masked as well; a percent escape right before the run stays when 25 or more remain after it. No other
|
|
1172
|
+
* path KOMOJU's card fields use holds such a run. The whole run reads {session}, never only its last 25
|
|
1173
|
+
* characters, so a longer id of another processor in the same text is never cut in two before that
|
|
1174
|
+
* processor's mask reads it.
|
|
1175
|
+
*/
|
|
1176
|
+
const KOMOJU_HOST = /^komoju\.com\.?$/i;
|
|
1177
|
+
const KOMOJU_SESSION = new RegExp(`(${SLASH}sessions)(${SLASH}+)[^/]*`, 'gi');
|
|
1178
|
+
const KOMOJU_SESSION_ID = /(%(?:25)*[0-9a-f]{2})?[a-z0-9]{25,}/gi;
|
|
1179
|
+
function maskKomojuSession(path) {
|
|
1180
|
+
return path.replace(KOMOJU_SESSION, '$1$2{session}').replace(KOMOJU_SESSION_ID, (_run, escape) => `${escape ?? ''}{session}`);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* SumUp's Payment Widget sends the card to https://api.sumup.com/v0.2/checkouts/<checkout id> with no cookie and
|
|
1184
|
+
* no auth header (payment-core sumup.js), so the checkout id is that request's only credential: with it anyone can
|
|
1185
|
+
* read the checkout's amount and merchant and pay it. On api.sumup.com the segment after /checkouts/, in any case
|
|
1186
|
+
* and any spelling of the slashes before it, reads {checkout}, as does any other id shaped like SumUp's UUID
|
|
1187
|
+
* checkout ids, in every spelling maskUuids reads.
|
|
1188
|
+
*/
|
|
1189
|
+
const SUMUP_API_HOST = /^api\.sumup\.com\.?$/i;
|
|
1190
|
+
const SUMUP_CHECKOUT = new RegExp(`(${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
|
|
1191
|
+
function maskSumupCheckout(path) {
|
|
1192
|
+
return maskUuids(path.replace(SUMUP_CHECKOUT, '$1{checkout}'), '{checkout}');
|
|
1193
|
+
}
|
|
1130
1194
|
/**
|
|
1131
1195
|
* The processors whose card request carries its own credential in the URL path, with the hosts
|
|
1132
|
-
* that send it, the text that names those hosts
|
|
1133
|
-
* guard in redact-url.test.mjs fails for a
|
|
1134
|
-
* this list or out of it.
|
|
1196
|
+
* that send it, the text that names those hosts anywhere else (a string that is not a URL, or a
|
|
1197
|
+
* path that carries a URL of its own), and the mask. The guard in redact-url.test.mjs fails for a
|
|
1198
|
+
* payment-core recognizer that no one has sorted into this list or out of it.
|
|
1135
1199
|
*/
|
|
1136
1200
|
/**
|
|
1137
1201
|
* Redsys's hosted payment page names its session in the path of every page and post after the entry
|
|
1138
1202
|
* (/sis/pagoTarjeta;jsessionid=<id>), and that id is what carries a card post into the session the
|
|
1139
|
-
* merchant's signed entry opened. On Redsys's hosts the session id, in any case or escaped,
|
|
1140
|
-
* {session}. The entry itself carries none.
|
|
1203
|
+
* merchant's signed entry opened. On Redsys's hosts the session id, in any case or escaped once or more,
|
|
1204
|
+
* reads {session}. The entry itself carries none.
|
|
1141
1205
|
*/
|
|
1142
1206
|
const REDSYS_HOST = /^sis(?:-t)?\.redsys\.es\.?$/i;
|
|
1143
|
-
const REDSYS_SESSION = /((?:;|%3b)jsessionid(?:=|%3d))[^/?#;]*/gi;
|
|
1207
|
+
const REDSYS_SESSION = /((?:;|%(?:25)*3b)jsessionid(?:=|%(?:25)*3d))[^/?#;]*/gi;
|
|
1144
1208
|
function maskRedsysSession(path) {
|
|
1145
1209
|
return path.replace(REDSYS_SESSION, '$1{session}');
|
|
1146
1210
|
}
|
|
@@ -1151,11 +1215,61 @@ function maskRedsysSession(path) {
|
|
|
1151
1215
|
* segment after /orders/, in any case or after an escaped slash, reads {order}.
|
|
1152
1216
|
*/
|
|
1153
1217
|
const PAYPAL_HOST = /(?:^|\.)paypal\.com\.?$/i;
|
|
1154
|
-
const PAYPAL_ORDER =
|
|
1218
|
+
const PAYPAL_ORDER = new RegExp(`(${SLASH}orders${SLASH}+)[^/]*`, 'gi');
|
|
1155
1219
|
function maskPaypalOrder(path) {
|
|
1156
1220
|
return path.replace(PAYPAL_ORDER, '$1{order}');
|
|
1157
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Przelewy24's pay wall names its transaction by one token in the path of every page and post
|
|
1224
|
+
* (/trnRequest/<token>, its method links, /card/p24form/<token>, /trnResult/<token>), and that token
|
|
1225
|
+
* is all a browser needs to open the transaction's pay wall (payment-core processors/przelewy24.js).
|
|
1226
|
+
* On Przelewy24's hosts a token-shaped segment, in any case, after an escaped slash or with its hyphens
|
|
1227
|
+
* escaped, reads {token}.
|
|
1228
|
+
*/
|
|
1229
|
+
const PRZELEWY24_HOST = /(?:^|\.)przelewy24\.pl\.?$/i;
|
|
1230
|
+
const PRZELEWY24_TOKEN = new RegExp(`[0-9a-f]{10}${HYPHEN}[0-9a-f]{6}${HYPHEN}[0-9a-f]{6}${HYPHEN}[0-9a-f]{10}`, 'gi');
|
|
1231
|
+
function maskPrzelewy24Token(path) {
|
|
1232
|
+
return path.replace(PRZELEWY24_TOKEN, '{token}');
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Revolut's card field and card popup send the card payment to /api/public/orders/<order token>/payments on
|
|
1236
|
+
* merchant.revolut.com (sandbox-merchant.revolut.com in test), and the frame polls /api/public/payments/<payment
|
|
1237
|
+
* token> (payment-core revolut.js). Neither carries a header or cookie that binds it: the order token alone
|
|
1238
|
+
* opens that order's checkout, its amount and its merchant, and pays it, and the payment token reads the payment.
|
|
1239
|
+
* Revolut's hosted checkout page names the same order token in its own paths (/payment-link/<order token>), and the
|
|
1240
|
+
* frame reads the order at merchant-mgmt.revolut.com/orders/token/<order token>. On those hosts every id of their
|
|
1241
|
+
* uuid shape, in every spelling maskUuids reads, reads {token}.
|
|
1242
|
+
*/
|
|
1243
|
+
const REVOLUT_HOST = /^(?:sandbox-)?(?:merchant|checkout|merchant-mgmt)\.revolut\.com\.?$/i;
|
|
1244
|
+
function maskRevolutToken(path) {
|
|
1245
|
+
return maskUuids(path, '{token}');
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Paytrail names its payment in the path of every route of its card method
|
|
1249
|
+
* (/payments/<transaction>/click-to-pay/loading-and-redirect, and its success and cancel routes), and the same id
|
|
1250
|
+
* opens Paytrail's own payment wall for that payment (pay.paytrail.com/pay/<transaction>), its amount, its
|
|
1251
|
+
* merchant and every method's form, to anyone who holds it (payment-core processors/paytrail.js). On every host
|
|
1252
|
+
* Paytrail owns, any id of its shape, with or without its hyphens, in any case, after an escaped slash or with its
|
|
1253
|
+
* hyphens escaped, reads {transaction}. Payment Highway, Paytrail's card platform, names its card page's session
|
|
1254
|
+
* the same way (/form/<session>/pay_with_click_to_pay), and that path is what carries a card post into the payment,
|
|
1255
|
+
* so on its hosts the same shape reads {session}. The pattern reads a percent escape as one unit and keeps it, so
|
|
1256
|
+
* the hex digits of an escaped separator (%2F, %252F, %3D) never start an id and leave part of it showing.
|
|
1257
|
+
*/
|
|
1258
|
+
const PAYTRAIL_HOST = /(?:^|\.)paytrail\.com\.?$/i;
|
|
1259
|
+
const PAYMENT_HIGHWAY_HOST = /(?:^|\.)(?:paymenthighway\.io|sph-test-solinor\.com)\.?$/i;
|
|
1260
|
+
const PAYTRAIL_ID_HYPHEN = '(?:-|%(?:25)*2d)?';
|
|
1261
|
+
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');
|
|
1262
|
+
function maskPaytrailId(path, label) {
|
|
1263
|
+
return path.replace(PAYTRAIL_ID, (token) => (token.startsWith('%') ? token : label));
|
|
1264
|
+
}
|
|
1265
|
+
function maskPaytrailTransaction(path) {
|
|
1266
|
+
return maskPaytrailId(path, '{transaction}');
|
|
1267
|
+
}
|
|
1268
|
+
function maskPaymentHighwaySession(path) {
|
|
1269
|
+
return maskPaytrailId(path, '{session}');
|
|
1270
|
+
}
|
|
1158
1271
|
const PATH_CREDENTIALS = [
|
|
1272
|
+
{ host: PATH_SECRET_HOST, named: /(?:wspay\.biz|monri\.com)/i, mask: maskPathSecret },
|
|
1159
1273
|
{ host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
|
|
1160
1274
|
{ host: GRAVY_HOST, named: /gr4vy\.app/i, mask: maskGravySession },
|
|
1161
1275
|
{ host: RAPYD_HOST, named: /rapyd\.net/i, mask: maskRapydToken },
|
|
@@ -1168,6 +1282,12 @@ const PATH_CREDENTIALS = [
|
|
|
1168
1282
|
{ host: PAYPAL_HOST, named: /paypal\.com/i, mask: maskPaypalOrder },
|
|
1169
1283
|
{ host: PAYMONGO_CHECKOUT_HOST, named: /checkout\.paymongo\.com/i, mask: maskPaymongoSession },
|
|
1170
1284
|
{ host: GOPAY_CARD_FORM_HOST, named: /secure(?:\.sandbox)?\.gopay\.com/i, mask: maskGopayToken },
|
|
1285
|
+
{ host: KOMOJU_HOST, named: /komoju\.com/i, mask: maskKomojuSession },
|
|
1286
|
+
{ host: PRZELEWY24_HOST, named: /przelewy24\.pl/i, mask: maskPrzelewy24Token },
|
|
1287
|
+
{ host: REVOLUT_HOST, named: /(?:merchant|checkout|merchant-mgmt)\.revolut\.com/i, mask: maskRevolutToken },
|
|
1288
|
+
{ host: SUMUP_API_HOST, named: /api\.sumup\.com/i, mask: maskSumupCheckout },
|
|
1289
|
+
{ host: PAYTRAIL_HOST, named: /paytrail\.com/i, mask: maskPaytrailTransaction },
|
|
1290
|
+
{ host: PAYMENT_HIGHWAY_HOST, named: /(?:paymenthighway\.io|sph-test-solinor\.com)/i, mask: maskPaymentHighwaySession },
|
|
1171
1291
|
];
|
|
1172
1292
|
/**
|
|
1173
1293
|
* A URL as it may appear in an error message: origin + path only. A paused
|
|
@@ -1192,30 +1312,51 @@ const PATH_CREDENTIALS = [
|
|
|
1192
1312
|
* https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
|
|
1193
1313
|
* On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
|
|
1194
1314
|
* On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1315
|
+
* On komoju.com a session id reads `{session}`: https://komoju.com/api/v1/sessions/{session}/pay.
|
|
1316
|
+
* On Przelewy24's hosts the transaction's token reads `{token}`:
|
|
1317
|
+
* https://secure.przelewy24.pl/trnRequest/{token}.
|
|
1318
|
+
* On Revolut's card payment and hosted checkout hosts an order or payment token reads `{token}`:
|
|
1319
|
+
* https://merchant.revolut.com/api/public/orders/{token}/payments.
|
|
1320
|
+
* On SumUp's API host a checkout id reads `{checkout}`: https://api.sumup.com/v0.2/checkouts/{checkout}.
|
|
1321
|
+
* On Paytrail's hosts a payment's id reads `{transaction}`:
|
|
1322
|
+
* https://services.paytrail.com/payments/{transaction}/click-to-pay/loading-and-redirect, and on Payment
|
|
1323
|
+
* Highway's a card page's session reads `{session}`.
|
|
1195
1324
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1196
|
-
* same segments masked.
|
|
1325
|
+
* same segments masked for every processor whose host it names.
|
|
1326
|
+
* A URL is read by its host only when the parser finds one. Text before a URL
|
|
1327
|
+
* can read as a scheme: `see:https://api.stripe.com/...` parses as a URL with
|
|
1328
|
+
* the scheme `see`, no host, and the whole Stripe URL as its path, so a parse
|
|
1329
|
+
* with no host is read as a string that is not a URL. A path can carry a URL of
|
|
1330
|
+
* its own, as `http:https://api.stripe.com/...` does once `https` reads as its
|
|
1331
|
+
* host, so every processor whose host the path names masks the URL too.
|
|
1197
1332
|
*/
|
|
1198
1333
|
export function redactUrl(raw) {
|
|
1199
|
-
|
|
1200
|
-
const aci = redactAciCheckoutId(raw);
|
|
1201
|
-
if (aci !== null)
|
|
1202
|
-
return aci;
|
|
1334
|
+
let url;
|
|
1203
1335
|
try {
|
|
1204
|
-
|
|
1205
|
-
// A trailing dot names the same host, so it cannot let the secret through.
|
|
1206
|
-
let path = PATH_SECRET_HOSTS.includes(u.hostname.replace(/\.$/, '')) ? u.pathname.replace(PATH_SECRET, '$1{client_secret}') : u.pathname;
|
|
1207
|
-
for (const rule of PATH_CREDENTIALS)
|
|
1208
|
-
if (rule.host.test(u.hostname))
|
|
1209
|
-
path = rule.mask(path);
|
|
1210
|
-
return `${u.origin}${path}`;
|
|
1336
|
+
url = new URL(raw);
|
|
1211
1337
|
}
|
|
1212
1338
|
catch {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
return
|
|
1339
|
+
url = null;
|
|
1340
|
+
}
|
|
1341
|
+
if (!url?.hostname) {
|
|
1342
|
+
const text = raw.split(/[?#]/)[0];
|
|
1343
|
+
return maskNamed(maskPathSecret(text), text);
|
|
1218
1344
|
}
|
|
1345
|
+
let path = url.pathname;
|
|
1346
|
+
for (const rule of PATH_CREDENTIALS)
|
|
1347
|
+
if (rule.host.test(url.hostname))
|
|
1348
|
+
path = rule.mask(path);
|
|
1349
|
+
return maskNamed(`${url.origin}${path}`, url.pathname);
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* `text` with the mask of every processor whose host `names` names. `names` is read before any mask
|
|
1353
|
+
* runs, so a mask that rewrites another processor's host cannot keep that processor's mask from running.
|
|
1354
|
+
*/
|
|
1355
|
+
function maskNamed(text, names) {
|
|
1356
|
+
for (const rule of PATH_CREDENTIALS)
|
|
1357
|
+
if (rule.named.test(names))
|
|
1358
|
+
text = rule.mask(text);
|
|
1359
|
+
return text;
|
|
1219
1360
|
}
|
|
1220
1361
|
/**
|
|
1221
1362
|
* Refusals a preparation create answers before any preparation exists, surfaced
|
|
@@ -2265,10 +2406,10 @@ export class VaultClient {
|
|
|
2265
2406
|
// amount_required.
|
|
2266
2407
|
if (!hasAmount && rec && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
|
|
2267
2408
|
throw new AmountRequiredError(rec.psp);
|
|
2268
|
-
// A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay
|
|
2269
|
-
// charges the total its payment already holds, so a zero pair
|
|
2270
|
-
// at all: it is refused like a missing one.
|
|
2271
|
-
if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP) && Number(input.amount) <= 0)
|
|
2409
|
+
// A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay or a
|
|
2410
|
+
// PayMongo attach charges the total its payment or checkout session already holds, so a zero pair
|
|
2411
|
+
// would show the cardholder no price at all: it is refused like a missing one.
|
|
2412
|
+
if (hasAmount && rec && (rec.psp === MONERIS_PSP || rec.psp === PAYFAST_PSP || rec.psp === PAYMONGO_PSP) && Number(input.amount) <= 0)
|
|
2272
2413
|
throw new AmountRequiredError(rec.psp);
|
|
2273
2414
|
// Nor is the agent's pair tied to the page by anything but the total the
|
|
2274
2415
|
// page shows: without it the cardholder could approve one figure while the
|
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;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare var PELECARD_TRANSACTION_HOSTS: readonly string[];
|
|
2
2
|
declare var PELECARD_TRANSACTION_PATH: string;
|
|
3
3
|
declare var PELECARD_REFUSAL_REASONS: readonly string[];
|
|
4
|
+
declare var PELECARD_SUCCESS_CODES: readonly string[];
|
|
4
5
|
declare function isPelecardTransactionUrl(url: any, method?: string): boolean;
|
|
5
6
|
declare function pelecardBodyRefusal(body: any): "unsupported" | "keep_on_file" | null;
|
|
6
7
|
declare function isPelecardNewCardBody(body: any): boolean;
|
|
@@ -29,4 +30,4 @@ declare function pelecardFacts(status: any, body: any): {
|
|
|
29
30
|
outcome: string;
|
|
30
31
|
refusalCode: any;
|
|
31
32
|
};
|
|
32
|
-
export { PELECARD_REFUSAL_REASONS, PELECARD_TRANSACTION_HOSTS, PELECARD_TRANSACTION_PATH, classifyPelecardRequest, injectPelecardCard, isPelecardNewCardBody, isPelecardTransactionRequest, isPelecardTransactionUrl, pelecardBodyRefusal, pelecardFacts, pelecardKeepOnFileRequested, pelecardRefusalMessage, pelecardResponseOutcome, pelecardTransactionRefusal, sanitizePelecardResponse };
|
|
33
|
+
export { PELECARD_REFUSAL_REASONS, PELECARD_SUCCESS_CODES, PELECARD_TRANSACTION_HOSTS, PELECARD_TRANSACTION_PATH, classifyPelecardRequest, injectPelecardCard, isPelecardNewCardBody, isPelecardTransactionRequest, isPelecardTransactionUrl, pelecardBodyRefusal, pelecardFacts, pelecardKeepOnFileRequested, pelecardRefusalMessage, pelecardResponseOutcome, pelecardTransactionRefusal, sanitizePelecardResponse };
|