@agent-cards/sdk 0.23.17 → 0.23.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -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 AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP, PAYFAST_PSP, WIX_PAYMENTS_PSP];
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(/(\/txn\/)[^/]+/gi, '$1{txId}')
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
@@ -1012,8 +1042,8 @@ function maskCreditguardTxId(path) {
1012
1042
  * public token that secret confirms the payment and reads its status.
1013
1043
  */
1014
1044
  const PATH_SECRET_HOSTS = ['proxy.wspay.biz', 'proxy-test.wspay.biz', 'ipg.monri.com', 'ipgtest.monri.com'];
1015
- /** The segment after a `payment` segment, in any case (the WSPay proxy is IIS) or after an escaped slash. */
1016
- const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
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');
1017
1047
  /**
1018
1048
  * Gravy (Gr4vy) gives every merchant hosts of its own under gr4vy.app, and Secure Fields sends the
1019
1049
  * card to /checkout/sessions/<session id>/fields with no auth header, so the session id is that
@@ -1023,10 +1053,9 @@ const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
1023
1053
  * session ids.
1024
1054
  */
1025
1055
  const GRAVY_HOST = /(?:^|\.)gr4vy\.app\.?$/i;
1026
- const GRAVY_SESSION = /(\/checkout\/sessions(?:\/|%2f))[^/]*/gi;
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;
1056
+ const GRAVY_SESSION = new RegExp(`(${SLASH}checkout${SLASH}sessions${SLASH}+)[^/]*`, 'gi');
1028
1057
  function maskGravySession(path) {
1029
- return path.replace(GRAVY_SESSION, '$1{session}').replace(GRAVY_SESSION_ID, '{session}');
1058
+ return maskUuids(path.replace(GRAVY_SESSION, '$1{session}'), '{session}');
1030
1059
  }
1031
1060
  /**
1032
1061
  * Rapyd's hosted pages send the card to api.rapyd.net (sandboxapi.rapyd.net in test) with no auth
@@ -1037,7 +1066,7 @@ function maskGravySession(path) {
1037
1066
  * after an escaped slash, read {token}.
1038
1067
  */
1039
1068
  const RAPYD_HOST = /(?:^|\.)rapyd\.net\.?$/i;
1040
- const RAPYD_CARD_PAGE = /(\/hosted\/collect\/card(?:\/|%2f))[^/]*/gi;
1069
+ const RAPYD_CARD_PAGE = new RegExp(`(${SLASH}hosted${SLASH}collect${SLASH}card${SLASH}+)[^/]*`, 'gi');
1041
1070
  const RAPYD_PAGE_ID = /(?:checkout|hp_[a-z]+)_[a-z0-9]+/gi;
1042
1071
  function maskRapydToken(path) {
1043
1072
  return path.replace(RAPYD_CARD_PAGE, '$1{token}').replace(RAPYD_PAGE_ID, '{token}');
@@ -1050,7 +1079,7 @@ function maskRapydToken(path) {
1050
1079
  * SetupIntent id stays: confirming one also needs its client_secret, which rides in the body.
1051
1080
  */
1052
1081
  const STRIPE_API_HOST = /^api\.stripe\.com\.?$/i;
1053
- const STRIPE_PAYMENT_PAGE = /(\/payment_pages(?:\/|%2f))[^/]*/gi;
1082
+ const STRIPE_PAYMENT_PAGE = new RegExp(`(${SLASH}payment_pages${SLASH}+)[^/]*`, 'gi');
1054
1083
  function maskStripeSession(path) {
1055
1084
  return path.replace(STRIPE_PAYMENT_PAGE, '$1{session}');
1056
1085
  }
@@ -1061,7 +1090,7 @@ function maskStripeSession(path) {
1061
1090
  * ACI's shape (<32 hex>.<cluster>-vm-tx<nn>) read {checkoutId}.
1062
1091
  */
1063
1092
  const ACI_HOST = /^(?:eu-prod\.|eu-test\.|test\.)?oppwa\.com\.?$/i;
1064
- const ACI_CHECKOUT = /(\/v1\/checkouts(?:\/|%2f))[^/]*/gi;
1093
+ const ACI_CHECKOUT = new RegExp(`(${SLASH}v1${SLASH}checkouts${SLASH}+)[^/]*`, 'gi');
1065
1094
  const ACI_CHECKOUT_ID = /[0-9a-f]{32}\.[a-z0-9]{1,16}-vm-tx[0-9]{1,3}/gi;
1066
1095
  function maskAciCheckout(path) {
1067
1096
  return path.replace(ACI_CHECKOUT, '$1{checkoutId}').replace(ACI_CHECKOUT_ID, '{checkoutId}');
@@ -1071,34 +1100,35 @@ function maskAciCheckout(path) {
1071
1100
  * with no auth header and no CSRF token (payment-core payfast.js): the payment's uuid is the
1072
1101
  * request's only credential, and with it anyone can read that payment page, its amount and its
1073
1102
  * 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 any case or after an escaped slash,
1075
- * reads {uuid}.
1103
+ * payfast.co.za and any name under them) any id of its shape, in every spelling maskUuids reads, reads {uuid}.
1076
1104
  */
1077
1105
  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
1106
  function maskPayfastUuid(path) {
1080
- return path.replace(PAYFAST_UUID, '{uuid}');
1107
+ return maskUuids(path, '{uuid}');
1081
1108
  }
1082
1109
  /**
1083
1110
  * Paddle's checkout frame pays a TokenEx checkout with its own /pay on checkout-service.paddle.com
1084
1111
  * (sandbox-checkout-service in test), and the checkout id in that path is the checkout's only
1085
1112
  * credential there, no cookies (payment-core paddle-tokenex.js): with it, and the card in the body,
1086
1113
  * a payment reaches that checkout. On those hosts the segment between /checkout/ or
1087
- * /transaction-checkout/ and /pay, in any case or after an escaped slash, reads {checkout}.
1114
+ * /transaction-checkout/ and /pay, in any case and any spelling of the slashes around it, reads {checkout}.
1115
+ * The segment ends at the first slash in any spelling, so a long run of them cannot make the pattern try
1116
+ * every way of splitting the run between the segment and the slashes.
1088
1117
  */
1089
1118
  const PADDLE_CHECKOUT_HOST = /^(?:sandbox-)?checkout-service\.paddle\.com\.?$/i;
1090
- const PADDLE_PAY = /(\/(?:checkout|transaction-checkout)(?:\/|%2f))[^/]*(\/pay)/gi;
1119
+ const PADDLE_PAY = new RegExp(`(${SLASH}(?:checkout|transaction-checkout)${SLASH}+)(?:[^/%]|%(?!(?:25)*2f))+(${SLASH}+pay)`, 'gi');
1091
1120
  function maskPaddleCheckout(path) {
1092
1121
  return path.replace(PADDLE_PAY, '$1{checkout}$2');
1093
1122
  }
1094
1123
  /**
1095
1124
  * Lyra's Krypton frame sends its payment to ProcessPayment/2 on the brand's API host with the form
1096
1125
  * 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
- * in any case: https://api.systempay.fr/api-payment/V4/Charge/Public/ProcessPayment/2;jsessionid={session}.
1126
+ * body carries, that id continues the payment on Lyra's side, so on Lyra's hosts it reads `{session}`, in any
1127
+ * case and with its `;` or `=` escaped once or more:
1128
+ * https://api.systempay.fr/api-payment/V4/Charge/Public/ProcessPayment/2;jsessionid={session}.
1099
1129
  */
1100
1130
  const LYRA_HOST = /^(?:api\.payzen\.eu|api\.systempay\.fr|api-sogecommerce\.societegenerale\.eu|api\.lyra\.com)\.?$/i;
1101
- const LYRA_SESSION = /;jsessionid=[^/;?#]*/gi;
1131
+ const LYRA_SESSION = /(?:;|%(?:25)*3b)jsessionid(?:=|%(?:25)*3d)[^/;?#]*/gi;
1102
1132
  function maskLyraSession(path) {
1103
1133
  return path.replace(LYRA_SESSION, ';jsessionid={session}');
1104
1134
  }
@@ -1123,10 +1153,25 @@ function maskPaymongoSession(path) {
1123
1153
  * case or after an escaped slash, reads {token}: https://secure.gopay.com/gp-card-comm/h/{token}.
1124
1154
  */
1125
1155
  const GOPAY_CARD_FORM_HOST = /^secure(?:\.sandbox)?\.gopay\.com\.?$/i;
1126
- const GOPAY_CARD_FORM_TOKEN = /(\/h(?:\/|%2f))[^/?#]*/gi;
1156
+ const GOPAY_CARD_FORM_TOKEN = new RegExp(`(${SLASH}h${SLASH}+)[^/?#]*`, 'gi');
1127
1157
  function maskGopayToken(path) {
1128
1158
  return path.replace(GOPAY_CARD_FORM_TOKEN, '$1{token}');
1129
1159
  }
1160
+ /**
1161
+ * KOMOJU's Hosted Fields frame pays a session with POST /api/v1/sessions/<id>/pay on komoju.com under
1162
+ * the merchant's publishable key, which is public (payment-core komoju.js): with the session id, anyone
1163
+ * can read that session's amount and buyer email and pay it. On komoju.com the segment after /sessions,
1164
+ * 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 any 25 letters and digits left that end a run read
1166
+ * {session} too, so an id after a dot segment, a backslash or an escape KOMOJU's frame never writes is
1167
+ * masked as well; no other path KOMOJU's card fields use holds such a run.
1168
+ */
1169
+ const KOMOJU_HOST = /^komoju\.com\.?$/i;
1170
+ const KOMOJU_SESSION = new RegExp(`(${SLASH}sessions)(${SLASH}+)[^/]*`, 'gi');
1171
+ const KOMOJU_SESSION_ID = /[a-z0-9]{25}(?![a-z0-9])/gi;
1172
+ function maskKomojuSession(path) {
1173
+ return path.replace(KOMOJU_SESSION, '$1$2{session}').replace(KOMOJU_SESSION_ID, '{session}');
1174
+ }
1130
1175
  /**
1131
1176
  * The processors whose card request carries its own credential in the URL path, with the hosts
1132
1177
  * that send it, the text that names those hosts in a string that is not a URL, and the mask. The
@@ -1136,11 +1181,11 @@ function maskGopayToken(path) {
1136
1181
  /**
1137
1182
  * Redsys's hosted payment page names its session in the path of every page and post after the entry
1138
1183
  * (/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, reads
1140
- * {session}. The entry itself carries none.
1184
+ * merchant's signed entry opened. On Redsys's hosts the session id, in any case or escaped once or more,
1185
+ * reads {session}. The entry itself carries none.
1141
1186
  */
1142
1187
  const REDSYS_HOST = /^sis(?:-t)?\.redsys\.es\.?$/i;
1143
- const REDSYS_SESSION = /((?:;|%3b)jsessionid(?:=|%3d))[^/?#;]*/gi;
1188
+ const REDSYS_SESSION = /((?:;|%(?:25)*3b)jsessionid(?:=|%(?:25)*3d))[^/?#;]*/gi;
1144
1189
  function maskRedsysSession(path) {
1145
1190
  return path.replace(REDSYS_SESSION, '$1{session}');
1146
1191
  }
@@ -1151,10 +1196,22 @@ function maskRedsysSession(path) {
1151
1196
  * segment after /orders/, in any case or after an escaped slash, reads {order}.
1152
1197
  */
1153
1198
  const PAYPAL_HOST = /(?:^|\.)paypal\.com\.?$/i;
1154
- const PAYPAL_ORDER = /(\/orders(?:\/|%2f))[^/]*/gi;
1199
+ const PAYPAL_ORDER = new RegExp(`(${SLASH}orders${SLASH}+)[^/]*`, 'gi');
1155
1200
  function maskPaypalOrder(path) {
1156
1201
  return path.replace(PAYPAL_ORDER, '$1{order}');
1157
1202
  }
1203
+ /**
1204
+ * Przelewy24's pay wall names its transaction by one token in the path of every page and post
1205
+ * (/trnRequest/<token>, its method links, /card/p24form/<token>, /trnResult/<token>), and that token
1206
+ * is all a browser needs to open the transaction's pay wall (payment-core processors/przelewy24.js).
1207
+ * On Przelewy24's hosts a token-shaped segment, in any case, after an escaped slash or with its hyphens
1208
+ * escaped, reads {token}.
1209
+ */
1210
+ const PRZELEWY24_HOST = /(?:^|\.)przelewy24\.pl\.?$/i;
1211
+ 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');
1212
+ function maskPrzelewy24Token(path) {
1213
+ return path.replace(PRZELEWY24_TOKEN, '{token}');
1214
+ }
1158
1215
  const PATH_CREDENTIALS = [
1159
1216
  { host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
1160
1217
  { host: GRAVY_HOST, named: /gr4vy\.app/i, mask: maskGravySession },
@@ -1168,6 +1225,8 @@ const PATH_CREDENTIALS = [
1168
1225
  { host: PAYPAL_HOST, named: /paypal\.com/i, mask: maskPaypalOrder },
1169
1226
  { host: PAYMONGO_CHECKOUT_HOST, named: /checkout\.paymongo\.com/i, mask: maskPaymongoSession },
1170
1227
  { host: GOPAY_CARD_FORM_HOST, named: /secure(?:\.sandbox)?\.gopay\.com/i, mask: maskGopayToken },
1228
+ { host: KOMOJU_HOST, named: /komoju\.com/i, mask: maskKomojuSession },
1229
+ { host: PRZELEWY24_HOST, named: /przelewy24\.pl/i, mask: maskPrzelewy24Token },
1171
1230
  ];
1172
1231
  /**
1173
1232
  * A URL as it may appear in an error message: origin + path only. A paused
@@ -1192,6 +1251,9 @@ const PATH_CREDENTIALS = [
1192
1251
  * https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
1193
1252
  * On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
1194
1253
  * On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
1254
+ * On komoju.com a session id reads `{session}`: https://komoju.com/api/v1/sessions/{session}/pay.
1255
+ * On Przelewy24's hosts the transaction's token reads `{token}`:
1256
+ * https://secure.przelewy24.pl/trnRequest/{token}.
1195
1257
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1196
1258
  * same segments masked.
1197
1259
  */
@@ -2265,10 +2327,10 @@ export class VaultClient {
2265
2327
  // amount_required.
2266
2328
  if (!hasAmount && rec && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
2267
2329
  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 would show the cardholder no price
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)
2330
+ // A Moneris token is charged later for whatever the merchant's server decides, and a Payfast pay or a
2331
+ // PayMongo attach charges the total its payment or checkout session already holds, so a zero pair
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) && Number(input.amount) <= 0)
2272
2334
  throw new AmountRequiredError(rec.psp);
2273
2335
  // Nor is the agent's pair tied to the page by anything but the total the
2274
2336
  // page shows: without it the cardholder could approve one figure while the