@agent-cards/sdk 0.23.9 → 0.23.11
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 +48 -0
- package/PREFLIGHT.md +7 -5
- package/README.md +211 -102
- package/dist/aci.d.ts +7 -5
- package/dist/aci.generated.d.ts +2 -1
- package/dist/aci.generated.js +14 -4
- package/dist/aci.js +28 -7
- package/dist/amadeus-checkout.generated.js +2 -2
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-fields.generated.js +165 -10
- package/dist/cdp.js +86 -34
- package/dist/client.d.ts +73 -33
- package/dist/client.js +136 -45
- package/dist/creditguard.generated.d.ts +1 -1
- package/dist/creditguard.generated.js +1 -3
- package/dist/cse-body.d.ts +7 -6
- package/dist/cse-body.js +8 -7
- package/dist/datatrans.generated.js +8 -5
- package/dist/elavon-converge.d.ts +5 -2
- package/dist/elavon-converge.generated.js +9 -5
- package/dist/elavon-converge.js +8 -4
- package/dist/fiserv.d.ts +9 -5
- package/dist/fiserv.generated.d.ts +3 -3
- package/dist/fiserv.generated.js +50 -20
- package/dist/fiserv.js +10 -6
- package/dist/lifecycle.d.ts +9 -1
- package/dist/lifecycle.js +34 -0
- package/dist/payrails.d.ts +78 -18
- package/dist/payrails.generated.d.ts +12 -2
- package/dist/payrails.generated.js +213 -18
- package/dist/payrails.js +165 -44
- package/dist/pre-claim.d.ts +7 -0
- package/dist/pre-claim.js +98 -36
- package/dist/preflight-capabilities.generated.d.ts +0 -106
- package/dist/preflight-capabilities.generated.js +0 -153
- package/dist/preflight-catalog.json +61 -181
- package/dist/preflight-schemas.json +6 -6
- package/dist/preflight.generated.d.ts +0 -106
- package/dist/preflight.generated.js +27 -15
- package/dist/preparation.d.ts +2 -0
- package/dist/preparation.js +4 -2
- package/dist/prepared-processor.d.ts +19 -5
- package/dist/prepared-processor.js +21 -5
- package/dist/primer.generated.d.ts +5 -1
- package/dist/primer.generated.js +202 -34
- package/dist/registry.d.ts +18 -12
- package/dist/registry.js +11 -8
- package/dist/shiji-sts.d.ts +24 -22
- package/dist/shiji-sts.generated.d.ts +1 -1
- package/dist/shiji-sts.generated.js +12 -8
- package/dist/shiji-sts.js +49 -46
- package/dist/stripe-checkout.generated.js +157 -49
- package/dist/verifone.generated.js +192 -17
- package/dist/verifone.js +1 -1
- package/dist/worldline-mycheckout.d.ts +27 -0
- package/dist/worldline-mycheckout.generated.d.ts +29 -0
- package/dist/worldline-mycheckout.generated.js +162 -0
- package/dist/worldline-mycheckout.js +93 -0
- package/dist/wspay.d.ts +17 -16
- package/dist/wspay.generated.d.ts +6 -1
- package/dist/wspay.generated.js +13 -3
- package/dist/wspay.js +9 -11
- package/examples/preflight/kernel-native/README.md +1 -1
- package/examples/preflight/kernel-native/inventory.json +1 -19
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -13,7 +13,7 @@ import { merchantSessionCapture } from './merchant-session-probe.js';
|
|
|
13
13
|
import { creditguardPageProcessUrl, creditguardPpDomainHost, creditguardPreparationEnvironment, creditguardProcessTxId, sanitizeCreditguardResponse, } from './creditguard.generated.js';
|
|
14
14
|
import { CONVERGE_CAPABILITY } from './elavon-converge.js';
|
|
15
15
|
import { SHIJI_STS_CAPABILITY, SHIJI_STS_PSP, isShijiStsServedCompanion, isShijiStsServedSubstitution } from './shiji-sts.js';
|
|
16
|
-
import { AMADEUS_CHECKOUT_CAPABILITY } from './amadeus-checkout.generated.js';
|
|
16
|
+
import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
|
|
17
17
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
18
18
|
import { VoiceModule } from './voice.js';
|
|
19
19
|
import { BuyModule } from './buy.js';
|
|
@@ -29,7 +29,7 @@ import { merchantCardEndpointFactsRefusal as sharedFactsRefusal, readMerchantCar
|
|
|
29
29
|
import { DATATRANS_CAPABILITY } from './datatrans.js';
|
|
30
30
|
import { PELECARD_CAPABILITY, PELECARD_PSP } from './pelecard.js';
|
|
31
31
|
import { PRIMER_CAPABILITY, PRIMER_PSP, primerTokenAnswer } from './primer.js';
|
|
32
|
-
import { PAYRAILS_CAPABILITY,
|
|
32
|
+
import { PAYRAILS_CAPABILITY, isPayrailsPaymentUrl, payrailsAnswer } from './payrails.js';
|
|
33
33
|
import { WSPAY_CAPABILITY, WSPAY_PSP } from './wspay.js';
|
|
34
34
|
import { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_FAST_POLL_MS, SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_REPLY_WINDOW_MS, secureAcceptanceCheckoutProfile, secureAcceptanceCheckoutRefusal, secureAcceptanceFrameAnswer, } from './cybersource-secure-acceptance.js';
|
|
35
35
|
/**
|
|
@@ -37,7 +37,7 @@ import { SECURE_ACCEPTANCE_CAPABILITY, SECURE_ACCEPTANCE_FAST_POLL_MS, SECURE_AC
|
|
|
37
37
|
* only recognizers in these modes, so a request this build cannot complete
|
|
38
38
|
* is never paused) and sent on every create. `device_encrypt`: the adapters
|
|
39
39
|
* capture the Payrails session key the page's init payload names (payrails.ts),
|
|
40
|
-
* and the cardholder's device encrypts and sends the save.
|
|
40
|
+
* and the cardholder's device encrypts and sends the save or card-form payment.
|
|
41
41
|
*/
|
|
42
42
|
export const SUPPORTED_MODES = ['token', 'cse', 'hosted_form', 'device_encrypt'];
|
|
43
43
|
/**
|
|
@@ -107,14 +107,17 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
107
107
|
* `primer_payment_instruments`: Primer's card tokenization, which this build
|
|
108
108
|
* judges before it pauses (primer.ts) and hands back to the page only as a
|
|
109
109
|
* single-use token. A merchant's client session can save the card after the
|
|
110
|
-
* payment with no sign in that request, so the API creates a Primer
|
|
111
|
-
* authorization only
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
110
|
+
* payment with no sign in that request, so the API creates a live Primer
|
|
111
|
+
* authorization only when it names the checkout page's https origin
|
|
112
|
+
* (checkout_origin, which this build sends from the page's origin): the
|
|
113
|
+
* approval says the merchant keeps the card on file, under the merchant name
|
|
114
|
+
* the create sent, which Agentcard cannot confirm, and pays only with a card
|
|
115
|
+
* the person stored. A refused create aborts the paused request with nothing
|
|
116
|
+
* sent. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused only
|
|
117
|
+
* after a sync.
|
|
118
|
+
* `payrails_device_encrypt`: Payrails' card save and card-form payment, which
|
|
119
|
+
* this build pauses only once it has bound the request to a captured session
|
|
120
|
+
* (payrails.ts). A gated
|
|
118
121
|
* recognizer never sits in BUILTIN_REGISTRY, so it is paused only after a sync.
|
|
119
122
|
* `converge_hpp_process`: Elavon Converge's Hosted Payment Page card request
|
|
120
123
|
* (elavon-converge.ts), paused in token mode and judged by the pre-claim gate.
|
|
@@ -166,8 +169,24 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
166
169
|
* finds a card in it (cybersource-secure-acceptance.ts). Served on the same terms,
|
|
167
170
|
* so every other company keeps the stop every build applies to a card posted to
|
|
168
171
|
* Secure Acceptance.
|
|
172
|
+
* `gravy_session_fields_put`: Gravy Secure Fields' update of a checkout session,
|
|
173
|
+
* the one card request a recognizer declares as a PUT. This build pauses that PUT
|
|
174
|
+
* from Gravy's controller frame as it pauses a POST (isRecognizerRequest), creates
|
|
175
|
+
* the authorization with the PUT, and answers the frame with Gravy's own answer to
|
|
176
|
+
* the cardholder's device. Served on the same terms; an older build never sees the
|
|
177
|
+
* entry, and every company Agentcard has not turned Gravy on for keeps today's path,
|
|
178
|
+
* where the request continues to Gravy untouched.
|
|
179
|
+
* `moneris_hosted_tokenization`: Moneris Hosted Tokenization's card request, the
|
|
180
|
+
* frame's form post to /HPPtoken/request.php. This build pauses it only when it
|
|
181
|
+
* carries the number, the expiry and the CVD the cardholder's device fills, fails a
|
|
182
|
+
* frame that leaves the expiry or CVD to the merchant's page in the browser with a
|
|
183
|
+
* `blocked` event, `request_without_card`, and answers with Moneris's token as the
|
|
184
|
+
* API recorded it. The device sends the card through the checkout relay. Served on
|
|
185
|
+
* the same terms: only to a build that lists it, for a company Agentcard turned
|
|
186
|
+
* Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
|
|
187
|
+
* only after a sync.
|
|
169
188
|
*/
|
|
170
|
-
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];
|
|
189
|
+
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'];
|
|
171
190
|
/**
|
|
172
191
|
* Processors whose paused request names no amount at all, so the pair the
|
|
173
192
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -185,12 +204,16 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
|
|
|
185
204
|
* it creates a token and charges nothing, and the merchant's server charges its
|
|
186
205
|
* own cart total later, so the agent's pair, which the prepared checkout already
|
|
187
206
|
* carries, is the total the cardholder approves, and the card that pays can spend
|
|
188
|
-
* no more than that (the API's card rule).
|
|
207
|
+
* no more than that (the API's card rule). Moneris Hosted Tokenization's
|
|
208
|
+
* request.php carries the card and no price: the merchant's server charges the
|
|
209
|
+
* token later, so the agent's pair, above zero (MONERIS_PSP below), is the one
|
|
210
|
+
* figure the approval shows.
|
|
189
211
|
*/
|
|
190
|
-
const
|
|
212
|
+
const MONERIS_PSP = 'moneris';
|
|
213
|
+
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP];
|
|
191
214
|
/** How AmountRequiredError names a processor. */
|
|
192
215
|
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
|
|
193
|
-
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance' };
|
|
216
|
+
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris' };
|
|
194
217
|
/**
|
|
195
218
|
* Processors whose request and answer name no amount at all, so the total the
|
|
196
219
|
* payment page shows (`pageAmount`) is the one figure that ties the agent's pair
|
|
@@ -400,6 +423,35 @@ const PLACEHOLDER_NUMBERS = Object.freeze({
|
|
|
400
423
|
discover: '6011000990139424',
|
|
401
424
|
jcb: '3566111111111113',
|
|
402
425
|
});
|
|
426
|
+
/**
|
|
427
|
+
* The placeholder number a processor needs in place of its network's default above, by processor
|
|
428
|
+
* (the recognizer's `psp`) and network. A processor whose page reads the typed number's IIN to
|
|
429
|
+
* pick a card product can read the default as another product than the card that pays:
|
|
430
|
+
* Worldline MyCheckout read 4111111111111111 as Visa Electron (122), not Visa (1), on CLZ's live
|
|
431
|
+
* page on 2026-10-12, and switched its form's product. The SDK refuses that submit
|
|
432
|
+
* (worldline-mycheckout.ts), so until this table names a Visa number Worldline reads as product 1,
|
|
433
|
+
* a Visa card cannot pay on a MyCheckout page that offers Visa Electron. The default table stays
|
|
434
|
+
* as it is: CyberSource Flex's Microform rule, ACI's brand check and EBANX's eligibility guard
|
|
435
|
+
* read its Visa and Mastercard numbers.
|
|
436
|
+
*
|
|
437
|
+
* TODO(Felipe): pick worldline_mycheckout's Visa number from Worldline Connect's own test-card
|
|
438
|
+
* list, then confirm it with one production IIN check on a MyCheckout card form that offers
|
|
439
|
+
* products 1 and 122: type it, wait for the IIN answer, read paymentProductId and the logo, clear
|
|
440
|
+
* the field, never Pay. Add it here as `worldline_mycheckout: { visa: '<number>' }`, with the
|
|
441
|
+
* check's date and merchant, and to the live runner's placeholder allowlist.
|
|
442
|
+
*/
|
|
443
|
+
const PROCESSOR_PLACEHOLDER_NUMBERS = Object.freeze({});
|
|
444
|
+
/**
|
|
445
|
+
* The placeholder number for a network (lowercase, as GET /api/v2/vault_cards names it) on a
|
|
446
|
+
* processor's page: the processor's own number for that network when `overrides` names one, else
|
|
447
|
+
* the network's default, else undefined for a network with no placeholder.
|
|
448
|
+
*/
|
|
449
|
+
export function placeholderNumber(brand, psp, overrides = PROCESSOR_PLACEHOLDER_NUMBERS) {
|
|
450
|
+
const own = psp !== undefined && Object.prototype.hasOwnProperty.call(overrides, psp) ? overrides[psp] : undefined;
|
|
451
|
+
if (own && Object.prototype.hasOwnProperty.call(own, brand))
|
|
452
|
+
return own[brand];
|
|
453
|
+
return Object.prototype.hasOwnProperty.call(PLACEHOLDER_NUMBERS, brand) ? PLACEHOLDER_NUMBERS[brand] : undefined;
|
|
454
|
+
}
|
|
403
455
|
/**
|
|
404
456
|
* The recognizer says this processor is preparation-required, and no preparation
|
|
405
457
|
* was passed. Its card may be sent only after the cardholder approved a
|
|
@@ -1162,8 +1214,9 @@ export class VaultClient {
|
|
|
1162
1214
|
}
|
|
1163
1215
|
/**
|
|
1164
1216
|
* The recogniser that claims a card request, or null; what a paused request names as
|
|
1165
|
-
* `recognizer`. A POST to a recognised endpoint,
|
|
1166
|
-
* GET (Worldpay eProtect) that
|
|
1217
|
+
* `recognizer`. A POST to a recognised endpoint, a PUT for a recognizer whose card request is
|
|
1218
|
+
* a PUT (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect) that
|
|
1219
|
+
* exact GET, query included.
|
|
1167
1220
|
*/
|
|
1168
1221
|
recognizerOf(url, method = 'POST') {
|
|
1169
1222
|
const rec = findRecognizer(url, this.mergedRegistry(), { sandbox: this.sandbox });
|
|
@@ -1189,7 +1242,8 @@ export class VaultClient {
|
|
|
1189
1242
|
// payrails_device_encrypt, converge_hpp_process, worldpay_eprotect_jsonp,
|
|
1190
1243
|
// wspay_confirm, shiji_sts_pinned_key, amadeus_checkout_actions.v1,
|
|
1191
1244
|
// worldline_mycheckout_form, verifone_card_capture, ekashu_hosted_payment_page,
|
|
1192
|
-
// cybersource_sa_token_create
|
|
1245
|
+
// cybersource_sa_token_create, gravy_session_fields_put,
|
|
1246
|
+
// moneris_hosted_tokenization) and
|
|
1193
1247
|
// the caller's company is one Agentcard turned that processor on for, so this
|
|
1194
1248
|
// build arms Fiserv's card capture, GMO's getToken, CyberSource Flex's card
|
|
1195
1249
|
// fields, Juspay's card flow, Datatrans's Secure Fields card post, the
|
|
@@ -1197,8 +1251,9 @@ export class VaultClient {
|
|
|
1197
1251
|
// CreditGuard's hosted page submit, Primer's card tokenization, Payrails' card
|
|
1198
1252
|
// save, Converge's payment page, Worldpay eProtect's registration, WSPay's
|
|
1199
1253
|
// card confirm, Shiji STS's card leg, Amadeus Checkout's card add,
|
|
1200
|
-
// Worldline MyCheckout's card form
|
|
1201
|
-
// create
|
|
1254
|
+
// Worldline MyCheckout's card form, CyberSource Secure Acceptance's token
|
|
1255
|
+
// create, Gravy's session fields PUT and Moneris Hosted
|
|
1256
|
+
// Tokenization's card request only for
|
|
1202
1257
|
// such a company, and an older build never receives a recognizer it cannot
|
|
1203
1258
|
// judge. Every other org's registry never recognizes a Flex, Juspay, Datatrans,
|
|
1204
1259
|
// Pelecard, ACI, CreditGuard, Primer, Payrails, Converge, eProtect, WSPay, Shiji STS, Amadeus or
|
|
@@ -1293,8 +1348,9 @@ export class VaultClient {
|
|
|
1293
1348
|
}
|
|
1294
1349
|
/**
|
|
1295
1350
|
* True when this request is a card tokenization we can take over: a POST to
|
|
1296
|
-
* a recognized endpoint,
|
|
1297
|
-
* (
|
|
1351
|
+
* a recognized endpoint, a PUT for a recognizer whose card request is a PUT
|
|
1352
|
+
* (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect)
|
|
1353
|
+
* that exact GET, query included.
|
|
1298
1354
|
*/
|
|
1299
1355
|
isCardRequest(url, method = 'POST') {
|
|
1300
1356
|
return this.recognizerOf(url, method) !== null;
|
|
@@ -1378,12 +1434,17 @@ export class VaultClient {
|
|
|
1378
1434
|
* preselects that card: without a pin the page preselects its own choice (its recommended
|
|
1379
1435
|
* card, or the first card the cardholder's device can open), which can be another card. After
|
|
1380
1436
|
* prepare(), pass the prepared checkout's `cardId`, the card the cardholder already approved.
|
|
1381
|
-
* The facts hold only while the cardholder pays with that card.
|
|
1382
|
-
*
|
|
1437
|
+
* The facts hold only while the cardholder pays with that card. `psp` names the processor
|
|
1438
|
+
* whose card form the agent types into (the recognizer's name, `worldline_mycheckout`): a
|
|
1439
|
+
* processor that needs its own number for the card's network gets it, and every other gets the
|
|
1440
|
+
* network's default. Throws PlaceholderCardError when there is no such card, its network has no
|
|
1441
|
+
* placeholder, or it carries no expiry.
|
|
1383
1442
|
*/
|
|
1384
1443
|
async placeholderCard(input) {
|
|
1385
1444
|
if (!input || typeof input.user !== 'string' || !input.user)
|
|
1386
1445
|
throw new Error('placeholderCard needs the user whose stored card will pay.');
|
|
1446
|
+
if (input.psp !== undefined && (typeof input.psp !== 'string' || !input.psp))
|
|
1447
|
+
throw new Error('placeholderCard takes psp as the processor\'s name, such as worldline_mycheckout.');
|
|
1387
1448
|
// The card list lives under /api/v2 only: the API aliases /v2/checkout alone without the prefix.
|
|
1388
1449
|
const list = await this.get(`/api/v2/vault_cards?user_id=${encodeURIComponent(input.user)}`, input.signal);
|
|
1389
1450
|
const cards = Array.isArray(list?.data) ? list.data : [];
|
|
@@ -1391,7 +1452,7 @@ export class VaultClient {
|
|
|
1391
1452
|
if (!card || typeof card !== 'object' || typeof card.id !== 'string' || !card.id)
|
|
1392
1453
|
throw new PlaceholderCardError('card_not_found');
|
|
1393
1454
|
const brand = typeof card.brand === 'string' ? card.brand.toLowerCase() : '';
|
|
1394
|
-
const number =
|
|
1455
|
+
const number = placeholderNumber(brand, input.psp);
|
|
1395
1456
|
if (!number)
|
|
1396
1457
|
throw new PlaceholderCardError('brand_unsupported');
|
|
1397
1458
|
const month = card.expiry_month, year = card.expiry_year;
|
|
@@ -1474,6 +1535,12 @@ export class VaultClient {
|
|
|
1474
1535
|
throw fail('secure_acceptance_profile_id_invalid');
|
|
1475
1536
|
if (secureAcceptance && !input.cardId)
|
|
1476
1537
|
throw fail('card_required');
|
|
1538
|
+
// Amadeus Checkout: Air France and KLM keep the expiry in their own form, where the agent types the
|
|
1539
|
+
// paying card's real expiry (placeholderCard), so the preparation names that card. The API refuses
|
|
1540
|
+
// one without it and approves no other card; refusing here asks nobody to approve it.
|
|
1541
|
+
const amadeus = input.psp === AMADEUS_CHECKOUT_PSP;
|
|
1542
|
+
if (amadeus && !input.cardId)
|
|
1543
|
+
throw fail('card_required');
|
|
1477
1544
|
// CyberSource Flex, tokenized before Pay: the loaded card field's capture context and fields,
|
|
1478
1545
|
// verified here against the keys payment-core pins before anything is created (the API and the
|
|
1479
1546
|
// cardholder's device verify them again).
|
|
@@ -1596,6 +1663,8 @@ export class VaultClient {
|
|
|
1596
1663
|
|| (eprotect && (state.paypage_id !== input.paypageId || state.card_id !== input.cardId))
|
|
1597
1664
|
// Secure Acceptance: the merchant profile and the card the cardholder approved are the ones asked for.
|
|
1598
1665
|
|| (secureAcceptance && (state.secure_acceptance_profile_id !== secureAcceptanceProfileId || state.card_id !== input.cardId))
|
|
1666
|
+
// Amadeus: the card the cardholder approved is the one whose expiry the agent typed.
|
|
1667
|
+
|| (amadeus && state.card_id !== input.cardId)
|
|
1599
1668
|
// The API says a preparation tokenizes before Pay exactly when this one carried a capture context.
|
|
1600
1669
|
|| (state.tokenize_before_pay === true) !== !!flexContext)
|
|
1601
1670
|
throw fail('ready_unconfirmed', id);
|
|
@@ -1863,7 +1932,12 @@ export class VaultClient {
|
|
|
1863
1932
|
// payrails_session_key_required.
|
|
1864
1933
|
const sidecar = input.request.payrails;
|
|
1865
1934
|
if (mode === 'device_encrypt' && (!sidecar || typeof sidecar.encryption_public_key !== 'string' || !sidecar.encryption_public_key)) {
|
|
1866
|
-
throw new Error('A Payrails card save needs request.payrails.encryption_public_key, the session key from the checkout\'s Payrails init payload (the adapters capture it). Nothing was created.');
|
|
1935
|
+
throw new Error('A Payrails card save or card-form payment needs request.payrails.encryption_public_key, the session key from the checkout\'s Payrails init payload (the adapters capture it). Nothing was created.');
|
|
1936
|
+
}
|
|
1937
|
+
// A card-form payment's ciphertext carries the session's holderReference, which its body does not.
|
|
1938
|
+
const payrailsPayment = mode === 'device_encrypt' && isPayrailsPaymentUrl(input.request.url);
|
|
1939
|
+
if (payrailsPayment && (typeof sidecar?.holder_reference !== 'string' || !sidecar.holder_reference)) {
|
|
1940
|
+
throw new Error('A Payrails card-form payment needs request.payrails.holder_reference, the holderReference from the checkout\'s Payrails init payload (the adapters capture it). Nothing was created.');
|
|
1867
1941
|
}
|
|
1868
1942
|
// A preparation that tokenized before Pay pays only a field request loaded under the capture
|
|
1869
1943
|
// context and fields its cardholder approved: the device holds CyberSource's token for that context.
|
|
@@ -1871,20 +1945,23 @@ export class VaultClient {
|
|
|
1871
1945
|
throw new CheckoutPreparationError(preparation.id, 'checkout_changed');
|
|
1872
1946
|
}
|
|
1873
1947
|
// Say no rather than coerce. Each recognizer's card request has exactly one
|
|
1874
|
-
// method (recognizerRequestMethod): a POST body,
|
|
1875
|
-
// GET whose query carries the card. The service stores
|
|
1876
|
-
// with that method, so a caller handing in another one
|
|
1877
|
-
// silently rewritten and the cardholder's device would replay
|
|
1878
|
-
// caller never asked for. A direct caller deserves to be told,
|
|
1879
|
-
// A card endpoint has no recognizer: its card request is its
|
|
1880
|
-
// Target Optical's PUT), which
|
|
1948
|
+
// method (recognizerRequestMethod): a POST body, Gravy's PUT body, or for
|
|
1949
|
+
// Worldpay eProtect the GET whose query carries the card. The service stores
|
|
1950
|
+
// the replay template with that method, so a caller handing in another one
|
|
1951
|
+
// would get it silently rewritten and the cardholder's device would replay
|
|
1952
|
+
// something the caller never asked for. A direct caller deserves to be told,
|
|
1953
|
+
// not surprised. A card endpoint has no recognizer: its card request is its
|
|
1954
|
+
// profile's own method (a POST, or Target Optical's PUT), which
|
|
1955
|
+
// admitCardEndpoint already held the request to.
|
|
1881
1956
|
const method = (input.request.method ?? cardEndpoint?.method ?? 'POST').toUpperCase();
|
|
1882
1957
|
const cardMethod = rec ? recognizerRequestMethod(rec) : cardEndpoint ? cardEndpoint.method : 'POST';
|
|
1883
1958
|
if (method !== cardMethod) {
|
|
1884
1959
|
throw new Error(cardMethod === 'GET'
|
|
1885
1960
|
? `a ${rec?.psp} card registration is a GET; got ${method} for ${redactUrl(input.request.url)}.`
|
|
1886
|
-
:
|
|
1887
|
-
|
|
1961
|
+
: cardMethod === 'PUT' && rec
|
|
1962
|
+
? `a ${rec.psp} card request is a PUT; got ${method} for ${redactUrl(input.request.url)}.`
|
|
1963
|
+
: `checkout authorization is POST-only; got ${method} for ${redactUrl(input.request.url)}. ` +
|
|
1964
|
+
'A non-POST tokenizer needs recognizer support before it can be intercepted.');
|
|
1888
1965
|
}
|
|
1889
1966
|
// A GET card request is its URL alone. This build completes one: Worldpay
|
|
1890
1967
|
// eProtect's registration exactly as payment-core admits it, with no body.
|
|
@@ -1915,6 +1992,10 @@ export class VaultClient {
|
|
|
1915
1992
|
// amount_required.
|
|
1916
1993
|
if (!hasAmount && rec && AGENT_AMOUNT_PROCESSORS.includes(rec.psp))
|
|
1917
1994
|
throw new AmountRequiredError(rec.psp);
|
|
1995
|
+
// A Moneris token is charged later for whatever the merchant's server decides, so a zero pair
|
|
1996
|
+
// would show the cardholder no price at all: it is refused like a missing one.
|
|
1997
|
+
if (hasAmount && rec && rec.psp === MONERIS_PSP && Number(input.amount) <= 0)
|
|
1998
|
+
throw new AmountRequiredError(rec.psp);
|
|
1918
1999
|
// Nor is the agent's pair tied to the page by anything but the total the
|
|
1919
2000
|
// page shows: without it the cardholder could approve one figure while the
|
|
1920
2001
|
// page charges another. The API refuses the same create as page_amount_required.
|
|
@@ -1924,7 +2005,8 @@ export class VaultClient {
|
|
|
1924
2005
|
// later, under another session), so the price the cardholder approves is
|
|
1925
2006
|
// the one sent here: the agent's pair or else the page total. Refused
|
|
1926
2007
|
// before any create or prompt, the way the API refuses amount_required.
|
|
1927
|
-
|
|
2008
|
+
// A card-form payment names the amount it authorizes, which the API reads.
|
|
2009
|
+
if (mode === 'device_encrypt' && !payrailsPayment && !hasAmount && !input.pageAmount) {
|
|
1928
2010
|
throw new Error('A Payrails card save names no amount, so pass amount and currency (or let the adapter read the page total): that is the amount the cardholder approves. Nothing was created and nothing was charged.');
|
|
1929
2011
|
}
|
|
1930
2012
|
const timeoutMs = input.timeoutMs ?? 15 * 60_000;
|
|
@@ -1996,8 +2078,10 @@ export class VaultClient {
|
|
|
1996
2078
|
microform: { version: input.request.microform.version, fields: [...input.request.microform.fields],
|
|
1997
2079
|
...(input.request.microform.trigger ? { trigger: input.request.microform.trigger } : {}) },
|
|
1998
2080
|
} : {}),
|
|
1999
|
-
// Only the key: the token already rides in the
|
|
2000
|
-
|
|
2081
|
+
// Only the key, and a card-form payment's holderReference: the token already rides in the
|
|
2082
|
+
// passthrough authorization header.
|
|
2083
|
+
...(mode === 'device_encrypt' && sidecar ? { payrails: { encryption_public_key: sidecar.encryption_public_key,
|
|
2084
|
+
...(payrailsPayment ? { holder_reference: sidecar.holder_reference } : {}) } } : {}),
|
|
2001
2085
|
},
|
|
2002
2086
|
};
|
|
2003
2087
|
try {
|
|
@@ -2063,7 +2147,13 @@ export class VaultClient {
|
|
|
2063
2147
|
// then at the caller's interval; at the default 2 seconds the first read alone would miss it.
|
|
2064
2148
|
// The API's bind names 100 ms as such a row's poll interval, so slow_down admits these reads
|
|
2065
2149
|
// (as an eProtect row names the 500 ms read above).
|
|
2066
|
-
|
|
2150
|
+
// A prepared Bambora token create too: Custom Checkout's card frame gives its tokenization
|
|
2151
|
+
// request 5 seconds from Pay, and the cardholder's device answers a prepared token 1.5 to 4
|
|
2152
|
+
// seconds after this bind. Read every 2 seconds, an answer later than the first read waited
|
|
2153
|
+
// for the read 4 seconds after the bind, at or past the frame's timer; read every 100 ms, it
|
|
2154
|
+
// reaches the frame within about 0.2 seconds of the device's answer. The API's bind names
|
|
2155
|
+
// 100 ms on such a row too.
|
|
2156
|
+
const preparedTightPollUntil = preparation?.cybersourceFlex || preparation?.psp === 'bambora' ? Date.now() + 10_000 : 0;
|
|
2067
2157
|
let execution = {};
|
|
2068
2158
|
let approvalDelivered = false;
|
|
2069
2159
|
const deliverApproval = (state) => {
|
|
@@ -2112,7 +2202,7 @@ export class VaultClient {
|
|
|
2112
2202
|
if (stopSignal?.aborted)
|
|
2113
2203
|
throw new PaymentOutcomeUnknownError(authorizationId, input.merchantSignal?.aborted ? 'merchant_request_aborted' : 'local_cancel');
|
|
2114
2204
|
const pollInterval = Date.now() < preparedFastPollUntil ? Math.min(this.pollIntervalMs, 500)
|
|
2115
|
-
: Date.now() <
|
|
2205
|
+
: Date.now() < preparedTightPollUntil ? Math.min(this.pollIntervalMs, 100) : this.pollIntervalMs;
|
|
2116
2206
|
// A Secure Acceptance read also wakes when its reply window closes (Infinity for every other processor).
|
|
2117
2207
|
await interruptibleSleep(Math.min(pollInterval, Math.max(0, deadline - Date.now()), Math.max(0, secureAcceptanceReplyBy - Date.now())), stopSignal);
|
|
2118
2208
|
if (stopSignal?.aborted)
|
|
@@ -2247,16 +2337,17 @@ export class VaultClient {
|
|
|
2247
2337
|
fields: { ...sub.fields },
|
|
2248
2338
|
...(removeRaw ? { remove: [...removeRaw] } : {}),
|
|
2249
2339
|
},
|
|
2250
|
-
...(shiji ? { shijiStsCompanion: Object.freeze({
|
|
2340
|
+
...(shiji ? { shijiStsCompanion: Object.freeze({ keySequenceSha256: Object.freeze([...s.shiji_sts_companion.key_sequence_sha256]) }) } : {}),
|
|
2251
2341
|
...amountAuthority,
|
|
2252
2342
|
...execution,
|
|
2253
2343
|
};
|
|
2254
2344
|
}
|
|
2255
2345
|
if (approvedMode === 'device_encrypt') {
|
|
2256
2346
|
// The device sent the card to Payrails itself; this is Payrails' answer to it. It
|
|
2257
|
-
// enters the page only as a 201 for a card instrument,
|
|
2258
|
-
// allow-list under a JSON
|
|
2259
|
-
//
|
|
2347
|
+
// enters the page only as a 201 for a card instrument or, for a card-form payment, a 2xx
|
|
2348
|
+
// naming the authorize action, rebuilt from payment-core's allow-list under a JSON
|
|
2349
|
+
// content type (payrails.ts payrailsAnswer, by the paused URL). Anything else is never
|
|
2350
|
+
// handed over: the card may be saved or paying, so the outcome is unknown, not declined.
|
|
2260
2351
|
// Autopilot never runs this mode, so an autopilot answer is not one to act on either.
|
|
2261
2352
|
if (execution.executionMode === 'autopilot')
|
|
2262
2353
|
throw new PaymentOutcomeUnknownError(authorizationId, 'autopilot_submission_mode_invalid');
|
|
@@ -2264,7 +2355,7 @@ export class VaultClient {
|
|
|
2264
2355
|
throw new PaymentOutcomeUnknownError(authorizationId, 'approved_mode_unsupported');
|
|
2265
2356
|
const saved = s.response;
|
|
2266
2357
|
const answer = saved && typeof saved === 'object' && Number.isInteger(saved.status) && typeof saved.body === 'string'
|
|
2267
|
-
?
|
|
2358
|
+
? payrailsAnswer(saved.status, saved.body, input.request.url) : null;
|
|
2268
2359
|
if (!answer)
|
|
2269
2360
|
throw new PaymentOutcomeUnknownError(authorizationId, 'payrails_answer_unconfirmed');
|
|
2270
2361
|
if (s.processor_context !== undefined)
|
|
@@ -54,7 +54,7 @@ declare function parseCreditguardTransaction(body: any, txId: any): {
|
|
|
54
54
|
personalIdAlwaysEnabled: boolean;
|
|
55
55
|
inputs: any;
|
|
56
56
|
} | null;
|
|
57
|
-
declare function creditguardTransactionRefusal(txn: any, expectation: any): "otp" | "keep_on_file" | "dcc" | "installments" | "personal_id" | "jwe_unsupported" | "transaction_type" | "amount_mismatch" | "environment_mismatch" | "currency_mismatch" | "transaction_unreadable" | "three_d_secure" | "form_inputs" | "card_field_hidden" | "
|
|
57
|
+
declare function creditguardTransactionRefusal(txn: any, expectation: any): "otp" | "keep_on_file" | "dcc" | "installments" | "personal_id" | "jwe_unsupported" | "transaction_type" | "amount_mismatch" | "environment_mismatch" | "currency_mismatch" | "transaction_unreadable" | "three_d_secure" | "form_inputs" | "card_field_hidden" | "jwe_public_key_malformed" | null;
|
|
58
58
|
declare function creditguardSendWindowRefusal(timing: any): "timing_unknown" | "page_expired" | "recaptcha_expired" | null;
|
|
59
59
|
declare var CREDITGUARD_REFUSAL_CODES: readonly string[];
|
|
60
60
|
declare function creditguardResponseOutcome(status: any, body: any, options: any): {
|
|
@@ -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: 58593275ccbce7e76a1aa68065eb63063cdf0ed00d533706bec557cb8a21cbc8
|
|
4
4
|
// src/creditguard.js
|
|
5
5
|
var CREDITGUARD_PPS_HOSTS = Object.freeze(["pps.creditguard.co.il", "pps2.creditguard.co.il", "ppsuat.creditguard.co.il"]);
|
|
6
6
|
var CREDITGUARD_SANDBOX_HOST = "ppsuat.creditguard.co.il";
|
|
@@ -572,8 +572,6 @@ function creditguardTransactionRefusal(txn, expectation) {
|
|
|
572
572
|
const form = formInputsRefusal(txn.inputs);
|
|
573
573
|
if (form)
|
|
574
574
|
return form;
|
|
575
|
-
if (txn.remainingSubmitRetries === null || txn.remainingSubmitRetries === 0)
|
|
576
|
-
return "no_retries_left";
|
|
577
575
|
const environment = creditguardPreparationEnvironment(expectation.hostname);
|
|
578
576
|
if (environment === null || environment === "sandbox" !== (txn.envType === "test") || typeof expectation.sandbox !== "boolean" || expectation.sandbox !== (environment === "sandbox"))
|
|
579
577
|
return "environment_mismatch";
|
package/dist/cse-body.d.ts
CHANGED
|
@@ -13,12 +13,13 @@ import type { PreparedCheckout, ReplayResponse } from './client.js';
|
|
|
13
13
|
* always a prepared one (authorize() requires it), so its preparation names
|
|
14
14
|
* the processor and the key pin the cardholder approved. It continues only a
|
|
15
15
|
* request on Fiserv's capture hosts, and only when that capture's own pin (its
|
|
16
|
-
* envelope's key,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* a Fiserv capture host. Every
|
|
16
|
+
* envelope's key, or for a credentials session's key its merchant's, pinned for
|
|
17
|
+
* its endpoint and its merchant) is the approved pin; then Fiserv's own
|
|
18
|
+
* substitution writes only the envelope's four members at source.encryptionData
|
|
19
|
+
* of the live paused body, and throws for a body that is not the capture
|
|
20
|
+
* Agentcard covers or an envelope under another key than that pin's
|
|
21
|
+
* (fiserv.ts). Any other approval never meets a Fiserv capture host. Every
|
|
22
|
+
* refusal throws before anything is sent.
|
|
22
23
|
*/
|
|
23
24
|
export declare function cseBody(body: string, replay: Extract<ReplayResponse, {
|
|
24
25
|
mode: 'cse';
|
package/dist/cse-body.js
CHANGED
|
@@ -18,12 +18,13 @@ import { substituteVerifoneBody } from './verifone.js';
|
|
|
18
18
|
* always a prepared one (authorize() requires it), so its preparation names
|
|
19
19
|
* the processor and the key pin the cardholder approved. It continues only a
|
|
20
20
|
* request on Fiserv's capture hosts, and only when that capture's own pin (its
|
|
21
|
-
* envelope's key,
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* a Fiserv capture host. Every
|
|
21
|
+
* envelope's key, or for a credentials session's key its merchant's, pinned for
|
|
22
|
+
* its endpoint and its merchant) is the approved pin; then Fiserv's own
|
|
23
|
+
* substitution writes only the envelope's four members at source.encryptionData
|
|
24
|
+
* of the live paused body, and throws for a body that is not the capture
|
|
25
|
+
* Agentcard covers or an envelope under another key than that pin's
|
|
26
|
+
* (fiserv.ts). Any other approval never meets a Fiserv capture host. Every
|
|
27
|
+
* refusal throws before anything is sent.
|
|
27
28
|
*/
|
|
28
29
|
export function cseBody(body, replay, url, preparation) {
|
|
29
30
|
// A Verifone approval is always a prepared one for the profile it pays, and continues only the
|
|
@@ -39,7 +40,7 @@ export function cseBody(body, replay, url, preparation) {
|
|
|
39
40
|
if (replay.kind !== undefined || !isFiservCaptureHost(url) || fiservCapturePinId(url, body) !== preparation.merchantProfile) {
|
|
40
41
|
throw new Error('Refusing to substitute: the paused request is not a Fiserv capture under the key pin the cardholder approved.');
|
|
41
42
|
}
|
|
42
|
-
return substituteFiservEnvelope(body, replay.substitutions);
|
|
43
|
+
return substituteFiservEnvelope(body, replay.substitutions, url);
|
|
43
44
|
}
|
|
44
45
|
if (isFiservCaptureHost(url))
|
|
45
46
|
throw new Error('Refusing to substitute: a Fiserv capture continues only through a prepared Fiserv approval.');
|
|
@@ -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: fdee611015d42dc0a679e93a64766ac971b190fcc8f17a107d761c414569e5f9
|
|
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";
|
|
@@ -178,15 +178,18 @@ function judgeBody(body) {
|
|
|
178
178
|
if (!pairs)
|
|
179
179
|
return null;
|
|
180
180
|
const fields = new Map(pairs.map(({ key, value }) => [key, value]));
|
|
181
|
+
const merchantHeldExpiry = !fields.get("expm") && !fields.get("expy") && fields.has("expm") === fields.has("expy") && fields.get("usage") !== "REGISTRATION";
|
|
181
182
|
for (const [key, value] of fields) {
|
|
182
183
|
if (!KEYS.has(key))
|
|
183
184
|
return null;
|
|
185
|
+
if (merchantHeldExpiry && (key === "expm" || key === "expy"))
|
|
186
|
+
continue;
|
|
184
187
|
if (EMPTY_ONLY.has(key) ? value !== "" : !RULES[key](value))
|
|
185
188
|
return null;
|
|
186
189
|
}
|
|
187
|
-
if (!REQUIRED.every((key) => fields.has(key)))
|
|
190
|
+
if (!REQUIRED.every((key) => fields.has(key) || merchantHeldExpiry && (key === "expm" || key === "expy")))
|
|
188
191
|
return null;
|
|
189
|
-
return { pairs, fields, usage: fields.get("usage") ?? "" };
|
|
192
|
+
return { pairs, fields, usage: fields.get("usage") ?? "", merchantHeldExpiry };
|
|
190
193
|
}
|
|
191
194
|
function datatransRegistrationRefusal(usage, card, policy = DATATRANS_REGISTRATION_POLICY) {
|
|
192
195
|
if (usage !== "REGISTRATION")
|
|
@@ -319,14 +322,14 @@ function injectDatatransCard(body, card, policy = DATATRANS_REGISTRATION_POLICY)
|
|
|
319
322
|
if (!fields.get("cvv") || !digits(card.cvc, 3, 4))
|
|
320
323
|
throw new Error(FILL_REFUSAL);
|
|
321
324
|
const yy = String(year % 100).padStart(2, "0");
|
|
322
|
-
const values = {
|
|
325
|
+
const values = judged.merchantHeldExpiry ? { cardNumber: card.number, cvv: card.cvc } : {
|
|
323
326
|
cardNumber: card.number,
|
|
324
327
|
cvv: card.cvc,
|
|
325
328
|
expm: fields.get("expm").length === 2 ? String(month).padStart(2, "0") : String(month),
|
|
326
329
|
expy: fields.get("expy").length === 4 ? `20${yy}` : yy
|
|
327
330
|
};
|
|
328
331
|
const written = judged.pairs.map((pair) => owns(values, pair.key) ? `${pair.rawKey}=${encodeDatatransFormValue(values[pair.key])}` : `${pair.rawKey}=${pair.raw}`);
|
|
329
|
-
return { body: written.join("&"), where:
|
|
332
|
+
return { body: written.join("&"), where: Object.keys(values) };
|
|
330
333
|
}
|
|
331
334
|
var TRANSACTION_ID = /^[0-9]{18}$/;
|
|
332
335
|
var CARD_WORD = /^[A-Za-z][A-Za-z _-]{0,31}$/;
|
|
@@ -7,9 +7,12 @@ export declare const CONVERGE_CAPABILITY = "converge_hpp_process";
|
|
|
7
7
|
* the device refuses with, so an integrator reads one code whichever of them refused.
|
|
8
8
|
* - A shape the Vault never pays: `converge_wallet_refused`, `converge_debit_card_refused`,
|
|
9
9
|
* `converge_push_to_card_refused`, `converge_gift_card_refused`, `converge_payment_type_refused`,
|
|
10
|
-
* `
|
|
10
|
+
* `converge_service_fee_refused`, `converge_recurring_refused`,
|
|
11
11
|
* `converge_card_saving_refused` (ssl_get_token or ssl_add_token), `converge_amount_add_on_refused`
|
|
12
12
|
* (a tip or cash back) and `converge_currency_refused` (anything but US dollars).
|
|
13
|
+
* - A CAPTCHA answer Google never issued: `converge_captcha_refused` (a captchaToken that is not a
|
|
14
|
+
* non-empty string of at most 4,096 URL-safe characters). A page with its CAPTCHA on pauses like
|
|
15
|
+
* any other once a person solved it, and the answer rides to Converge unchanged.
|
|
13
16
|
* - A follow-up after a step the Vault cannot complete: `converge_3ds_follow_up_refused` (3-D
|
|
14
17
|
* Secure or Pay Defense) and `converge_surcharge_refused`.
|
|
15
18
|
* - A body the cardholder has nothing to approve on: `converge_amount_missing` and
|
|
@@ -20,7 +23,7 @@ export declare const CONVERGE_CAPABILITY = "converge_hpp_process";
|
|
|
20
23
|
*/
|
|
21
24
|
export declare const CONVERGE_REFUSAL_REASONS: readonly ["converge_body_malformed", "converge_payment_type_refused", "converge_debit_card_refused", "converge_push_to_card_refused", "converge_gift_card_refused", "converge_credential_refused", "converge_wallet_refused", "converge_captcha_refused", "converge_service_fee_refused", "converge_3ds_follow_up_refused", "converge_surcharge_refused", "converge_amount_add_on_refused", "converge_card_saving_refused", "converge_recurring_refused", "converge_currency_refused", "converge_amount_missing", "converge_context_missing", "converge_card_missing", "converge_card_malformed", "converge_url_refused", "converge_method_refused", "converge_content_type_refused"];
|
|
22
25
|
export type ConvergeRefusalReason = typeof CONVERGE_REFUSAL_REASONS[number];
|
|
23
|
-
/** True when the URL's host is one of Converge's
|
|
26
|
+
/** True when the URL's host is one of Converge's three payment page hosts: api, www and the demo. */
|
|
24
27
|
export declare function isConvergeHost(url: string): boolean;
|
|
25
28
|
/**
|
|
26
29
|
* The pause decision for a card-carrying POST the registry recognized on a Converge host. Null
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 136e1c2b0fd2bdee0cfbf5efbbfaf1a92ace7d1cc52e17a3fd5a4584370e5e53
|
|
4
4
|
// src/elavon-converge.js
|
|
5
|
-
var CONVERGE_HOSTS = /* @__PURE__ */ Object.freeze(["api.convergepay.com", "api.demo.convergepay.com"]);
|
|
5
|
+
var CONVERGE_HOSTS = /* @__PURE__ */ Object.freeze(["api.convergepay.com", "api.demo.convergepay.com", "www.convergepay.com"]);
|
|
6
6
|
var CONVERGE_DEMO_HOST = "api.demo.convergepay.com";
|
|
7
7
|
var CONVERGE_PROCESS_PATH = "/hosted-payments/service/payment/hpp/process";
|
|
8
8
|
var CONVERGE_PROCESS_URLS = /* @__PURE__ */ Object.freeze([
|
|
9
9
|
"https://api.convergepay.com/hosted-payments/service/payment/hpp/process",
|
|
10
|
-
"https://api.demo.convergepay.com/hosted-payments/service/payment/hpp/process"
|
|
10
|
+
"https://api.demo.convergepay.com/hosted-payments/service/payment/hpp/process",
|
|
11
|
+
"https://www.convergepay.com/hosted-payments/service/payment/hpp/process"
|
|
11
12
|
]);
|
|
12
13
|
var CONVERGE_REFUSAL_CODES = /* @__PURE__ */ Object.freeze([
|
|
13
14
|
"converge_body_malformed",
|
|
@@ -72,15 +73,18 @@ var RECURRING_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
|
72
73
|
"ssl_recurring_batch_count",
|
|
73
74
|
"ssl_installment_id"
|
|
74
75
|
]);
|
|
76
|
+
var ONE_TIME_RECURRING_FLAG = "false";
|
|
75
77
|
var CARD_SAVING_FLAGS = /* @__PURE__ */ Object.freeze(["ssl_get_token", "ssl_add_token"]);
|
|
76
78
|
var CREDENTIAL_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_account_id", "ssl_merchant_id", "ssl_user_id", "ssl_pin", "ssl_vendor_id"]);
|
|
77
79
|
var AMOUNT_ADD_ON_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_tip_amount", "ssl_cashback_amount"]);
|
|
80
|
+
var SOLVED_CAPTCHA = /^[A-Za-z0-9_-]{1,4096}$/;
|
|
78
81
|
var CARD_NUMBER = /^\d{12,19}$/;
|
|
79
82
|
var EXPIRY_MMYY = /^(0[1-9]|1[0-2])\d{2}$/;
|
|
80
83
|
var CVC = /^\d{3,4}$/;
|
|
81
84
|
var AMOUNT = /^(\d{1,13})(?:\.(\d{1,2}))?$/;
|
|
82
85
|
var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
83
86
|
var owns = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
87
|
+
var recurringRequested = (data) => RECURRING_FIELDS.some((key) => owns(data, key) && !(key === "recurring" && data[key] === ONE_TIME_RECURRING_FLAG));
|
|
84
88
|
var refuse = (code) => Object.freeze({ verdict: "refuse", code });
|
|
85
89
|
var CARD = /* @__PURE__ */ Object.freeze({ verdict: "card" });
|
|
86
90
|
var CONTINUE = /* @__PURE__ */ Object.freeze({ verdict: "continue" });
|
|
@@ -129,7 +133,7 @@ function convergeBodyVerdict(body) {
|
|
|
129
133
|
return refuse("converge_credential_refused");
|
|
130
134
|
if (WALLET_FIELDS.some((key) => owns(data, key)))
|
|
131
135
|
return refuse("converge_wallet_refused");
|
|
132
|
-
if (owns(parsed, "captchaToken"))
|
|
136
|
+
if (owns(parsed, "captchaToken") && !(typeof parsed.captchaToken === "string" && SOLVED_CAPTCHA.test(parsed.captchaToken)))
|
|
133
137
|
return refuse("converge_captcha_refused");
|
|
134
138
|
if (SERVICE_FEE_FIELDS.some((key) => owns(data, key)))
|
|
135
139
|
return refuse("converge_service_fee_refused");
|
|
@@ -142,7 +146,7 @@ function convergeBodyVerdict(body) {
|
|
|
142
146
|
if (owns(data, "ssl_token") || CARD_SAVING_FLAGS.some((key) => owns(data, key) && !(typeof data[key] === "string" && /^n$/i.test(data[key])))) {
|
|
143
147
|
return refuse("converge_card_saving_refused");
|
|
144
148
|
}
|
|
145
|
-
if (
|
|
149
|
+
if (recurringRequested(data))
|
|
146
150
|
return refuse("converge_recurring_refused");
|
|
147
151
|
if (owns(data, "ssl_transaction_currency") && !(typeof data.ssl_transaction_currency === "string" && /^usd$/i.test(data.ssl_transaction_currency))) {
|
|
148
152
|
return refuse("converge_currency_refused");
|
package/dist/elavon-converge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Elavon Converge Hosted Payment Page (psp `elavon_converge`): the page NY DMV's payment apps
|
|
2
|
-
// redirect to, api.convergepay.com/hosted-payments/?ssl_txn_auth_token=<one-time token
|
|
3
|
-
//
|
|
2
|
+
// redirect to, api.convergepay.com/hosted-payments/?ssl_txn_auth_token=<one-time token>. Every
|
|
3
|
+
// Converge Buy Button and pay link opens the same page on www.convergepay.com, and the demo
|
|
4
|
+
// environment serves it on api.demo.convergepay.com. Converge's own AngularJS
|
|
4
5
|
// app posts the card in one same-origin JSON XHR, POST /hosted-payments/service/payment/hpp/process,
|
|
5
6
|
// with the card at paymentData.ssl_card_number, ssl_exp_date (MMYY) and ssl_cvv2cvc2, the amount
|
|
6
7
|
// at paymentData.ssl_amount, and the page's rolling session in `context`. The same URL also takes
|
|
@@ -24,9 +25,12 @@ export const CONVERGE_CAPABILITY = 'converge_hpp_process';
|
|
|
24
25
|
* the device refuses with, so an integrator reads one code whichever of them refused.
|
|
25
26
|
* - A shape the Vault never pays: `converge_wallet_refused`, `converge_debit_card_refused`,
|
|
26
27
|
* `converge_push_to_card_refused`, `converge_gift_card_refused`, `converge_payment_type_refused`,
|
|
27
|
-
* `
|
|
28
|
+
* `converge_service_fee_refused`, `converge_recurring_refused`,
|
|
28
29
|
* `converge_card_saving_refused` (ssl_get_token or ssl_add_token), `converge_amount_add_on_refused`
|
|
29
30
|
* (a tip or cash back) and `converge_currency_refused` (anything but US dollars).
|
|
31
|
+
* - A CAPTCHA answer Google never issued: `converge_captcha_refused` (a captchaToken that is not a
|
|
32
|
+
* non-empty string of at most 4,096 URL-safe characters). A page with its CAPTCHA on pauses like
|
|
33
|
+
* any other once a person solved it, and the answer rides to Converge unchanged.
|
|
30
34
|
* - A follow-up after a step the Vault cannot complete: `converge_3ds_follow_up_refused` (3-D
|
|
31
35
|
* Secure or Pay Defense) and `converge_surcharge_refused`.
|
|
32
36
|
* - A body the cardholder has nothing to approve on: `converge_amount_missing` and
|
|
@@ -59,7 +63,7 @@ export const CONVERGE_REFUSAL_REASONS = [
|
|
|
59
63
|
'converge_method_refused',
|
|
60
64
|
'converge_content_type_refused',
|
|
61
65
|
];
|
|
62
|
-
/** True when the URL's host is one of Converge's
|
|
66
|
+
/** True when the URL's host is one of Converge's three payment page hosts: api, www and the demo. */
|
|
63
67
|
export function isConvergeHost(url) {
|
|
64
68
|
try {
|
|
65
69
|
return CONVERGE_HOSTS.includes(new URL(url).hostname);
|