@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
|
@@ -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: 66c54c571ca1294220b070eaca1ca3888feebcd7f2562b761d74ded25a453c57
|
|
4
4
|
// src/json-path.js
|
|
5
5
|
function roundTrips(parsed, body) {
|
|
6
6
|
try {
|
|
@@ -237,6 +237,10 @@ var PAYRAILS_CAPABILITY = "payrails_device_encrypt";
|
|
|
237
237
|
var PAYRAILS_OPERATION = "payrails.device_encrypted_card";
|
|
238
238
|
var PAYRAILS_SAVE_PATH = "/public/payment/instruments";
|
|
239
239
|
var PAYRAILS_SAVE_HOSTS = Object.freeze(["api-pub.payrails.io", "api-pub.staging.payrails.io", "sandbox-api-pub.staging.payrails.io"]);
|
|
240
|
+
var PAYRAILS_MERCHANT_API_HOST = /^api-pub\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.payrails\.io$/;
|
|
241
|
+
var PAYRAILS_AUTHORIZE_WORKFLOW = "payment-acceptance";
|
|
242
|
+
var AUTHORIZE_PATH = /^\/public\/merchant\/workflows\/payment-acceptance\/executions\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/authorize$/;
|
|
243
|
+
var EXECUTION_PATH = /^\/public\/merchant\/workflows\/payment-acceptance\/executions\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
|
|
240
244
|
var PAYRAILS_JWE_HEADER = '{"alg":"RSA-OAEP-256","enc":"A256CBC-HS512"}';
|
|
241
245
|
var PAYRAILS_JWE_HEADER_B64 = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0";
|
|
242
246
|
var PAYRAILS_KEY_BITS = Object.freeze([2048, 3072, 4096]);
|
|
@@ -292,21 +296,33 @@ function base64UrlDecode(text, max) {
|
|
|
292
296
|
const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
293
297
|
return base64UrlEncode(bytes) === text ? bytes : null;
|
|
294
298
|
}
|
|
295
|
-
function
|
|
299
|
+
function isPayrailsApiHost(hostname) {
|
|
300
|
+
return typeof hostname === "string" && (PAYRAILS_SAVE_HOSTS.includes(hostname) || PAYRAILS_MERCHANT_API_HOST.test(hostname));
|
|
301
|
+
}
|
|
302
|
+
function payrailsApiUrl(url, exact2) {
|
|
296
303
|
if (typeof url !== "string" || url.length > 2048)
|
|
297
|
-
return
|
|
304
|
+
return null;
|
|
298
305
|
let parsed;
|
|
299
306
|
try {
|
|
300
307
|
parsed = new URL(url);
|
|
301
308
|
}
|
|
302
309
|
catch {
|
|
303
|
-
return
|
|
310
|
+
return null;
|
|
304
311
|
}
|
|
305
|
-
if (parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password)
|
|
306
|
-
return
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
312
|
+
if (parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password || !isPayrailsApiHost(parsed.hostname))
|
|
313
|
+
return null;
|
|
314
|
+
return !exact2 || parsed.search === "" && parsed.hash === "" && !/[?#]/.test(url) ? parsed : null;
|
|
315
|
+
}
|
|
316
|
+
function isPayrailsSaveUrl(url, { exact: exact2 = false } = {}) {
|
|
317
|
+
return payrailsApiUrl(url, exact2)?.pathname === PAYRAILS_SAVE_PATH;
|
|
318
|
+
}
|
|
319
|
+
function isPayrailsAuthorizeUrl(url, { exact: exact2 = false } = {}) {
|
|
320
|
+
const parsed = payrailsApiUrl(url, exact2);
|
|
321
|
+
return !!parsed && AUTHORIZE_PATH.test(parsed.pathname);
|
|
322
|
+
}
|
|
323
|
+
function payrailsExecutionId(url) {
|
|
324
|
+
const parsed = payrailsApiUrl(url, true);
|
|
325
|
+
return parsed ? AUTHORIZE_PATH.exec(parsed.pathname)?.[1] ?? null : null;
|
|
310
326
|
}
|
|
311
327
|
function payrailsJweParts(jwe) {
|
|
312
328
|
if (typeof jwe !== "string" || jwe.length > 8192)
|
|
@@ -386,8 +402,141 @@ function payrailsCardOnFileRefusal(body) {
|
|
|
386
402
|
const usage = payrailsKeepsCardFor(save.value);
|
|
387
403
|
return usage === null || PAYRAILS_CARD_ON_FILE_CONSENTED.includes(usage) ? null : "payrails_card_on_file_consent_required";
|
|
388
404
|
}
|
|
405
|
+
var AUTHORIZE_LIMIT = 32768;
|
|
406
|
+
var AUTHORIZE_KEYS = Object.freeze(["amount", "paymentComposition", "returnInfo", "meta"]);
|
|
407
|
+
var COMPOSITION_KEYS = Object.freeze([
|
|
408
|
+
"paymentMethodCode",
|
|
409
|
+
"integrationType",
|
|
410
|
+
"amount",
|
|
411
|
+
"storeInstrument",
|
|
412
|
+
"paymentInstrumentData",
|
|
413
|
+
"enrollInstrumentToNetworkOffers"
|
|
414
|
+
]);
|
|
415
|
+
var RETURN_KEYS = Object.freeze(["success", "cancel", "error", "pending"]);
|
|
416
|
+
var AMOUNT_VALUE = /^[0-9]{1,15}(?:\.[0-9]{1,12})?$/;
|
|
417
|
+
var CURRENCY = /^[A-Z]{3}$/;
|
|
418
|
+
var scalar = (value) => value === null || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.length <= 2048;
|
|
419
|
+
var META_KEYS = Object.freeze(["clientContext", "risk", "order", "customer"]);
|
|
420
|
+
var ORDER_KEYS = Object.freeze(["processingType"]);
|
|
421
|
+
var CUSTOMER_KEYS = Object.freeze([]);
|
|
422
|
+
var caseCollides = (value) => {
|
|
423
|
+
const lower = Object.keys(value).map((key) => key.toLowerCase());
|
|
424
|
+
return new Set(lower).size !== lower.length;
|
|
425
|
+
};
|
|
426
|
+
var onlyKeys = (value, keys) => record(value) && !caseCollides(value) && Object.keys(value).every((key) => keys.includes(key));
|
|
427
|
+
function payrailsMetaRefusal(meta) {
|
|
428
|
+
if (owns(meta, "subscription"))
|
|
429
|
+
return "payrails_future_usage_refused";
|
|
430
|
+
if (!onlyKeys(meta, META_KEYS))
|
|
431
|
+
return "payrails_request_malformed";
|
|
432
|
+
if (owns(meta, "clientContext") && !(record(meta.clientContext) && !caseCollides(meta.clientContext) && Object.values(meta.clientContext).every(scalar))) {
|
|
433
|
+
return "payrails_request_malformed";
|
|
434
|
+
}
|
|
435
|
+
if (owns(meta, "risk")) {
|
|
436
|
+
if (!record(meta.risk) || caseCollides(meta.risk))
|
|
437
|
+
return "payrails_request_malformed";
|
|
438
|
+
for (const [key, value] of Object.entries(meta.risk)) {
|
|
439
|
+
const sound = key === "sessionId" ? plainText(value, 256) : key === "sessions" ? Array.isArray(value) && value.length <= 8 && value.every((session) => record(session) && !caseCollides(session) && Object.values(session).every(scalar)) : key === "threeDSMode" ? value === "Default" : /^skip[A-Z]/.test(key) ? value === false : false;
|
|
440
|
+
if (!sound)
|
|
441
|
+
return "payrails_request_malformed";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (owns(meta, "order")) {
|
|
445
|
+
const order = meta.order;
|
|
446
|
+
if (!onlyKeys(order, ORDER_KEYS))
|
|
447
|
+
return "payrails_request_malformed";
|
|
448
|
+
if (owns(order, "processingType") && order.processingType !== "Regular") {
|
|
449
|
+
if (order.processingType === "CardOnFile" || order.processingType === "UnscheduledCardOnFile")
|
|
450
|
+
return "payrails_card_on_file_consent_required";
|
|
451
|
+
return order.processingType === "Subscription" ? "payrails_future_usage_refused" : "payrails_request_malformed";
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (owns(meta, "customer") && !onlyKeys(meta.customer, CUSTOMER_KEYS))
|
|
455
|
+
return "payrails_request_malformed";
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
function readAmount(value) {
|
|
459
|
+
if (!record(value) || Object.keys(value).length !== 2 || typeof value.value !== "string" || !AMOUNT_VALUE.test(value.value) || typeof value.currency !== "string" || !CURRENCY.test(value.currency))
|
|
460
|
+
return null;
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
function readAuthorizeBody(body) {
|
|
464
|
+
const malformed = refusal("payrails_request_malformed");
|
|
465
|
+
if (typeof body !== "string" || body.length === 0 || body.length > AUTHORIZE_LIMIT)
|
|
466
|
+
return malformed;
|
|
467
|
+
let parsed;
|
|
468
|
+
try {
|
|
469
|
+
parsed = JSON.parse(body);
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
return malformed;
|
|
473
|
+
}
|
|
474
|
+
if (!record(parsed) || !roundTrips(parsed, body) || !sameKeys(parsed, AUTHORIZE_KEYS) || !record(parsed.meta))
|
|
475
|
+
return malformed;
|
|
476
|
+
const amount = readAmount(parsed.amount);
|
|
477
|
+
const parts = parsed.paymentComposition;
|
|
478
|
+
const part = Array.isArray(parts) && parts.length === 1 && record(parts[0]) ? parts[0] : null;
|
|
479
|
+
if (!amount || !part || !sameKeys(part, COMPOSITION_KEYS) || part.paymentMethodCode !== "card" || part.integrationType !== "api")
|
|
480
|
+
return malformed;
|
|
481
|
+
const partAmount = readAmount(part.amount);
|
|
482
|
+
if (!partAmount || partAmount.value !== amount.value || partAmount.currency !== amount.currency)
|
|
483
|
+
return malformed;
|
|
484
|
+
if (typeof part.storeInstrument !== "boolean" || typeof part.enrollInstrumentToNetworkOffers !== "boolean")
|
|
485
|
+
return malformed;
|
|
486
|
+
const data = part.paymentInstrumentData;
|
|
487
|
+
const keys = record(data) ? Object.keys(data) : [];
|
|
488
|
+
if (keys[0] !== "encryptedData" || keys[1] !== "vaultProviderConfigId" || !(keys.length === 2 || keys.length === 3 && keys[2] === "billingAddress" && record(data.billingAddress)))
|
|
489
|
+
return malformed;
|
|
490
|
+
if (typeof data.vaultProviderConfigId !== "string" || !UUID.test(data.vaultProviderConfigId))
|
|
491
|
+
return malformed;
|
|
492
|
+
const back = parsed.returnInfo;
|
|
493
|
+
if (!record(back) || !sameKeys(back, RETURN_KEYS) || !RETURN_KEYS.every((key) => plainText(back[key], 2048)))
|
|
494
|
+
return malformed;
|
|
495
|
+
const metaRefusal = payrailsMetaRefusal(parsed.meta);
|
|
496
|
+
if (metaRefusal)
|
|
497
|
+
return refusal(metaRefusal);
|
|
498
|
+
const jwe = payrailsJweParts(data.encryptedData);
|
|
499
|
+
if (!jwe)
|
|
500
|
+
return refusal("payrails_jwe_malformed");
|
|
501
|
+
return {
|
|
502
|
+
value: Object.freeze({
|
|
503
|
+
amount: Object.freeze({ value: amount.value, currency: amount.currency }),
|
|
504
|
+
storeInstrument: part.storeInstrument,
|
|
505
|
+
enrollInstrumentToNetworkOffers: part.enrollInstrumentToNetworkOffers,
|
|
506
|
+
encryptedData: data.encryptedData,
|
|
507
|
+
vaultProviderConfigId: data.vaultProviderConfigId,
|
|
508
|
+
wrappedKeyBytes: jwe.wrappedKey.length
|
|
509
|
+
})
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function payrailsAuthorizeRefusal(body) {
|
|
513
|
+
const payment = readAuthorizeBody(body);
|
|
514
|
+
if (payment.code)
|
|
515
|
+
return payment.code;
|
|
516
|
+
return payment.value.storeInstrument || payment.value.enrollInstrumentToNetworkOffers ? "payrails_card_on_file_consent_required" : null;
|
|
517
|
+
}
|
|
518
|
+
function parsePayrailsAuthorize(body) {
|
|
519
|
+
return readAuthorizeBody(body).value ?? null;
|
|
520
|
+
}
|
|
521
|
+
function authorizeShaped(body) {
|
|
522
|
+
if (typeof body !== "string" || body.length > AUTHORIZE_LIMIT)
|
|
523
|
+
return false;
|
|
524
|
+
try {
|
|
525
|
+
const parsed = JSON.parse(body);
|
|
526
|
+
return record(parsed) && owns(parsed, "paymentComposition");
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function compositionCiphertext(parsed) {
|
|
533
|
+
const parts = record(parsed) ? parsed.paymentComposition : null;
|
|
534
|
+
const part = Array.isArray(parts) ? parts[0] : null;
|
|
535
|
+
const data = record(part) ? part.paymentInstrumentData : null;
|
|
536
|
+
return record(data) && owns(data, "encryptedData") ? data.encryptedData : void 0;
|
|
537
|
+
}
|
|
389
538
|
function classifyPayrailsRequest(url, method, body) {
|
|
390
|
-
if (method !== "POST" || !isPayrailsSaveUrl(url))
|
|
539
|
+
if (method !== "POST" || !(isPayrailsSaveUrl(url) || isPayrailsAuthorizeUrl(url)))
|
|
391
540
|
return null;
|
|
392
541
|
if (typeof body !== "string" || body === "")
|
|
393
542
|
return "continue";
|
|
@@ -399,8 +548,8 @@ function classifyPayrailsRequest(url, method, body) {
|
|
|
399
548
|
return "continue";
|
|
400
549
|
}
|
|
401
550
|
const data = record(parsed) ? parsed.data : null;
|
|
402
|
-
const
|
|
403
|
-
return
|
|
551
|
+
const holds = (value) => typeof value === "string" && value !== "" || typeof value === "number";
|
|
552
|
+
return holds(record(data) && owns(data, "encryptedData") ? data.encryptedData : void 0) || holds(compositionCiphertext(parsed)) ? "card" : "continue";
|
|
404
553
|
}
|
|
405
554
|
function payrailsAuthorizeCarriesCard(url, method, body) {
|
|
406
555
|
if (method !== "POST" || typeof url !== "string" || typeof body !== "string" || body.length > 262144)
|
|
@@ -437,13 +586,13 @@ function payrailsSessionKeyBytes(spki) {
|
|
|
437
586
|
}
|
|
438
587
|
}
|
|
439
588
|
function payrailsSessionKeyRefusal(spki, body) {
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
442
|
-
return
|
|
589
|
+
const request = authorizeShaped(body) ? readAuthorizeBody(body) : readSaveBody(body);
|
|
590
|
+
if (request.code)
|
|
591
|
+
return request.code;
|
|
443
592
|
const size = payrailsSessionKeyBytes(spki);
|
|
444
593
|
if (size === null)
|
|
445
594
|
return "payrails_key_invalid";
|
|
446
|
-
return size ===
|
|
595
|
+
return size === request.value.wrappedKeyBytes ? null : "payrails_key_mismatch";
|
|
447
596
|
}
|
|
448
597
|
var TOKEN_LIMIT = 8192;
|
|
449
598
|
var CLAIM_NAME = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
@@ -571,7 +720,43 @@ function instrumentMatchesCard(instrument, card) {
|
|
|
571
720
|
const data = instrument.data;
|
|
572
721
|
return number.length >= 12 && data.suffix === number.slice(-4) && exact(/^(?:0[1-9]|1[0-2])$/, data.expiryMonth) && Number(data.expiryMonth) === month && exact(/^(?:[0-9]{2}|[0-9]{4})$/, data.expiryYear) && Number(data.expiryYear) % 100 === year % 100;
|
|
573
722
|
}
|
|
574
|
-
|
|
723
|
+
var ACTION_TIME = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?(?:Z|[+-][0-9]{2}:[0-9]{2})$/;
|
|
724
|
+
function payrailsLink(value) {
|
|
725
|
+
if (!plainText(value, 2048))
|
|
726
|
+
return null;
|
|
727
|
+
let parsed;
|
|
728
|
+
try {
|
|
729
|
+
parsed = new URL(value);
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
return parsed.protocol === "https:" && !parsed.port && !parsed.username && !parsed.password && /(?:^|\.)payrails\.io$/.test(parsed.hostname) ? value : null;
|
|
735
|
+
}
|
|
736
|
+
function readAuthorizeAction(text) {
|
|
737
|
+
const value = readJson(text);
|
|
738
|
+
if (!record(value) || value.name !== "authorize" || typeof value.actionId !== "string" || !UUID.test(value.actionId) || !record(value.links))
|
|
739
|
+
return null;
|
|
740
|
+
const execution = payrailsLink(value.links.execution);
|
|
741
|
+
if (!execution)
|
|
742
|
+
return null;
|
|
743
|
+
return compact({ name: "authorize", actionId: value.actionId, executedAt: pick(value.executedAt, ACTION_TIME), links: { execution } });
|
|
744
|
+
}
|
|
745
|
+
function namesPausedExecution(link, url) {
|
|
746
|
+
const paused = payrailsApiUrl(url, true);
|
|
747
|
+
const id = paused ? AUTHORIZE_PATH.exec(paused.pathname)?.[1] : void 0;
|
|
748
|
+
const named = payrailsApiUrl(link, true);
|
|
749
|
+
return !!id && !!named && named.hostname === paused.hostname && EXECUTION_PATH.exec(named.pathname)?.[1] === id;
|
|
750
|
+
}
|
|
751
|
+
function sanitizePayrailsAuthorizeResponse(text, url) {
|
|
752
|
+
const action = readAuthorizeAction(text);
|
|
753
|
+
if (!action)
|
|
754
|
+
throw new Error("Refusing to record: the Payrails answer is not an authorize action.");
|
|
755
|
+
if (!namesPausedExecution(action.links.execution, url))
|
|
756
|
+
throw new Error("Refusing to record: the Payrails answer names another payment.");
|
|
757
|
+
return JSON.stringify(action);
|
|
758
|
+
}
|
|
759
|
+
function payrailsResponseOutcome(status, text, { card, url } = {}) {
|
|
575
760
|
if (!Number.isInteger(status))
|
|
576
761
|
return { outcome: "unknown", reason: "payrails_unconfirmed" };
|
|
577
762
|
if (status === 409 || status === 429)
|
|
@@ -580,6 +765,16 @@ function payrailsResponseOutcome(status, text, { card } = {}) {
|
|
|
580
765
|
return { outcome: "refused", code: payrailsFacts(text).refusalCode || `http_${status}` };
|
|
581
766
|
if (status >= 500)
|
|
582
767
|
return { outcome: "unknown", reason: "server_error" };
|
|
768
|
+
if (isPayrailsAuthorizeUrl(url)) {
|
|
769
|
+
if (status < 200 || status > 299)
|
|
770
|
+
return { outcome: "unknown", reason: "payrails_unconfirmed" };
|
|
771
|
+
const action = readAuthorizeAction(text);
|
|
772
|
+
if (!action)
|
|
773
|
+
return { outcome: "unknown", reason: "payrails_unsafe_response" };
|
|
774
|
+
if (!namesPausedExecution(action.links.execution, url))
|
|
775
|
+
return { outcome: "unknown", reason: "payrails_execution_mismatch" };
|
|
776
|
+
return { outcome: "approved", body: JSON.stringify(action) };
|
|
777
|
+
}
|
|
583
778
|
if (status !== 201)
|
|
584
779
|
return { outcome: "unknown", reason: "payrails_unconfirmed" };
|
|
585
780
|
const instrument = readInstrument(text);
|
|
@@ -593,4 +788,4 @@ function payrailsResponseOutcome(status, text, { card } = {}) {
|
|
|
593
788
|
return { outcome: "unknown", reason: "payrails_instrument_mismatch" };
|
|
594
789
|
return { outcome: "approved", body: sanitizePayrailsResponse(text) };
|
|
595
790
|
}
|
|
596
|
-
export { PAYRAILS_CAPABILITY, PAYRAILS_CARD_ON_FILE_CONSENTED, PAYRAILS_FUTURE_USAGES, PAYRAILS_JWE_HEADER, PAYRAILS_JWE_HEADER_B64, PAYRAILS_KEY_BITS, PAYRAILS_OPERATION, PAYRAILS_PSP, PAYRAILS_REFUSALS, PAYRAILS_SAVE_HOSTS, PAYRAILS_SAVE_PATH, PAYRAILS_SERVED_CARD_FACTS, PAYRAILS_SESSION_MIN_SECONDS, PAYRAILS_UNUSABLE_STATUSES, PAYRAILS_USABLE_STATUSES, base64UrlDecode, base64UrlEncode, classifyPayrailsRequest, isPayrailsSaveUrl, parsePayrailsSave, payrailsAuthorizeCarriesCard, payrailsCardOnFileRefusal, payrailsFacts, payrailsJweParts, payrailsKeepsCardFor, payrailsRefusalMessage, payrailsResponseOutcome, payrailsSessionKeyBytes, payrailsSessionKeyRefusal, payrailsTemplateRefusal, payrailsTokenClaims, payrailsWorkspacePinRefusal, sanitizePayrailsResponse };
|
|
791
|
+
export { PAYRAILS_AUTHORIZE_WORKFLOW, PAYRAILS_CAPABILITY, PAYRAILS_CARD_ON_FILE_CONSENTED, PAYRAILS_FUTURE_USAGES, PAYRAILS_JWE_HEADER, PAYRAILS_JWE_HEADER_B64, PAYRAILS_KEY_BITS, PAYRAILS_MERCHANT_API_HOST, PAYRAILS_OPERATION, PAYRAILS_PSP, PAYRAILS_REFUSALS, PAYRAILS_SAVE_HOSTS, PAYRAILS_SAVE_PATH, PAYRAILS_SERVED_CARD_FACTS, PAYRAILS_SESSION_MIN_SECONDS, PAYRAILS_UNUSABLE_STATUSES, PAYRAILS_USABLE_STATUSES, base64UrlDecode, base64UrlEncode, classifyPayrailsRequest, isPayrailsApiHost, isPayrailsAuthorizeUrl, isPayrailsSaveUrl, parsePayrailsAuthorize, parsePayrailsSave, payrailsAuthorizeCarriesCard, payrailsAuthorizeRefusal, payrailsCardOnFileRefusal, payrailsExecutionId, payrailsFacts, payrailsJweParts, payrailsKeepsCardFor, payrailsRefusalMessage, payrailsResponseOutcome, payrailsSessionKeyBytes, payrailsSessionKeyRefusal, payrailsTemplateRefusal, payrailsTokenClaims, payrailsWorkspacePinRefusal, sanitizePayrailsAuthorizeResponse, sanitizePayrailsResponse };
|
package/dist/payrails.js
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
// Payrails web SDK
|
|
1
|
+
// Payrails web SDK secure fields (Blacklane's consumer checkout, FlixBus's guest checkout), as the
|
|
2
|
+
// checkout SDK sees it.
|
|
2
3
|
//
|
|
3
4
|
// payrails.js 6.5.0 saves a card with one fetch from the merchant page: a POST to the session's
|
|
4
5
|
// vaultConfiguration.links.saveInstrument.href (https://api-pub.payrails.io/public/payment/instruments;
|
|
5
|
-
// test hosts api-pub.staging.payrails.io and sandbox-api-pub.staging.payrails.io).
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// the
|
|
14
|
-
//
|
|
6
|
+
// test hosts api-pub.staging.payrails.io and sandbox-api-pub.staging.payrails.io). The card form's Pay
|
|
7
|
+
// button (web SDK 5.50.0 at FlixBus) pays with the card instead: a POST to the session's execution
|
|
8
|
+
// authorize link, /public/merchant/workflows/payment-acceptance/executions/<id>/authorize. A merchant
|
|
9
|
+
// can have its own public API host, api-pub.<merchant>.payrails.io (FlixBus: api-pub.flix.payrails.io).
|
|
10
|
+
// Either body carries the whole card as one JWE, made in Payrails' secure frame under a per-session RSA
|
|
11
|
+
// key. That key is not in the paused request: it comes in the init payload ({version, data}) the
|
|
12
|
+
// merchant's backend hands the page before the card form renders, at the top of an answer
|
|
13
|
+
// (Blacklane) or deep inside one (FlixBus's /payment/configuration/list, six levels down). So the
|
|
14
|
+
// runtime reads the page's own XHR and fetch answers for that payload and keeps, per attachment and
|
|
15
|
+
// in memory only, each session's token, holderReference, providerConfigId, saveInstrument href,
|
|
16
|
+
// execution id and key. When a card request pauses, exactly one captured session must match it, seen
|
|
17
|
+
// at most nine minutes before: a save on its token, its URL, its holderReference and its
|
|
18
|
+
// providerConfigId; a card-form payment on its token, its execution and host, and its
|
|
19
|
+
// providerConfigId. Its key rides on the create (request.payrails.encryption_public_key), and for a
|
|
20
|
+
// payment its holderReference too (request.payrails.holder_reference), which the payment's JWE
|
|
21
|
+
// carries and its body does not. The cardholder's device encrypts the real card under the key and
|
|
22
|
+
// sends the request itself (mode device_encrypt), and the page receives only Payrails' answer, rebuilt
|
|
23
|
+
// from an allow-list: a save's 201, or the authorize action a payment's 202 names, whose execution
|
|
24
|
+
// link the page's SDK then polls for the issuer's answer. The device's ciphertext never reaches this
|
|
25
|
+
// runtime.
|
|
15
26
|
//
|
|
16
27
|
// The registry entry ships dark (capability payrails_device_encrypt). This build declares the
|
|
17
28
|
// capability and the mode on syncRegistry, and the API serves the entry only to an org Agentcard
|
|
@@ -26,33 +37,43 @@
|
|
|
26
37
|
// merchant's integration decides that shape, so the page's next save would be the same;
|
|
27
38
|
// - a card save with no captured session, or whose captured key is not as long as the key the
|
|
28
39
|
// page's frame wrapped, aborts alone: the agent reloads the checkout and tries again;
|
|
29
|
-
// -
|
|
30
|
-
//
|
|
31
|
-
//
|
|
40
|
+
// - the card form's payment on an authorize link the registry recognizes is judged the same
|
|
41
|
+
// way: payment-core's guard first (a payment that stores the card at the merchant aborts as
|
|
42
|
+
// payrails_card_on_file_consent_required, another shape as unsupported_payrails_card_payment,
|
|
43
|
+
// and the attachment holds), then the captured session, which a miss aborts alone;
|
|
44
|
+
// - any other payment that carries an encrypted card inside paymentComposition (a stored card
|
|
45
|
+
// with a CVC field, a split payment, another link, or a registry that does not recognize the
|
|
46
|
+
// payment) aborts as an unsupported checkout, so the agent's placeholder card never reaches
|
|
47
|
+
// an authorization;
|
|
32
48
|
// - a POST with no body the gate can read (none, an empty one, or one the adapter could not
|
|
33
49
|
// read) aborts alone, on both adapters alike (pre-claim.ts says why), except on Payrails'
|
|
34
50
|
// static host, which serves files and takes no payment;
|
|
35
51
|
// - every other request continues.
|
|
36
|
-
import { PAYRAILS_CAPABILITY, PAYRAILS_PSP, PAYRAILS_SAVE_HOSTS, PAYRAILS_SAVE_PATH, classifyPayrailsRequest, isPayrailsSaveUrl, payrailsAuthorizeCarriesCard, payrailsCardOnFileRefusal, payrailsResponseOutcome, payrailsSessionKeyRefusal, } from './payrails.generated.js';
|
|
52
|
+
import { PAYRAILS_CAPABILITY, PAYRAILS_PSP, PAYRAILS_SAVE_HOSTS, PAYRAILS_SAVE_PATH, classifyPayrailsRequest, isPayrailsAuthorizeUrl, isPayrailsSaveUrl, payrailsAuthorizeCarriesCard, payrailsAuthorizeRefusal, payrailsCardOnFileRefusal, payrailsExecutionId, payrailsResponseOutcome, payrailsSessionKeyRefusal, } from './payrails.generated.js';
|
|
37
53
|
export { PAYRAILS_CAPABILITY, PAYRAILS_PSP };
|
|
38
54
|
/**
|
|
39
55
|
* How the pre-claim gate reads a POST to a Payrails host: `card` for a card save on a save URL
|
|
40
56
|
* (data.encryptedData present, payment-core's pause predicate), `card_payment` for a payment that
|
|
41
|
-
* carries an encrypted card inside paymentComposition,
|
|
57
|
+
* carries an encrypted card inside paymentComposition, the card form's included, and null for
|
|
58
|
+
* anything else, which continues.
|
|
42
59
|
*/
|
|
43
60
|
export function payrailsRequestKind(url, method, body) {
|
|
44
61
|
if (method.toUpperCase() !== 'POST')
|
|
45
62
|
return null;
|
|
46
|
-
if (classifyPayrailsRequest(url, 'POST', body) === 'card')
|
|
63
|
+
if (isPayrailsSaveUrl(url) && classifyPayrailsRequest(url, 'POST', body) === 'card')
|
|
47
64
|
return 'card';
|
|
48
65
|
return payrailsAuthorizeCarriesCard(url, 'POST', body) ? 'card_payment' : null;
|
|
49
66
|
}
|
|
50
|
-
/**
|
|
67
|
+
/** Whether `url` is exactly a payment-acceptance execution's authorize link on a Payrails API host, where a card-form payment may pause. */
|
|
68
|
+
export function isPayrailsPaymentUrl(url) {
|
|
69
|
+
return isPayrailsAuthorizeUrl(url, { exact: true });
|
|
70
|
+
}
|
|
71
|
+
/** Refusals of the request's own shape: the page's next one would be the same, so the attachment holds. */
|
|
51
72
|
export const PAYRAILS_HOLDING_REASONS = [
|
|
52
73
|
'unsupported_payrails_card_payment', 'payrails_request_malformed', 'payrails_not_card_save',
|
|
53
74
|
'payrails_jwe_malformed', 'payrails_future_usage_refused', 'payrails_card_on_file_consent_required',
|
|
54
75
|
];
|
|
55
|
-
/** What the agent reads when a
|
|
76
|
+
/** What the agent reads when a card request's session cannot be bound. Not shown to the cardholder. */
|
|
56
77
|
export const PAYRAILS_SESSION_MESSAGE = 'Payrails session expired, reload the checkout';
|
|
57
78
|
/** Captured sessions kept per attachment; the oldest is dropped first. */
|
|
58
79
|
export const PAYRAILS_CAPTURE_LIMIT = 8;
|
|
@@ -81,6 +102,7 @@ function base64Json(value) {
|
|
|
81
102
|
return null;
|
|
82
103
|
}
|
|
83
104
|
}
|
|
105
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
84
106
|
/** The binding fields of one init payload `{version, data}`, or null when `candidate` is not one. */
|
|
85
107
|
function sessionOf(candidate) {
|
|
86
108
|
if (!record(candidate) || typeof candidate.version !== 'string' || typeof candidate.data !== 'string')
|
|
@@ -94,14 +116,22 @@ function sessionOf(candidate) {
|
|
|
94
116
|
if (!text(data.token, 8192) || !text(data.holderReference, 256) || !text(vault.providerConfigId, 64)
|
|
95
117
|
|| !text(vault.encryptionPublicKey, 1024) || !text(href, 2048) || !isPayrailsSaveUrl(href, { exact: true }))
|
|
96
118
|
return null;
|
|
119
|
+
const execution = record(data.execution) && typeof data.execution.id === 'string' && UUID.test(data.execution.id) ? data.execution.id : null;
|
|
97
120
|
return { token: data.token, holderReference: data.holderReference, providerConfigId: vault.providerConfigId,
|
|
98
|
-
saveInstrumentHref: href, encryptionPublicKey: vault.encryptionPublicKey };
|
|
121
|
+
saveInstrumentHref: href, executionId: execution, encryptionPublicKey: vault.encryptionPublicKey };
|
|
99
122
|
}
|
|
123
|
+
/** How deep inside an answer an init payload is looked for, and how many of its objects at most. */
|
|
124
|
+
export const PAYRAILS_INIT_SEARCH_DEPTH = 8;
|
|
125
|
+
export const PAYRAILS_INIT_SEARCH_NODES = 4096;
|
|
100
126
|
/**
|
|
101
|
-
* Every Payrails init payload one JSON answer body carries: the body itself, or
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
127
|
+
* Every Payrails init payload one JSON answer body carries: the body itself, or any object or array
|
|
128
|
+
* item inside it at most PAYRAILS_INIT_SEARCH_DEPTH levels down, read breadth first over at most
|
|
129
|
+
* PAYRAILS_INIT_SEARCH_NODES of them. Blacklane's session answer is the payload itself; FlixBus's
|
|
130
|
+
* /payment/configuration/list carries it six levels down
|
|
131
|
+
* (options.initialState.selection.paymentMethods[i].paymentToken). An init payload is
|
|
132
|
+
* `{version, data}` whose `data` decodes to JSON with a token, a holderReference and a
|
|
133
|
+
* vaultConfiguration naming its providerConfigId, its encryptionPublicKey and a saveInstrument href
|
|
134
|
+
* on a Payrails save URL; nothing inside one is read further.
|
|
105
135
|
*/
|
|
106
136
|
export function payrailsInitSessions(body) {
|
|
107
137
|
if (typeof body !== 'string' || body.length === 0 || body.length > PAYRAILS_INIT_BODY_LIMIT)
|
|
@@ -113,11 +143,23 @@ export function payrailsInitSessions(body) {
|
|
|
113
143
|
catch {
|
|
114
144
|
return [];
|
|
115
145
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
const found = [];
|
|
147
|
+
const queue = [{ node: parsed, depth: 0 }];
|
|
148
|
+
for (let next = 0; next < queue.length && next < PAYRAILS_INIT_SEARCH_NODES; next++) {
|
|
149
|
+
const { node, depth } = queue[next];
|
|
150
|
+
const session = sessionOf(node);
|
|
151
|
+
if (session) {
|
|
152
|
+
found.push(session);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (depth >= PAYRAILS_INIT_SEARCH_DEPTH)
|
|
156
|
+
continue;
|
|
157
|
+
for (const member of Array.isArray(node) ? node : record(node) ? Object.values(node) : []) {
|
|
158
|
+
if (member !== null && typeof member === 'object')
|
|
159
|
+
queue.push({ node: member, depth: depth + 1 });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return found;
|
|
121
163
|
}
|
|
122
164
|
/** Whether an answer may carry an init payload worth reading: a 2xx XHR or fetch with a JSON type. */
|
|
123
165
|
export function payrailsInitCandidate(resourceType, status, contentType) {
|
|
@@ -127,7 +169,16 @@ export function payrailsInitCandidate(resourceType, status, contentType) {
|
|
|
127
169
|
}
|
|
128
170
|
const sameSession = (a, b) => a.token === b.token && a.holderReference === b.holderReference
|
|
129
171
|
&& a.providerConfigId === b.providerConfigId && a.saveInstrumentHref === b.saveInstrumentHref
|
|
130
|
-
&& a.encryptionPublicKey === b.encryptionPublicKey;
|
|
172
|
+
&& a.executionId === b.executionId && a.encryptionPublicKey === b.encryptionPublicKey;
|
|
173
|
+
/** The hostname of a URL, or null. */
|
|
174
|
+
function hostnameOf(url) {
|
|
175
|
+
try {
|
|
176
|
+
return new URL(url).hostname;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
131
182
|
/** The bearer token a paused save carries in `authorization`, any case of the name; null otherwise. */
|
|
132
183
|
function bearerOf(headers) {
|
|
133
184
|
const values = Object.entries(headers ?? {}).filter(([name]) => name.toLowerCase() === 'authorization').map(([, value]) => value);
|
|
@@ -149,6 +200,19 @@ function saveBindingOf(body) {
|
|
|
149
200
|
return null;
|
|
150
201
|
}
|
|
151
202
|
}
|
|
203
|
+
/** A card-form payment body's paymentComposition[0].paymentInstrumentData.vaultProviderConfigId, read leniently; null when absent. */
|
|
204
|
+
function paymentProviderConfigOf(body) {
|
|
205
|
+
try {
|
|
206
|
+
const parsed = JSON.parse(body);
|
|
207
|
+
const parts = record(parsed) ? parsed.paymentComposition : undefined;
|
|
208
|
+
const part = Array.isArray(parts) ? parts[0] : undefined;
|
|
209
|
+
const data = record(part) ? part.paymentInstrumentData : undefined;
|
|
210
|
+
return record(data) && typeof data.vaultProviderConfigId === 'string' ? data.vaultProviderConfigId : null;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
152
216
|
/**
|
|
153
217
|
* The Payrails sessions one attachment has seen, in memory only: at most PAYRAILS_CAPTURE_LIMIT,
|
|
154
218
|
* each dropped PAYRAILS_CAPTURE_TTL_MS after it was first seen. Nothing here is ever logged or
|
|
@@ -207,18 +271,48 @@ export class PayrailsSessionCapture {
|
|
|
207
271
|
* The session key for a paused card save: the key of the captured session whose token is the
|
|
208
272
|
* save's bearer token, whose saveInstrument href is the save's URL, and whose holderReference and
|
|
209
273
|
* providerConfigId are the body's, first seen at most PAYRAILS_SESSION_MAX_AGE_MS before `now`.
|
|
210
|
-
* Null when none matches, or when matching sessions disagree on the key.
|
|
274
|
+
* Null when none matches, or when matching sessions disagree on the key. bind() reads a card-form
|
|
275
|
+
* payment too.
|
|
211
276
|
*/
|
|
212
277
|
keyFor(request, now = Date.now()) {
|
|
278
|
+
return isPayrailsSaveUrl(request.url) ? this.bind(request, now)?.key ?? null : null;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The session a paused card request binds to: its key and holderReference. A save binds as keyFor
|
|
282
|
+
* says. A card-form payment binds to the captured session whose token is the payment's bearer
|
|
283
|
+
* token, whose execution is the one the authorize link names, whose saveInstrument href is on the
|
|
284
|
+
* link's host, and whose providerConfigId is the payment's vaultProviderConfigId, first seen at most
|
|
285
|
+
* PAYRAILS_SESSION_MAX_AGE_MS before `now`. Null when none matches, or when matching sessions
|
|
286
|
+
* disagree on the key or the holderReference.
|
|
287
|
+
*/
|
|
288
|
+
bind(request, now = Date.now()) {
|
|
213
289
|
this.prune(now);
|
|
214
290
|
const token = bearerOf(request.headers);
|
|
215
|
-
|
|
216
|
-
if (!token || !binding)
|
|
291
|
+
if (!token || typeof request.body !== 'string')
|
|
217
292
|
return null;
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
293
|
+
const fresh = (session) => session.token === token && session.seenAt <= now && now - session.seenAt <= PAYRAILS_SESSION_MAX_AGE_MS;
|
|
294
|
+
let matches;
|
|
295
|
+
if (isPayrailsSaveUrl(request.url)) {
|
|
296
|
+
const binding = saveBindingOf(request.body);
|
|
297
|
+
if (!binding)
|
|
298
|
+
return null;
|
|
299
|
+
matches = this.sessions.filter((session) => fresh(session) && session.saveInstrumentHref === request.url
|
|
300
|
+
&& session.holderReference === binding.holderReference && session.providerConfigId === binding.providerConfigId);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
const executionId = payrailsExecutionId(request.url);
|
|
304
|
+
const host = hostnameOf(request.url);
|
|
305
|
+
const providerConfigId = paymentProviderConfigOf(request.body);
|
|
306
|
+
if (!executionId || !host || !providerConfigId)
|
|
307
|
+
return null;
|
|
308
|
+
matches = this.sessions.filter((session) => fresh(session) && session.executionId === executionId
|
|
309
|
+
&& hostnameOf(session.saveInstrumentHref) === host && session.providerConfigId === providerConfigId);
|
|
310
|
+
}
|
|
311
|
+
const bindings = new Map(matches.map((session) => [`${session.encryptionPublicKey}\n${session.holderReference}`, session]));
|
|
312
|
+
if (bindings.size !== 1)
|
|
313
|
+
return null;
|
|
314
|
+
const [session] = bindings.values();
|
|
315
|
+
return { key: session.encryptionPublicKey, holderReference: session.holderReference };
|
|
222
316
|
}
|
|
223
317
|
/** How many sessions are held; for tests. */
|
|
224
318
|
get size() { return this.sessions.length; }
|
|
@@ -276,9 +370,27 @@ export function payrailsSaveRefusal(body) {
|
|
|
276
370
|
return 'payrails_request_malformed';
|
|
277
371
|
}
|
|
278
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* payment-core's judgement of a card-form payment's own shape, before any session is looked up:
|
|
375
|
+
* null when it is the SDK's own payment that pays once, else the reason. A payment that stores the
|
|
376
|
+
* card at the merchant reads as payrails_card_on_file_consent_required, one that sets up a
|
|
377
|
+
* subscription as payrails_future_usage_refused, and a ciphertext of another shape as
|
|
378
|
+
* payrails_jwe_malformed; every other shape (a stored card's CVC, a split or installment payment, a
|
|
379
|
+
* preferred scheme) is one this build does not take over, unsupported_payrails_card_payment. A guard
|
|
380
|
+
* that throws refuses.
|
|
381
|
+
*/
|
|
382
|
+
export function payrailsPaymentRefusal(body) {
|
|
383
|
+
try {
|
|
384
|
+
const code = payrailsAuthorizeRefusal(body);
|
|
385
|
+
return code === 'payrails_request_malformed' ? 'unsupported_payrails_card_payment' : code ?? null;
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return 'unsupported_payrails_card_payment';
|
|
389
|
+
}
|
|
390
|
+
}
|
|
279
391
|
/**
|
|
280
392
|
* The key check for a bound session: null when the captured key is an RSA-2048, 3072 or 4096 key as
|
|
281
|
-
* long as the key the page's frame wrapped for this save, else the refusal code.
|
|
393
|
+
* long as the key the page's frame wrapped for this save or payment, else the refusal code.
|
|
282
394
|
*/
|
|
283
395
|
export function payrailsKeyRefusal(key, body) {
|
|
284
396
|
try {
|
|
@@ -289,17 +401,26 @@ export function payrailsKeyRefusal(key, body) {
|
|
|
289
401
|
}
|
|
290
402
|
}
|
|
291
403
|
/**
|
|
292
|
-
* The only answer the page may receive for an approved Payrails
|
|
293
|
-
* instrument, rebuilt from payment-core's allow-list (the BIN cut to
|
|
294
|
-
*
|
|
404
|
+
* The only answer the page may receive for an approved Payrails card request, by the paused `url`: for
|
|
405
|
+
* a save, Payrails' 201 for a card instrument, rebuilt from payment-core's allow-list (the BIN cut to
|
|
406
|
+
* six digits); for a card-form payment, Payrails' 2xx for the authorize action it accepted, rebuilt to
|
|
407
|
+
* its name, id, time and execution link, which the page's SDK polls for the issuer's answer. That link
|
|
408
|
+
* must be the paused payment's own execution on its own host, or the page would poll another payment's
|
|
409
|
+
* result. Under a JSON content type; null for anything else.
|
|
295
410
|
*/
|
|
296
|
-
export function
|
|
411
|
+
export function payrailsAnswer(status, body, url) {
|
|
297
412
|
try {
|
|
298
|
-
const verdict = payrailsResponseOutcome(status, body);
|
|
413
|
+
const verdict = payrailsResponseOutcome(status, body, { url });
|
|
299
414
|
const answer = verdict.outcome === 'approved' ? verdict.body : undefined;
|
|
300
|
-
|
|
415
|
+
if (typeof answer !== 'string')
|
|
416
|
+
return null;
|
|
417
|
+
return { status: isPayrailsAuthorizeUrl(url) ? status : 201, headers: { 'content-type': 'application/json' }, body: answer };
|
|
301
418
|
}
|
|
302
419
|
catch {
|
|
303
420
|
return null;
|
|
304
421
|
}
|
|
305
422
|
}
|
|
423
|
+
/** payrailsAnswer for a save. */
|
|
424
|
+
export function payrailsSaveAnswer(status, body) {
|
|
425
|
+
return payrailsAnswer(status, body, `https://${PAYRAILS_SAVE_HOSTS[0]}${PAYRAILS_SAVE_PATH}`);
|
|
426
|
+
}
|