@agent-cards/sdk 0.23.10 → 0.23.12
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 +20 -2
- package/PREFLIGHT.md +2 -0
- package/README.md +41 -19
- package/dist/card-fields.generated.js +74 -2
- package/dist/cdp.js +35 -7
- package/dist/client.d.ts +32 -8
- package/dist/client.js +164 -19
- package/dist/elavon-converge.d.ts +4 -1
- package/dist/elavon-converge.generated.js +3 -2
- package/dist/elavon-converge.js +4 -1
- package/dist/lifecycle.d.ts +9 -1
- package/dist/lifecycle.js +34 -0
- package/dist/paddle-vgs.generated.d.ts +50 -0
- package/dist/paddle-vgs.generated.js +289 -0
- package/dist/payrails.generated.js +8 -12
- package/dist/pre-claim.d.ts +7 -0
- package/dist/pre-claim.js +47 -7
- package/dist/prepared-processor.d.ts +12 -4
- package/dist/prepared-processor.js +17 -5
- package/dist/primer.generated.js +90 -18
- package/dist/stripe-checkout.generated.js +59 -2
- 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/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -14,6 +14,7 @@ import { creditguardPageProcessUrl, creditguardPpDomainHost, creditguardPreparat
|
|
|
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
16
|
import { AMADEUS_CHECKOUT_CAPABILITY, AMADEUS_CHECKOUT_PSP } from './amadeus-checkout.generated.js';
|
|
17
|
+
import { PADDLE_VGS_CAPABILITY, PADDLE_VGS_PSP } from './paddle-vgs.generated.js';
|
|
17
18
|
import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
18
19
|
import { VoiceModule } from './voice.js';
|
|
19
20
|
import { BuyModule } from './buy.js';
|
|
@@ -185,8 +186,16 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
|
|
|
185
186
|
* the same terms: only to a build that lists it, for a company Agentcard turned
|
|
186
187
|
* Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
|
|
187
188
|
* only after a sync.
|
|
189
|
+
*
|
|
190
|
+
* `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
|
|
191
|
+
* vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
|
|
192
|
+
* checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
|
|
193
|
+
* and this build answers that second request from the same approval. The cardholder's
|
|
194
|
+
* device sends the card itself and the frame gets back the stored card's id alone.
|
|
195
|
+
* Served on the same terms: only to a build that lists it, for a company Agentcard
|
|
196
|
+
* turned Paddle on for.
|
|
188
197
|
*/
|
|
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'];
|
|
198
|
+
export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CYBERSOURCE_FLEX_CAPABILITY, JUSPAY_CAPABILITY, DATATRANS_CAPABILITY, 'authorize_net_dpm_form', PELECARD_CAPABILITY, ACI_CAPABILITY, 'creditguard_pps_process', PRIMER_CAPABILITY, PAYRAILS_CAPABILITY, CONVERGE_CAPABILITY, EPROTECT_CAPABILITY, WSPAY_CAPABILITY, SHIJI_STS_CAPABILITY, AMADEUS_CHECKOUT_CAPABILITY, 'worldline_mycheckout_form', 'verifone_card_capture', 'ekashu_hosted_payment_page', SECURE_ACCEPTANCE_CAPABILITY, 'gravy_session_fields_put', 'moneris_hosted_tokenization', PADDLE_VGS_CAPABILITY];
|
|
190
199
|
/**
|
|
191
200
|
* Processors whose paused request names no amount at all, so the pair the
|
|
192
201
|
* agent passes is the amount the cardholder approves: authorize() refuses one
|
|
@@ -207,13 +216,15 @@ export const SUPPORTED_CAPABILITIES = ['fiserv_card_capture', GMO_CAPABILITY, CY
|
|
|
207
216
|
* no more than that (the API's card rule). Moneris Hosted Tokenization's
|
|
208
217
|
* request.php carries the card and no price: the merchant's server charges the
|
|
209
218
|
* token later, so the agent's pair, above zero (MONERIS_PSP below), is the one
|
|
210
|
-
* figure the approval shows.
|
|
219
|
+
* figure the approval shows. Paddle VGS's card request carries none either: Paddle
|
|
220
|
+
* charges the stored card at /pay for what the checkout says, so the agent's pair,
|
|
221
|
+
* which the prepared checkout carries, is the total the cardholder approves.
|
|
211
222
|
*/
|
|
212
223
|
const MONERIS_PSP = 'moneris';
|
|
213
|
-
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP];
|
|
224
|
+
const AGENT_AMOUNT_PROCESSORS = [GMO_PSP, PELECARD_PSP, WSPAY_PSP, SECURE_ACCEPTANCE_PSP, MONERIS_PSP, PADDLE_VGS_PSP];
|
|
214
225
|
/** How AmountRequiredError names a processor. */
|
|
215
226
|
const PROCESSOR_NAMES = { [GMO_PSP]: 'GMO', [PELECARD_PSP]: 'Pelecard', [WSPAY_PSP]: 'WSPay',
|
|
216
|
-
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris' };
|
|
227
|
+
[SECURE_ACCEPTANCE_PSP]: 'CyberSource Secure Acceptance', [MONERIS_PSP]: 'Moneris', [PADDLE_VGS_PSP]: 'Paddle' };
|
|
217
228
|
/**
|
|
218
229
|
* Processors whose request and answer name no amount at all, so the total the
|
|
219
230
|
* payment page shows (`pageAmount`) is the one figure that ties the agent's pair
|
|
@@ -423,6 +434,35 @@ const PLACEHOLDER_NUMBERS = Object.freeze({
|
|
|
423
434
|
discover: '6011000990139424',
|
|
424
435
|
jcb: '3566111111111113',
|
|
425
436
|
});
|
|
437
|
+
/**
|
|
438
|
+
* The placeholder number a processor needs in place of its network's default above, by processor
|
|
439
|
+
* (the recognizer's `psp`) and network. A processor whose page reads the typed number's IIN to
|
|
440
|
+
* pick a card product can read the default as another product than the card that pays:
|
|
441
|
+
* Worldline MyCheckout read 4111111111111111 as Visa Electron (122), not Visa (1), on CLZ's live
|
|
442
|
+
* page on 2026-10-12, and switched its form's product. The SDK refuses that submit
|
|
443
|
+
* (worldline-mycheckout.ts), so until this table names a Visa number Worldline reads as product 1,
|
|
444
|
+
* a Visa card cannot pay on a MyCheckout page that offers Visa Electron. The default table stays
|
|
445
|
+
* as it is: CyberSource Flex's Microform rule, ACI's brand check and EBANX's eligibility guard
|
|
446
|
+
* read its Visa and Mastercard numbers.
|
|
447
|
+
*
|
|
448
|
+
* TODO(Felipe): pick worldline_mycheckout's Visa number from Worldline Connect's own test-card
|
|
449
|
+
* list, then confirm it with one production IIN check on a MyCheckout card form that offers
|
|
450
|
+
* products 1 and 122: type it, wait for the IIN answer, read paymentProductId and the logo, clear
|
|
451
|
+
* the field, never Pay. Add it here as `worldline_mycheckout: { visa: '<number>' }`, with the
|
|
452
|
+
* check's date and merchant, and to the live runner's placeholder allowlist.
|
|
453
|
+
*/
|
|
454
|
+
const PROCESSOR_PLACEHOLDER_NUMBERS = Object.freeze({});
|
|
455
|
+
/**
|
|
456
|
+
* The placeholder number for a network (lowercase, as GET /api/v2/vault_cards names it) on a
|
|
457
|
+
* processor's page: the processor's own number for that network when `overrides` names one, else
|
|
458
|
+
* the network's default, else undefined for a network with no placeholder.
|
|
459
|
+
*/
|
|
460
|
+
export function placeholderNumber(brand, psp, overrides = PROCESSOR_PLACEHOLDER_NUMBERS) {
|
|
461
|
+
const own = psp !== undefined && Object.prototype.hasOwnProperty.call(overrides, psp) ? overrides[psp] : undefined;
|
|
462
|
+
if (own && Object.prototype.hasOwnProperty.call(own, brand))
|
|
463
|
+
return own[brand];
|
|
464
|
+
return Object.prototype.hasOwnProperty.call(PLACEHOLDER_NUMBERS, brand) ? PLACEHOLDER_NUMBERS[brand] : undefined;
|
|
465
|
+
}
|
|
426
466
|
/**
|
|
427
467
|
* The recognizer says this processor is preparation-required, and no preparation
|
|
428
468
|
* was passed. Its card may be sent only after the cardholder approved a
|
|
@@ -920,6 +960,71 @@ function maskCreditguardTxId(path) {
|
|
|
920
960
|
const PATH_SECRET_HOSTS = ['proxy.wspay.biz', 'proxy-test.wspay.biz', 'ipg.monri.com', 'ipgtest.monri.com'];
|
|
921
961
|
/** The segment after a `payment` segment, in any case (the WSPay proxy is IIS) or after an escaped slash. */
|
|
922
962
|
const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
|
|
963
|
+
/**
|
|
964
|
+
* Gravy (Gr4vy) gives every merchant hosts of its own under gr4vy.app, and Secure Fields sends the
|
|
965
|
+
* card to /checkout/sessions/<session id>/fields with no auth header, so the session id is that
|
|
966
|
+
* request's only credential (payment-core definitions.js): with it anyone can write card fields
|
|
967
|
+
* into the session until it expires. On those hosts the segment after /checkout/sessions/, in any
|
|
968
|
+
* case or after an escaped slash, reads {session}, as does any other id shaped like Gravy's UUID
|
|
969
|
+
* session ids.
|
|
970
|
+
*/
|
|
971
|
+
const GRAVY_HOST = /(?:^|\.)gr4vy\.app\.?$/i;
|
|
972
|
+
const GRAVY_SESSION = /(\/checkout\/sessions(?:\/|%2f))[^/]*/gi;
|
|
973
|
+
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;
|
|
974
|
+
function maskGravySession(path) {
|
|
975
|
+
return path.replace(GRAVY_SESSION, '$1{session}').replace(GRAVY_SESSION_ID, '{session}');
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Rapyd's hosted pages send the card to api.rapyd.net (sandboxapi.rapyd.net in test) with no auth
|
|
979
|
+
* header: the page's own id in the path, checkout_<id> for a checkout page and hp_card_<id> for a
|
|
980
|
+
* card page, is the request's only credential (payment-core definitions.js), and with it anyone can
|
|
981
|
+
* read that page and post a card to it until it expires. On Rapyd's hosts every id of those shapes
|
|
982
|
+
* (hp_reuse_<id>, a payment link's, too) and the segment after /hosted/collect/card/, in any case or
|
|
983
|
+
* after an escaped slash, read {token}.
|
|
984
|
+
*/
|
|
985
|
+
const RAPYD_HOST = /(?:^|\.)rapyd\.net\.?$/i;
|
|
986
|
+
const RAPYD_CARD_PAGE = /(\/hosted\/collect\/card(?:\/|%2f))[^/]*/gi;
|
|
987
|
+
const RAPYD_PAGE_ID = /(?:checkout|hp_[a-z]+)_[a-z0-9]+/gi;
|
|
988
|
+
function maskRapydToken(path) {
|
|
989
|
+
return path.replace(RAPYD_CARD_PAGE, '$1{token}').replace(RAPYD_PAGE_ID, '{token}');
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Stripe Checkout's pages confirm a Checkout Session at /v1/payment_pages/<session id>/confirm on
|
|
993
|
+
* api.stripe.com with the merchant's publishable key, which is public, so the session id is what
|
|
994
|
+
* lets anyone confirm a payment on that checkout until it expires. There the segment after
|
|
995
|
+
* /payment_pages/, in any case or after an escaped slash, reads {session}. A PaymentIntent or
|
|
996
|
+
* SetupIntent id stays: confirming one also needs its client_secret, which rides in the body.
|
|
997
|
+
*/
|
|
998
|
+
const STRIPE_API_HOST = /^api\.stripe\.com\.?$/i;
|
|
999
|
+
const STRIPE_PAYMENT_PAGE = /(\/payment_pages(?:\/|%2f))[^/]*/gi;
|
|
1000
|
+
function maskStripeSession(path) {
|
|
1001
|
+
return path.replace(STRIPE_PAYMENT_PAGE, '$1{session}');
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* redactAciCheckoutId (aci.ts) takes the checkout id out of the paths ACI's widget sends. This rule
|
|
1005
|
+
* takes any other copy of it out on ACI's hosts: a trailing dot on the host, an escaped or doubled
|
|
1006
|
+
* slash, another case, a string that is not a URL. The segment after /v1/checkouts/ and any id of
|
|
1007
|
+
* ACI's shape (<32 hex>.<cluster>-vm-tx<nn>) read {checkoutId}.
|
|
1008
|
+
*/
|
|
1009
|
+
const ACI_HOST = /^(?:eu-prod\.|eu-test\.|test\.)?oppwa\.com\.?$/i;
|
|
1010
|
+
const ACI_CHECKOUT = /(\/v1\/checkouts(?:\/|%2f))[^/]*/gi;
|
|
1011
|
+
const ACI_CHECKOUT_ID = /[0-9a-f]{32}\.[a-z0-9]{1,16}-vm-tx[0-9]{1,3}/gi;
|
|
1012
|
+
function maskAciCheckout(path) {
|
|
1013
|
+
return path.replace(ACI_CHECKOUT, '$1{checkoutId}').replace(ACI_CHECKOUT_ID, '{checkoutId}');
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* The processors whose card request carries its own credential in the URL path, with the hosts
|
|
1017
|
+
* that send it, the text that names those hosts in a string that is not a URL, and the mask. The
|
|
1018
|
+
* guard in redact-url.test.mjs fails for a payment-core recognizer that no one has sorted into
|
|
1019
|
+
* this list or out of it.
|
|
1020
|
+
*/
|
|
1021
|
+
const PATH_CREDENTIALS = [
|
|
1022
|
+
{ host: CREDITGUARD_HOST, named: /creditguard\.co\.il/i, mask: maskCreditguardTxId },
|
|
1023
|
+
{ host: GRAVY_HOST, named: /gr4vy\.app/i, mask: maskGravySession },
|
|
1024
|
+
{ host: RAPYD_HOST, named: /rapyd\.net/i, mask: maskRapydToken },
|
|
1025
|
+
{ host: STRIPE_API_HOST, named: /api\.stripe\.com/i, mask: maskStripeSession },
|
|
1026
|
+
{ host: ACI_HOST, named: /oppwa\.com/i, mask: maskAciCheckout },
|
|
1027
|
+
];
|
|
923
1028
|
/**
|
|
924
1029
|
* A URL as it may appear in an error message: origin + path only. A paused
|
|
925
1030
|
* request's URL can carry a client secret in its query string, and error
|
|
@@ -929,8 +1034,13 @@ const PATH_SECRET = /(\/payment(?:\/|%2f))[^/]*/gi;
|
|
|
929
1034
|
* so it reads {txId}, as does any other id-shaped segment there.
|
|
930
1035
|
* On the Monri and WSPay hosts the secret is a path segment, so it is replaced
|
|
931
1036
|
* with `{client_secret}`: https://proxy.wspay.biz/v2/payment/{client_secret}/confirm.
|
|
1037
|
+
* On Gravy's hosts the checkout session id is the credential, so it reads
|
|
1038
|
+
* `{session}`: https://api.<merchant>.gr4vy.app/checkout/sessions/{session}/fields.
|
|
1039
|
+
* On Rapyd's hosts the hosted page's id reads `{token}`: https://api.rapyd.net/v1/checkout/{token}.
|
|
1040
|
+
* On api.stripe.com a Checkout Session id reads `{session}`:
|
|
1041
|
+
* https://api.stripe.com/v1/payment_pages/{session}/confirm.
|
|
932
1042
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
933
|
-
* same
|
|
1043
|
+
* same segments masked.
|
|
934
1044
|
*/
|
|
935
1045
|
export function redactUrl(raw) {
|
|
936
1046
|
// An ACI checkout id is that checkout's only capability; it never rides on an event.
|
|
@@ -940,12 +1050,18 @@ export function redactUrl(raw) {
|
|
|
940
1050
|
try {
|
|
941
1051
|
const u = new URL(raw);
|
|
942
1052
|
// A trailing dot names the same host, so it cannot let the secret through.
|
|
943
|
-
|
|
944
|
-
|
|
1053
|
+
let path = PATH_SECRET_HOSTS.includes(u.hostname.replace(/\.$/, '')) ? u.pathname.replace(PATH_SECRET, '$1{client_secret}') : u.pathname;
|
|
1054
|
+
for (const rule of PATH_CREDENTIALS)
|
|
1055
|
+
if (rule.host.test(u.hostname))
|
|
1056
|
+
path = rule.mask(path);
|
|
1057
|
+
return `${u.origin}${path}`;
|
|
945
1058
|
}
|
|
946
1059
|
catch {
|
|
947
|
-
|
|
948
|
-
|
|
1060
|
+
let bare = raw.split(/[?#]/)[0].replace(PATH_SECRET, '$1{client_secret}');
|
|
1061
|
+
for (const rule of PATH_CREDENTIALS)
|
|
1062
|
+
if (rule.named.test(bare))
|
|
1063
|
+
bare = rule.mask(bare);
|
|
1064
|
+
return bare;
|
|
949
1065
|
}
|
|
950
1066
|
}
|
|
951
1067
|
/**
|
|
@@ -1000,6 +1116,17 @@ const EPROTECT_PREPARATION_REFUSALS = ['merchant_profile_mismatch'];
|
|
|
1000
1116
|
const SECURE_ACCEPTANCE_PREPARATION_REFUSALS = [
|
|
1001
1117
|
'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'unsupported_checkout', 'merchant_profile_mismatch',
|
|
1002
1118
|
];
|
|
1119
|
+
/**
|
|
1120
|
+
* The API's refusals of a Paddle VGS preparation, before any preparation exists, that prepareCheckout
|
|
1121
|
+
* surfaces by name: the lane is held to Agentcard's own account (`processor_unavailable`), the environment
|
|
1122
|
+
* is not the client's test or live mode, a live checkout's merchant is not the name the reviewed seller pin
|
|
1123
|
+
* carries (`merchant_profile_mismatch`), and the locked card is on a network Paddle's card request is not
|
|
1124
|
+
* paid on (`paddle_vgs_card_unsupported`). An unreviewed seller's page answers `unsupported_checkout`, which
|
|
1125
|
+
* PREPARATION_REFUSALS names for every checkout. Only for a Paddle checkout.
|
|
1126
|
+
*/
|
|
1127
|
+
const PADDLE_VGS_PREPARATION_REFUSALS = [
|
|
1128
|
+
'processor_unavailable', 'sandbox_host_on_live_row', 'live_host_on_sandbox_row', 'merchant_profile_mismatch', 'paddle_vgs_card_unsupported',
|
|
1129
|
+
];
|
|
1003
1130
|
/**
|
|
1004
1131
|
* The reason the cardholder's device declines a bound prepared row with, per processor, when its own
|
|
1005
1132
|
* send boundary passed before the card left it (the Vault's sendWindow, 12 seconds after it last saw
|
|
@@ -1405,12 +1532,17 @@ export class VaultClient {
|
|
|
1405
1532
|
* preselects that card: without a pin the page preselects its own choice (its recommended
|
|
1406
1533
|
* card, or the first card the cardholder's device can open), which can be another card. After
|
|
1407
1534
|
* prepare(), pass the prepared checkout's `cardId`, the card the cardholder already approved.
|
|
1408
|
-
* The facts hold only while the cardholder pays with that card.
|
|
1409
|
-
*
|
|
1535
|
+
* The facts hold only while the cardholder pays with that card. `psp` names the processor
|
|
1536
|
+
* whose card form the agent types into (the recognizer's name, `worldline_mycheckout`): a
|
|
1537
|
+
* processor that needs its own number for the card's network gets it, and every other gets the
|
|
1538
|
+
* network's default. Throws PlaceholderCardError when there is no such card, its network has no
|
|
1539
|
+
* placeholder, or it carries no expiry.
|
|
1410
1540
|
*/
|
|
1411
1541
|
async placeholderCard(input) {
|
|
1412
1542
|
if (!input || typeof input.user !== 'string' || !input.user)
|
|
1413
1543
|
throw new Error('placeholderCard needs the user whose stored card will pay.');
|
|
1544
|
+
if (input.psp !== undefined && (typeof input.psp !== 'string' || !input.psp))
|
|
1545
|
+
throw new Error('placeholderCard takes psp as the processor\'s name, such as worldline_mycheckout.');
|
|
1414
1546
|
// The card list lives under /api/v2 only: the API aliases /v2/checkout alone without the prefix.
|
|
1415
1547
|
const list = await this.get(`/api/v2/vault_cards?user_id=${encodeURIComponent(input.user)}`, input.signal);
|
|
1416
1548
|
const cards = Array.isArray(list?.data) ? list.data : [];
|
|
@@ -1418,7 +1550,7 @@ export class VaultClient {
|
|
|
1418
1550
|
if (!card || typeof card !== 'object' || typeof card.id !== 'string' || !card.id)
|
|
1419
1551
|
throw new PlaceholderCardError('card_not_found');
|
|
1420
1552
|
const brand = typeof card.brand === 'string' ? card.brand.toLowerCase() : '';
|
|
1421
|
-
const number =
|
|
1553
|
+
const number = placeholderNumber(brand, input.psp);
|
|
1422
1554
|
if (!number)
|
|
1423
1555
|
throw new PlaceholderCardError('brand_unsupported');
|
|
1424
1556
|
const month = card.expiry_month, year = card.expiry_year;
|
|
@@ -1507,6 +1639,12 @@ export class VaultClient {
|
|
|
1507
1639
|
const amadeus = input.psp === AMADEUS_CHECKOUT_PSP;
|
|
1508
1640
|
if (amadeus && !input.cardId)
|
|
1509
1641
|
throw fail('card_required');
|
|
1642
|
+
// Paddle VGS: Paddle's /pay sends the expiry and the card network the agent typed into Paddle's form
|
|
1643
|
+
// (placeholderCard) beside the card the device stores, so the preparation names that card, and the API
|
|
1644
|
+
// approves no other; refusing here asks nobody to approve a preparation that could never bind.
|
|
1645
|
+
const paddle = input.psp === PADDLE_VGS_PSP;
|
|
1646
|
+
if (paddle && !input.cardId)
|
|
1647
|
+
throw fail('card_required');
|
|
1510
1648
|
// CyberSource Flex, tokenized before Pay: the loaded card field's capture context and fields,
|
|
1511
1649
|
// verified here against the keys payment-core pins before anything is created (the API and the
|
|
1512
1650
|
// cardholder's device verify them again).
|
|
@@ -1631,6 +1769,8 @@ export class VaultClient {
|
|
|
1631
1769
|
|| (secureAcceptance && (state.secure_acceptance_profile_id !== secureAcceptanceProfileId || state.card_id !== input.cardId))
|
|
1632
1770
|
// Amadeus: the card the cardholder approved is the one whose expiry the agent typed.
|
|
1633
1771
|
|| (amadeus && state.card_id !== input.cardId)
|
|
1772
|
+
// Paddle VGS: the card the cardholder approved is the one whose facts the agent typed.
|
|
1773
|
+
|| (paddle && state.card_id !== input.cardId)
|
|
1634
1774
|
// The API says a preparation tokenizes before Pay exactly when this one carried a capture context.
|
|
1635
1775
|
|| (state.tokenize_before_pay === true) !== !!flexContext)
|
|
1636
1776
|
throw fail('ready_unconfirmed', id);
|
|
@@ -1675,6 +1815,7 @@ export class VaultClient {
|
|
|
1675
1815
|
&& ((error.status >= 400 && error.status < 500 && (PREPARATION_REFUSALS.includes(error.code)
|
|
1676
1816
|
|| (input.psp === 'fiserv' && FISERV_PREPARATION_REFUSALS.includes(error.code))
|
|
1677
1817
|
|| (input.psp === EPROTECT_PSP && EPROTECT_PREPARATION_REFUSALS.includes(error.code))
|
|
1818
|
+
|| (input.psp === PADDLE_VGS_PSP && PADDLE_VGS_PREPARATION_REFUSALS.includes(error.code))
|
|
1678
1819
|
|| (!!flexContext && CYBERSOURCE_FLEX_PREPARATION_REFUSALS.includes(error.code))))
|
|
1679
1820
|
|| (input.psp === 'fiserv' && error.status === 502 && error.code === 'cse_key_unavailable')))
|
|
1680
1821
|
throw fail(error.code);
|
|
@@ -2104,9 +2245,12 @@ export class VaultClient {
|
|
|
2104
2245
|
// A prepared CyberSource Secure Acceptance token create too, for 30 seconds:
|
|
2105
2246
|
// the merchant's page asks its frame for the reply for 30 seconds from Save,
|
|
2106
2247
|
// and the API names 500 ms as such a row's poll interval.
|
|
2248
|
+
// A prepared Paddle VGS card request too, for 20 seconds: Paddle's frame aborts it 10 seconds after
|
|
2249
|
+
// Pay and sends it once more, and the API names 500 ms as such a row's poll interval.
|
|
2107
2250
|
const preparedFastPollUntil = preparation?.psp === 'mercado_pago' ? Date.now() + 10_000
|
|
2108
|
-
: preparation?.psp ===
|
|
2109
|
-
: preparation?.psp ===
|
|
2251
|
+
: preparation?.psp === PADDLE_VGS_PSP ? Date.now() + 20_000
|
|
2252
|
+
: preparation?.psp === EPROTECT_PSP ? Date.now() + 15_000
|
|
2253
|
+
: preparation?.psp === SECURE_ACCEPTANCE_PSP ? Date.now() + SECURE_ACCEPTANCE_FAST_POLL_MS : 0;
|
|
2110
2254
|
// A preparation that tokenized before Pay: the cardholder's device already holds CyberSource's
|
|
2111
2255
|
// token and approves as soon as this bind reaches it, while the page may give createToken only
|
|
2112
2256
|
// seconds (Alaska Airlines' flight checkout gives it three). Poll every 100 ms for 10 seconds,
|
|
@@ -2652,11 +2796,12 @@ export class VaultClient {
|
|
|
2652
2796
|
* approval produced: a SetupIntent confirm that carries no card and pays with
|
|
2653
2797
|
* exactly the approved payment method (the page's own, or written by the
|
|
2654
2798
|
* adapters in place of a card the confirm carries again).
|
|
2655
|
-
* The API allows a card save
|
|
2656
|
-
*
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
2659
|
-
*
|
|
2799
|
+
* The API allows a card save for any company, for an approval given on the
|
|
2800
|
+
* Vault page, not by voice, with no amount, and a SetupIntent Stripe reports
|
|
2801
|
+
* still confirmable on the same Stripe account; any other answer rejects with
|
|
2802
|
+
* a CheckoutApiError whose code says why (for example `amount_mismatch`, or
|
|
2803
|
+
* `continuation_invalid` for a confirm that is not a card-free SetupIntent
|
|
2804
|
+
* confirm). Nothing
|
|
2660
2805
|
* is charged: the adapters continue the page's request once this resolves.
|
|
2661
2806
|
*/
|
|
2662
2807
|
async checkStripeSetupContinuation(authorizationId, request) {
|
|
@@ -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
|
|
@@ -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: 136e1c2b0fd2bdee0cfbf5efbbfaf1a92ace7d1cc52e17a3fd5a4584370e5e53
|
|
4
4
|
// src/elavon-converge.js
|
|
5
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";
|
|
@@ -77,6 +77,7 @@ var ONE_TIME_RECURRING_FLAG = "false";
|
|
|
77
77
|
var CARD_SAVING_FLAGS = /* @__PURE__ */ Object.freeze(["ssl_get_token", "ssl_add_token"]);
|
|
78
78
|
var CREDENTIAL_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_account_id", "ssl_merchant_id", "ssl_user_id", "ssl_pin", "ssl_vendor_id"]);
|
|
79
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}$/;
|
|
80
81
|
var CARD_NUMBER = /^\d{12,19}$/;
|
|
81
82
|
var EXPIRY_MMYY = /^(0[1-9]|1[0-2])\d{2}$/;
|
|
82
83
|
var CVC = /^\d{3,4}$/;
|
|
@@ -132,7 +133,7 @@ function convergeBodyVerdict(body) {
|
|
|
132
133
|
return refuse("converge_credential_refused");
|
|
133
134
|
if (WALLET_FIELDS.some((key) => owns(data, key)))
|
|
134
135
|
return refuse("converge_wallet_refused");
|
|
135
|
-
if (owns(parsed, "captchaToken"))
|
|
136
|
+
if (owns(parsed, "captchaToken") && !(typeof parsed.captchaToken === "string" && SOLVED_CAPTCHA.test(parsed.captchaToken)))
|
|
136
137
|
return refuse("converge_captcha_refused");
|
|
137
138
|
if (SERVICE_FEE_FIELDS.some((key) => owns(data, key)))
|
|
138
139
|
return refuse("converge_service_fee_refused");
|
package/dist/elavon-converge.js
CHANGED
|
@@ -25,9 +25,12 @@ export const CONVERGE_CAPABILITY = 'converge_hpp_process';
|
|
|
25
25
|
* the device refuses with, so an integrator reads one code whichever of them refused.
|
|
26
26
|
* - A shape the Vault never pays: `converge_wallet_refused`, `converge_debit_card_refused`,
|
|
27
27
|
* `converge_push_to_card_refused`, `converge_gift_card_refused`, `converge_payment_type_refused`,
|
|
28
|
-
* `
|
|
28
|
+
* `converge_service_fee_refused`, `converge_recurring_refused`,
|
|
29
29
|
* `converge_card_saving_refused` (ssl_get_token or ssl_add_token), `converge_amount_add_on_refused`
|
|
30
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.
|
|
31
34
|
* - A follow-up after a step the Vault cannot complete: `converge_3ds_follow_up_refused` (3-D
|
|
32
35
|
* Secure or Pay Defense) and `converge_surcharge_refused`.
|
|
33
36
|
* - A body the cardholder has nothing to approve on: `converge_amount_missing` and
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -106,7 +106,14 @@ export interface CheckoutController {
|
|
|
106
106
|
resume(result: ResumeResult): Promise<void>;
|
|
107
107
|
/** Await device consent before the caller starts the first native Pay action. One use per attachment. */
|
|
108
108
|
prepare(options: PrepareCheckoutOptions): Promise<PreparedCheckout>;
|
|
109
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Stop locally and best-effort retire an unbound preparation. Does not cancel a processor payment.
|
|
111
|
+
* A checkout that already ended keeps its state and reason (`completed`, `declined`, `failed`,
|
|
112
|
+
* `timed_out`, `unsupported`, `cancelled`, `outcome_unknown`) once no card request is in flight, or
|
|
113
|
+
* once the request in flight has its own outcome recorded (a cancel from onStateChange or the `failed` event).
|
|
114
|
+
* Any other reads `outcome_unknown` once a card request is in flight or an authorization exists,
|
|
115
|
+
* and `cancelled` before, with reason `local_cancel`.
|
|
116
|
+
*/
|
|
110
117
|
cancel(): void;
|
|
111
118
|
/** Ask the application's merchant resolver. A rejection records unknown; never automatically retries payment. */
|
|
112
119
|
reconcile(): Promise<Readonly<CheckoutState>>;
|
|
@@ -131,6 +138,7 @@ export declare class CheckoutLifecycle implements CheckoutController {
|
|
|
131
138
|
private state;
|
|
132
139
|
private held;
|
|
133
140
|
private active;
|
|
141
|
+
private settled;
|
|
134
142
|
private cancelled;
|
|
135
143
|
private merchantAborted;
|
|
136
144
|
private unboundStripeToken;
|
package/dist/lifecycle.js
CHANGED
|
@@ -2,6 +2,20 @@ import { AdyenTestPlatformRefusedError, ApprovalDeclinedError, ApprovalTimeoutEr
|
|
|
2
2
|
import { CardEndpointRefusedError } from './card-endpoint.js';
|
|
3
3
|
import { parseOwnedShopReceipt, OWNED_SHOP_SKU, OWNED_SHOP_AMOUNT_CENTS, OWNED_SHOP_CURRENCY } from './owned-shop.generated.js';
|
|
4
4
|
import { isWspayChallenge } from './wspay.js';
|
|
5
|
+
/**
|
|
6
|
+
* The statuses that end an attempt: its outcome is known (`completed`, `declined`, `failed`,
|
|
7
|
+
* `timed_out`, `unsupported`, `cancelled`), or the SDK already reported it unknown with its own
|
|
8
|
+
* reason (`outcome_unknown`). cancel() stops and holds the attachment but leaves these as they are,
|
|
9
|
+
* so an application that cancels at cleanup still reads the outcome, once no card request is in
|
|
10
|
+
* flight or the request in flight has its own outcome recorded. A checkout not started (`idle`) or
|
|
11
|
+
* still under way (`awaiting_approval`, `ready_to_submit`, `awaiting_merchant`, `requires_user_action`,
|
|
12
|
+
* or any status while a request whose outcome is not recorded is in flight) has none:
|
|
13
|
+
* cancelling it reads `outcome_unknown` once a request is in flight or an authorization exists,
|
|
14
|
+
* since a card or a token may still move, and `cancelled` before.
|
|
15
|
+
*/
|
|
16
|
+
const FINAL_STATUSES = new Set([
|
|
17
|
+
'completed', 'declined', 'failed', 'timed_out', 'unsupported', 'cancelled', 'outcome_unknown',
|
|
18
|
+
]);
|
|
5
19
|
/** How many times one approval's card-free Stripe confirmation may be checked before it is simply held. */
|
|
6
20
|
export const MAX_STRIPE_CONTINUATION_CHECKS = 3;
|
|
7
21
|
/**
|
|
@@ -29,6 +43,10 @@ export class CheckoutLifecycle {
|
|
|
29
43
|
state = { status: 'idle', authorizationId: null };
|
|
30
44
|
held = false;
|
|
31
45
|
active = false;
|
|
46
|
+
// The card request in flight has its own outcome recorded (failed(), handedOff(), merchantNeverRetried()), and
|
|
47
|
+
// the adapter has yet to call end(): it still answers the page and tidies up. An application that cancels
|
|
48
|
+
// from onStateChange or the `failed` event runs in that window, and the request waits on nothing there.
|
|
49
|
+
settled = false;
|
|
32
50
|
cancelled = false;
|
|
33
51
|
merchantAborted = false;
|
|
34
52
|
unboundStripeToken = false;
|
|
@@ -113,6 +131,7 @@ export class CheckoutLifecycle {
|
|
|
113
131
|
}
|
|
114
132
|
begin() {
|
|
115
133
|
this.active = true;
|
|
134
|
+
this.settled = false;
|
|
116
135
|
this.set({ status: 'awaiting_approval', authorizationId: null, ...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}) });
|
|
117
136
|
}
|
|
118
137
|
end() { this.active = false; }
|
|
@@ -137,9 +156,20 @@ export class CheckoutLifecycle {
|
|
|
137
156
|
catch { /* observer only */ }
|
|
138
157
|
}
|
|
139
158
|
cancel() {
|
|
159
|
+
// A checkout that already ended keeps its outcome: a refusal the processor gave, a payment the
|
|
160
|
+
// merchant confirmed, an unknown the SDK already named. Stopping the attachment afterwards
|
|
161
|
+
// changes nothing that happened, so only an attempt still under way reads as stopped here.
|
|
162
|
+
// A card request still in flight is under way whatever the state says: its authorization can
|
|
163
|
+
// still be approved on the cardholder's device, so cancelling it reads outcome_unknown as before.
|
|
164
|
+
// One whose own outcome is recorded (settled) is not: the adapter only has to finish answering
|
|
165
|
+
// the page, so a cancel from onStateChange or the `failed` event keeps that outcome.
|
|
166
|
+
// Read before the abort, so the state the application saw decides.
|
|
167
|
+
const ended = FINAL_STATUSES.has(this.state.status) && (!this.active || this.settled);
|
|
140
168
|
this.cancelled = true;
|
|
141
169
|
this.held = true;
|
|
142
170
|
this.abort.abort();
|
|
171
|
+
if (ended)
|
|
172
|
+
return;
|
|
143
173
|
this.set({ ...this.state, status: this.active || this.state.authorizationId ? 'outcome_unknown' : 'cancelled', reason: 'local_cancel' });
|
|
144
174
|
}
|
|
145
175
|
/** The exact browser request is gone; a late approval cannot reopen it. */
|
|
@@ -180,6 +210,7 @@ export class CheckoutLifecycle {
|
|
|
180
210
|
merchantNeverRetried(authorizationId) {
|
|
181
211
|
if (this.cancelled)
|
|
182
212
|
return;
|
|
213
|
+
this.settled = true;
|
|
183
214
|
this.set({ ...this.state, authorizationId, status: 'declined', reason: 'merchant_never_retried' });
|
|
184
215
|
}
|
|
185
216
|
/**
|
|
@@ -324,6 +355,7 @@ export class CheckoutLifecycle {
|
|
|
324
355
|
handedOff(replay) {
|
|
325
356
|
if (this.merchantAborted)
|
|
326
357
|
return;
|
|
358
|
+
this.settled = true;
|
|
327
359
|
if (this.ownedShopOrderId) {
|
|
328
360
|
this.held = true;
|
|
329
361
|
this.set({ status: 'completed', authorizationId: replay.authorizationId, mode: 'token', orderId: this.ownedShopOrderId });
|
|
@@ -359,6 +391,8 @@ export class CheckoutLifecycle {
|
|
|
359
391
|
failed(error, handoffStarted = false) {
|
|
360
392
|
if (this.cancelled)
|
|
361
393
|
return;
|
|
394
|
+
// Before any state is set, so an observer that cancels from onStateChange sees the outcome as recorded.
|
|
395
|
+
this.settled = true;
|
|
362
396
|
if (this.merchantAborted) {
|
|
363
397
|
this.merchantRequestAborted();
|
|
364
398
|
return;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
declare var PADDLE_VGS_PSP: string;
|
|
2
|
+
declare var PADDLE_VGS_CAPABILITY: string;
|
|
3
|
+
declare var PADDLE_VGS_OPERATION: string;
|
|
4
|
+
declare var PADDLE_VGS_CONTENT_TYPE: string;
|
|
5
|
+
declare var PADDLE_VGS_CARDS_URLS: Readonly<{
|
|
6
|
+
production: "https://vault.paddle.com/cards";
|
|
7
|
+
sandbox: "https://sandbox-vault.paddle.com/cards";
|
|
8
|
+
}>;
|
|
9
|
+
declare var PADDLE_VGS_REFUSAL_CODES: readonly string[];
|
|
10
|
+
declare function paddleVgsEnvironment(url: any): "production" | "sandbox" | null;
|
|
11
|
+
declare function isPaddleVgsCardsUrl(url: any): boolean;
|
|
12
|
+
declare function paddleVgsBodyVerdict(body: any): Readonly<{
|
|
13
|
+
verdict: "refuse";
|
|
14
|
+
code: any;
|
|
15
|
+
}> | Readonly<{
|
|
16
|
+
verdict: "card";
|
|
17
|
+
}>;
|
|
18
|
+
declare function paddleVgsRequestRefusal(request: any): any;
|
|
19
|
+
declare function isPreparedPaddleVgsRequest(url: any, method: any, body: any, environment: any, headers: any): boolean;
|
|
20
|
+
declare var PADDLE_VGS_MERCHANT_PINS: readonly any[];
|
|
21
|
+
declare function paddleVgsMerchantPin(merchantOrigin: any, pins?: readonly any[]): any;
|
|
22
|
+
declare function paddleVgsMerchantRefusal(environment: any, merchantOrigin: any, authorization: any, pins?: readonly any[]): "unsupported_environment" | "paddle_vgs_merchant_unreviewed" | "paddle_vgs_seller_unreviewed" | null;
|
|
23
|
+
declare function paddleVgsSellerClaimsPinned(pins?: readonly any[]): boolean;
|
|
24
|
+
declare function paddleVgsRefusalMessage(code: any): any;
|
|
25
|
+
declare var PADDLE_VGS_CARD_NETWORKS: readonly string[];
|
|
26
|
+
declare function paddleVgsCardRefusal(body: any, card: any): any;
|
|
27
|
+
declare function paddleVgsStoredCardRefusal(brand: any): "paddle_vgs_card_unsupported" | null;
|
|
28
|
+
declare function paddleVgsTypedCardRefusal(body: any, stored: any): any;
|
|
29
|
+
declare function injectPaddleVgsCard(body: any, card: any): {
|
|
30
|
+
body: string;
|
|
31
|
+
where: string[];
|
|
32
|
+
};
|
|
33
|
+
declare function paddleVgsRefusalCode(text: any): string | null;
|
|
34
|
+
declare function paddleVgsAnswerOutcome(status: any, text: any, card: any): {
|
|
35
|
+
outcome: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
code?: undefined;
|
|
38
|
+
body?: undefined;
|
|
39
|
+
} | {
|
|
40
|
+
outcome: string;
|
|
41
|
+
code: string;
|
|
42
|
+
reason?: undefined;
|
|
43
|
+
body?: undefined;
|
|
44
|
+
} | {
|
|
45
|
+
outcome: string;
|
|
46
|
+
body: string;
|
|
47
|
+
reason?: undefined;
|
|
48
|
+
code?: undefined;
|
|
49
|
+
};
|
|
50
|
+
export { PADDLE_VGS_CAPABILITY, PADDLE_VGS_CARDS_URLS, PADDLE_VGS_CARD_NETWORKS, PADDLE_VGS_CONTENT_TYPE, PADDLE_VGS_MERCHANT_PINS, PADDLE_VGS_OPERATION, PADDLE_VGS_PSP, PADDLE_VGS_REFUSAL_CODES, injectPaddleVgsCard, isPaddleVgsCardsUrl, isPreparedPaddleVgsRequest, paddleVgsAnswerOutcome, paddleVgsBodyVerdict, paddleVgsCardRefusal, paddleVgsEnvironment, paddleVgsMerchantPin, paddleVgsMerchantRefusal, paddleVgsRefusalCode, paddleVgsRefusalMessage, paddleVgsRequestRefusal, paddleVgsSellerClaimsPinned, paddleVgsStoredCardRefusal, paddleVgsTypedCardRefusal };
|