@trusty-squire/mcp 1.1.13-rc.8 → 1.1.13
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/README.md +139 -62
- package/assets/login/vnc-input.js +9 -0
- package/assets/login/vnc.html +214 -202
- package/assets/screencaps/connect-walkthrough.png +0 -0
- package/assets/screencaps/connect-walkthrough.svg +1 -1
- package/dist/api-client.d.ts +26 -3
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +19 -31
- package/dist/api-client.js.map +1 -1
- package/dist/bin.js +2 -3
- package/dist/bin.js.map +1 -1
- package/dist/bot/browser.d.ts +134 -77
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +1788 -1543
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/compact-observation-v2.d.ts +167 -0
- package/dist/bot/compact-observation-v2.d.ts.map +1 -0
- package/dist/bot/compact-observation-v2.js +1038 -0
- package/dist/bot/compact-observation-v2.js.map +1 -0
- package/dist/bot/credential-shape.d.ts +2 -0
- package/dist/bot/credential-shape.d.ts.map +1 -1
- package/dist/bot/credential-shape.js +21 -8
- package/dist/bot/credential-shape.js.map +1 -1
- package/dist/bot/element-fingerprint.d.ts +9 -0
- package/dist/bot/element-fingerprint.d.ts.map +1 -0
- package/dist/bot/element-fingerprint.js +147 -0
- package/dist/bot/element-fingerprint.js.map +1 -0
- package/dist/bot/email-verification.d.ts +7 -1
- package/dist/bot/email-verification.d.ts.map +1 -1
- package/dist/bot/email-verification.js +48 -14
- package/dist/bot/email-verification.js.map +1 -1
- package/dist/bot/google-login.d.ts +28 -52
- package/dist/bot/google-login.d.ts.map +1 -1
- package/dist/bot/google-login.js +246 -1119
- package/dist/bot/google-login.js.map +1 -1
- package/dist/bot/install-completion.d.ts +1 -2
- package/dist/bot/install-completion.d.ts.map +1 -1
- package/dist/bot/install-completion.js +1 -2
- package/dist/bot/install-completion.js.map +1 -1
- package/dist/bot/login-state.d.ts +13 -7
- package/dist/bot/login-state.d.ts.map +1 -1
- package/dist/bot/login-state.js +50 -114
- package/dist/bot/login-state.js.map +1 -1
- package/dist/bot/oauth-providers.d.ts +1 -1
- package/dist/bot/oauth-providers.d.ts.map +1 -1
- package/dist/bot/oauth-providers.js +3 -3
- package/dist/bot/oauth-providers.js.map +1 -1
- package/dist/bot/oauth-scope.d.ts +4 -0
- package/dist/bot/oauth-scope.d.ts.map +1 -0
- package/dist/bot/oauth-scope.js +58 -0
- package/dist/bot/oauth-scope.js.map +1 -0
- package/dist/bot/operator-browser-watchdog.d.ts +127 -0
- package/dist/bot/operator-browser-watchdog.d.ts.map +1 -0
- package/dist/bot/operator-browser-watchdog.js +438 -0
- package/dist/bot/operator-browser-watchdog.js.map +1 -0
- package/dist/bot/owner-process-reaper-worker.d.ts +2 -0
- package/dist/bot/owner-process-reaper-worker.d.ts.map +1 -0
- package/dist/bot/owner-process-reaper-worker.js +17 -0
- package/dist/bot/owner-process-reaper-worker.js.map +1 -0
- package/dist/bot/owner-process-reaper.d.ts +103 -0
- package/dist/bot/owner-process-reaper.d.ts.map +1 -0
- package/dist/bot/owner-process-reaper.js +1058 -0
- package/dist/bot/owner-process-reaper.js.map +1 -0
- package/dist/bot/pay-operator.d.ts +13 -2
- package/dist/bot/pay-operator.d.ts.map +1 -1
- package/dist/bot/pay-operator.js +319 -123
- package/dist/bot/pay-operator.js.map +1 -1
- package/dist/bot/profile.d.ts +10 -1
- package/dist/bot/profile.d.ts.map +1 -1
- package/dist/bot/profile.js +137 -11
- package/dist/bot/profile.js.map +1 -1
- package/dist/bot/promote-to-skill.js +1 -1
- package/dist/bot/promote-to-skill.js.map +1 -1
- package/dist/bot/provision-session.d.ts +60 -164
- package/dist/bot/provision-session.d.ts.map +1 -1
- package/dist/bot/provision-session.js +2049 -1546
- package/dist/bot/provision-session.js.map +1 -1
- package/dist/bot/remote-login-display.d.ts +61 -0
- package/dist/bot/remote-login-display.d.ts.map +1 -0
- package/dist/bot/remote-login-display.js +765 -0
- package/dist/bot/remote-login-display.js.map +1 -0
- package/dist/bot/session/hosts.d.ts +8 -0
- package/dist/bot/session/hosts.d.ts.map +1 -0
- package/dist/bot/session/hosts.js +36 -0
- package/dist/bot/session/hosts.js.map +1 -0
- package/dist/bot/session/lifecycle.d.ts +56 -0
- package/dist/bot/session/lifecycle.d.ts.map +1 -0
- package/dist/bot/session/lifecycle.js +962 -0
- package/dist/bot/session/lifecycle.js.map +1 -0
- package/dist/bot/session/model.d.ts +175 -0
- package/dist/bot/session/model.d.ts.map +1 -0
- package/dist/bot/session/model.js +77 -0
- package/dist/bot/session/model.js.map +1 -0
- package/dist/install/cli.d.ts +29 -9
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +243 -322
- package/dist/install/cli.js.map +1 -1
- package/dist/install/interactive.d.ts +1 -0
- package/dist/install/interactive.d.ts.map +1 -1
- package/dist/install/interactive.js +19 -5
- package/dist/install/interactive.js.map +1 -1
- package/dist/install/ui.d.ts.map +1 -1
- package/dist/install/ui.js +3 -15
- package/dist/install/ui.js.map +1 -1
- package/dist/operator-env.js +1 -1
- package/dist/operator-env.js.map +1 -1
- package/dist/server-instance-registry.d.ts +103 -0
- package/dist/server-instance-registry.d.ts.map +1 -0
- package/dist/server-instance-registry.js +392 -0
- package/dist/server-instance-registry.js.map +1 -0
- package/dist/server.d.ts +15 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +164 -62
- package/dist/server.js.map +1 -1
- package/dist/session-guard.d.ts +28 -0
- package/dist/session-guard.d.ts.map +1 -0
- package/dist/session-guard.js +84 -0
- package/dist/session-guard.js.map +1 -0
- package/dist/session.d.ts +57 -13
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +246 -127
- package/dist/session.js.map +1 -1
- package/dist/tools/audit-log.d.ts +16 -2
- package/dist/tools/audit-log.d.ts.map +1 -1
- package/dist/tools/audit-log.js +223 -13
- package/dist/tools/audit-log.js.map +1 -1
- package/dist/tools/audit-rollup.d.ts +80 -0
- package/dist/tools/audit-rollup.d.ts.map +1 -0
- package/dist/tools/audit-rollup.js +0 -0
- package/dist/tools/audit-rollup.js.map +1 -0
- package/dist/tools/fetch-credential.d.ts +37 -0
- package/dist/tools/fetch-credential.d.ts.map +1 -0
- package/dist/tools/fetch-credential.js +153 -0
- package/dist/tools/fetch-credential.js.map +1 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/operate-pay.d.ts +4 -4
- package/dist/tools/operate-pay.d.ts.map +1 -1
- package/dist/tools/operate-pay.js +163 -96
- package/dist/tools/operate-pay.js.map +1 -1
- package/dist/tools/provision-drive.d.ts +43 -35
- package/dist/tools/provision-drive.d.ts.map +1 -1
- package/dist/tools/provision-drive.js +268 -164
- package/dist/tools/provision-drive.js.map +1 -1
- package/dist/tools/use-credential.d.ts.map +1 -1
- package/dist/tools/use-credential.js +3 -2
- package/dist/tools/use-credential.js.map +1 -1
- package/package.json +4 -6
- package/dist/bot/operator-direct-identity.d.ts +0 -26
- package/dist/bot/operator-direct-identity.d.ts.map +0 -1
- package/dist/bot/operator-direct-identity.js +0 -120
- package/dist/bot/operator-direct-identity.js.map +0 -1
- package/dist/bot/operator-profile-pool.d.ts +0 -97
- package/dist/bot/operator-profile-pool.d.ts.map +0 -1
- package/dist/bot/operator-profile-pool.js +0 -855
- package/dist/bot/operator-profile-pool.js.map +0 -1
- package/dist/bot/xvfb.d.ts +0 -13
- package/dist/bot/xvfb.d.ts.map +0 -1
- package/dist/bot/xvfb.js +0 -146
- package/dist/bot/xvfb.js.map +0 -1
package/dist/bot/pay-operator.js
CHANGED
|
@@ -3,7 +3,7 @@ import { classifyPaymentCandidateBinding, } from "@trusty-squire/skill-schema";
|
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
4
|
import { createLocalJWKSet, decodeJwt, jwtVerify } from "jose";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { ApiCallError } from "../api-client.js";
|
|
6
|
+
import { ApiCallError, isPaymentApprovalTransportTimeout, } from "../api-client.js";
|
|
7
7
|
import { PaymentCardFillCleanupError, PaymentSubmitOutcomeUnknownError, UnrecognizedPaymentFrameError, } from "./browser.js";
|
|
8
8
|
import { generateOperatorKeypair, openSealed } from "./payment-hpke.js";
|
|
9
9
|
const cardSchema = z.object({
|
|
@@ -162,6 +162,40 @@ function normalizeCard(value) {
|
|
|
162
162
|
function confidenceAtLeastLow(value) {
|
|
163
163
|
return value === "low" || value === "medium" || value === "high";
|
|
164
164
|
}
|
|
165
|
+
const MAX_PREVERIFIED_MANDATE_RELAY_MS = 18 * 60 * 1_000;
|
|
166
|
+
function isJwtExpired(error) {
|
|
167
|
+
return (error !== null &&
|
|
168
|
+
typeof error === "object" &&
|
|
169
|
+
"code" in error &&
|
|
170
|
+
error.code === "ERR_JWT_EXPIRED");
|
|
171
|
+
}
|
|
172
|
+
async function verifyRelayedAssertion(jws, jwks, expectedAudience) {
|
|
173
|
+
const options = {
|
|
174
|
+
issuer: "https://vouchflow.dev",
|
|
175
|
+
audience: expectedAudience,
|
|
176
|
+
};
|
|
177
|
+
try {
|
|
178
|
+
return (await jwtVerify(jws, jwks, options)).payload;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!isJwtExpired(error))
|
|
182
|
+
throw error;
|
|
183
|
+
const decoded = decodeJwt(jws);
|
|
184
|
+
const issuedAt = decoded.iat;
|
|
185
|
+
const expiresAt = decoded.exp;
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
if (!Number.isSafeInteger(issuedAt) ||
|
|
188
|
+
!Number.isSafeInteger(expiresAt) ||
|
|
189
|
+
expiresAt <= issuedAt ||
|
|
190
|
+
now - expiresAt * 1_000 > MAX_PREVERIFIED_MANDATE_RELAY_MS) {
|
|
191
|
+
throw new Error("mandate_assertion_expired");
|
|
192
|
+
}
|
|
193
|
+
return (await jwtVerify(jws, jwks, {
|
|
194
|
+
...options,
|
|
195
|
+
currentDate: new Date((expiresAt - 1) * 1_000),
|
|
196
|
+
})).payload;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
165
199
|
async function verifyMandate(jws, expectedHash, vouchflowApiBase, expectedAudience, fetchImpl) {
|
|
166
200
|
const jwksUrl = `${vouchflowApiBase.replace(/\/+$/, "")}/.well-known/jwks.json`;
|
|
167
201
|
const signal = AbortSignal.timeout(5_000);
|
|
@@ -185,10 +219,11 @@ async function verifyMandate(jws, expectedHash, vouchflowApiBase, expectedAudien
|
|
|
185
219
|
!Array.isArray(body.keys)) {
|
|
186
220
|
throw new Error("invalid_jwks");
|
|
187
221
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
222
|
+
// Every candidate returned by this authenticated relay was already checked
|
|
223
|
+
// at phone submission. Re-check all cryptographic and binding properties,
|
|
224
|
+
// while allowing only that exact candidate's short-lived assertion to age
|
|
225
|
+
// within the still-live approval window.
|
|
226
|
+
const payload = await verifyRelayedAssertion(jws, createLocalJWKSet(body), expectedAudience);
|
|
192
227
|
const signedHash = decodePayloadHash(payload.payload_sha256);
|
|
193
228
|
if (!timingSafeEqual(Buffer.from(expectedHash), Buffer.from(signedHash))) {
|
|
194
229
|
throw new Error("payload_hash_mismatch");
|
|
@@ -212,6 +247,7 @@ function safeFailureReason(error) {
|
|
|
212
247
|
"payload_hash_mismatch",
|
|
213
248
|
"invalid_mandate_context",
|
|
214
249
|
"insufficient_mandate_confidence",
|
|
250
|
+
"mandate_assertion_expired",
|
|
215
251
|
"invalid_card_pan",
|
|
216
252
|
"invalid_card_expiry",
|
|
217
253
|
];
|
|
@@ -241,6 +277,29 @@ function cardRequiredResult(approvalUrl, checkout, reason) {
|
|
|
241
277
|
},
|
|
242
278
|
};
|
|
243
279
|
}
|
|
280
|
+
function approvalDeniedResult(approvalId, approvalUrl, checkout) {
|
|
281
|
+
return {
|
|
282
|
+
status: "payment_approval_denied",
|
|
283
|
+
approval_id: approvalId,
|
|
284
|
+
approval_url: approvalUrl,
|
|
285
|
+
merchant: checkout.merchant,
|
|
286
|
+
amount_cents: checkout.amount_cents,
|
|
287
|
+
currency: checkout.currency,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function approvalExpiredResult(approvalUrl, checkout, jit, boundCardRef) {
|
|
291
|
+
const base = {
|
|
292
|
+
status: "payment_approval_timeout",
|
|
293
|
+
approval_url: approvalUrl,
|
|
294
|
+
merchant: checkout.merchant,
|
|
295
|
+
amount_cents: checkout.amount_cents,
|
|
296
|
+
currency: checkout.currency,
|
|
297
|
+
};
|
|
298
|
+
return jit && hasBoundCard(boundCardRef) ? { ...base, card_persisted: true } : base;
|
|
299
|
+
}
|
|
300
|
+
function isPaymentApprovalDeniedError(error) {
|
|
301
|
+
return error instanceof ApiCallError && error.code === "payment_approval_denied";
|
|
302
|
+
}
|
|
244
303
|
// Total additional time (beyond this call's own bounded wait) that a
|
|
245
304
|
// resumed, still-pending decoupled/out-of-band 3DS challenge stays
|
|
246
305
|
// checkable via operate_payment_status before handing back an accurate
|
|
@@ -248,6 +307,7 @@ function cardRequiredResult(approvalUrl, checkout, reason) {
|
|
|
248
307
|
// their phone, open the banking app, and approve — but bounded, matching
|
|
249
308
|
// the rest of this file's "wait, but never forever" posture.
|
|
250
309
|
const THREE_DS_RESUME_WINDOW_MS = 20 * 60 * 1000;
|
|
310
|
+
const PAYMENT_APPROVAL_RESPONSE_RESERVE_MS = 500;
|
|
251
311
|
// The cardholder approves 3-D Secure via an app-push in their bank app while
|
|
252
312
|
// the browser's checkout JavaScript owns the native challenge handshake. Fires
|
|
253
313
|
// the Telegram nudge WITHOUT awaiting it (a slow/unresolved Telegram call must
|
|
@@ -306,6 +366,8 @@ function statusAfterThreeDsResolution(currentStatus, resolution) {
|
|
|
306
366
|
return "payment_submitted";
|
|
307
367
|
case "failed":
|
|
308
368
|
return "payment_declined";
|
|
369
|
+
case "challenge_pending":
|
|
370
|
+
return "payment_3ds_required";
|
|
309
371
|
case "timeout":
|
|
310
372
|
return currentStatus;
|
|
311
373
|
}
|
|
@@ -370,7 +432,11 @@ function defaultDependencies() {
|
|
|
370
432
|
onCardFillCleanupFailed: () => undefined,
|
|
371
433
|
onSubmitStarted: () => undefined,
|
|
372
434
|
onApprovalPending: () => undefined,
|
|
435
|
+
onApprovalTerminal: () => undefined,
|
|
436
|
+
onThreeDsHandoffArmed: () => undefined,
|
|
437
|
+
coordinateThreeDsAudit: async (_state, audit) => await audit(),
|
|
373
438
|
onThreeDsPending: () => undefined,
|
|
439
|
+
onThreeDsCleared: () => undefined,
|
|
374
440
|
};
|
|
375
441
|
}
|
|
376
442
|
function logPaymentReviewLifecycle(event) {
|
|
@@ -418,15 +484,28 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
418
484
|
}
|
|
419
485
|
if (resume !== undefined) {
|
|
420
486
|
let reusable = false;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
487
|
+
try {
|
|
488
|
+
const live = await api.getPaymentApproval(resume.approval_id);
|
|
489
|
+
const liveDeadline = Date.parse(live.expires_at);
|
|
490
|
+
const terminalStatus = live.status === "denied"
|
|
491
|
+
? "denied"
|
|
492
|
+
: live.status === "expired" ||
|
|
493
|
+
(Number.isFinite(liveDeadline) ? liveDeadline : resume.deadline) <= deps.now()
|
|
494
|
+
? "expired"
|
|
495
|
+
: null;
|
|
496
|
+
if (terminalStatus !== null) {
|
|
497
|
+
deps.onApprovalTerminal(resume, terminalStatus);
|
|
498
|
+
resumableState = undefined;
|
|
499
|
+
keypairHandedOff = false;
|
|
500
|
+
return terminalStatus === "denied"
|
|
501
|
+
? approvalDeniedResult(resume.approval_id, resume.approval_url, resume.checkout)
|
|
502
|
+
: approvalExpiredResult(resume.approval_url, resume.checkout, resume.jit, resume.boundCardRef);
|
|
425
503
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
504
|
+
reusable = isLiveResumableApproval(live, resume, deps.now());
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
if (!(error instanceof ApiCallError && error.code === "payment_approval_not_found")) {
|
|
508
|
+
throw error;
|
|
430
509
|
}
|
|
431
510
|
}
|
|
432
511
|
if (!reusable) {
|
|
@@ -538,14 +617,10 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
538
617
|
...(threeDsWaitSeconds !== undefined ? { three_ds_wait_seconds: threeDsWaitSeconds } : {}),
|
|
539
618
|
});
|
|
540
619
|
await deps.surfaceApprovalUrl(approvalUrl);
|
|
541
|
-
//
|
|
542
|
-
// Undefined pollBudgetMs (direct executeOperatePay callers) = no additional
|
|
543
|
-
// bound, i.e. the legacy full-deadline blocking behavior.
|
|
620
|
+
// This call's wait is bounded by both its client budget and server expiry.
|
|
544
621
|
const callDeadline = deps.pollBudgetMs === undefined
|
|
545
622
|
? deadline
|
|
546
623
|
: Math.min(deadline, deps.now() + deps.pollBudgetMs);
|
|
547
|
-
// True once this call's (not the overall) budget is exhausted with no
|
|
548
|
-
// resolution — distinguishes "still pending, ask again" from "expired".
|
|
549
624
|
let budgetExhausted = false;
|
|
550
625
|
const shouldKeepPolling = () => {
|
|
551
626
|
const now = deps.now();
|
|
@@ -557,37 +632,30 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
557
632
|
}
|
|
558
633
|
return true;
|
|
559
634
|
};
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
// can distinguish "no card was ever added" (card_required) from "card was
|
|
563
|
-
// stored but never approved" (payment_approval_timeout, card persists).
|
|
635
|
+
// A JIT approval that expires before binding still needs a card; a bound
|
|
636
|
+
// card remains stored even though the approval itself is terminal.
|
|
564
637
|
const timeoutResult = () => {
|
|
565
638
|
if (jit && !hasBoundCard(boundCardRef)) {
|
|
566
639
|
return cardRequiredResult(approvalUrl, checkout, "the add-card link expired before a card was added");
|
|
567
640
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
641
|
+
return approvalExpiredResult(approvalUrl, checkout, jit, boundCardRef);
|
|
642
|
+
};
|
|
643
|
+
const approvalExpired = () => deps.now() >= deadline;
|
|
644
|
+
let terminalApprovalState;
|
|
645
|
+
const terminalApprovalResult = (terminalStatus) => {
|
|
646
|
+
const state = resumableState?.() ?? terminalApprovalState;
|
|
647
|
+
if (state !== undefined)
|
|
648
|
+
deps.onApprovalTerminal(state, terminalStatus);
|
|
649
|
+
resumableState = undefined;
|
|
650
|
+
keypairHandedOff = false;
|
|
651
|
+
return terminalStatus === "denied"
|
|
652
|
+
? approvalDeniedResult(approvalId, approvalUrl, checkout)
|
|
653
|
+
: timeoutResult();
|
|
578
654
|
};
|
|
655
|
+
const expiredApprovalResult = () => terminalApprovalResult("expired");
|
|
579
656
|
let approved;
|
|
580
657
|
let claims;
|
|
581
|
-
// Always
|
|
582
|
-
// before ever reporting pending, and a resumed call with a zero poll
|
|
583
|
-
// budget must too. Two things gate every re-entry to the top: (1) right
|
|
584
|
-
// before each sleep, shouldKeepPolling() skips the sleep entirely when
|
|
585
|
-
// the budget is already exhausted, so a not-yet-resolved call never
|
|
586
|
-
// blocks on a real wait before returning; (2) the iteration>0 check
|
|
587
|
-
// here re-validates AFTER that sleep (real time — or a mocked clock —
|
|
588
|
-
// may have advanced past the deadline during it) so the loop never
|
|
589
|
-
// spends one more live poll call past the budget it just decided to
|
|
590
|
-
// respect.
|
|
658
|
+
// Always make one live read; later iterations recheck the budget after sleep.
|
|
591
659
|
let iteration = 0;
|
|
592
660
|
let immediateReviewFollowup = false;
|
|
593
661
|
while (true) {
|
|
@@ -595,12 +663,30 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
595
663
|
break;
|
|
596
664
|
immediateReviewFollowup = false;
|
|
597
665
|
iteration++;
|
|
598
|
-
const
|
|
666
|
+
const remainingPollMs = Math.max(Math.min(callDeadline, deadline) - deps.now(), 0);
|
|
667
|
+
const candidateRead = remainingPollMs > 0 ? true : "immediate";
|
|
668
|
+
let approval;
|
|
669
|
+
try {
|
|
670
|
+
approval =
|
|
671
|
+
candidateRead === true
|
|
672
|
+
? await api.getPaymentApproval(approvalId, true, Math.min(Math.max(remainingPollMs - PAYMENT_APPROVAL_RESPONSE_RESERVE_MS, 0), 15_000), remainingPollMs)
|
|
673
|
+
: await api.getPaymentApproval(approvalId, "immediate");
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
if (!isPaymentApprovalTransportTimeout(error))
|
|
677
|
+
throw error;
|
|
678
|
+
budgetExhausted = true;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
const liveDeadline = Date.parse(approval.expires_at);
|
|
682
|
+
if (Number.isFinite(liveDeadline))
|
|
683
|
+
deadline = Math.min(deadline, liveDeadline);
|
|
599
684
|
boundCardRef = approval.card_ref;
|
|
600
|
-
if (approval.status === "
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
685
|
+
if (approval.status === "denied") {
|
|
686
|
+
return terminalApprovalResult("denied");
|
|
687
|
+
}
|
|
688
|
+
if (approval.status === "expired" || approvalExpired()) {
|
|
689
|
+
return expiredApprovalResult();
|
|
604
690
|
}
|
|
605
691
|
const hasCandidate = typeof approval.jws === "string" && typeof approval.sealed_card === "string";
|
|
606
692
|
if (approval.status === "approved" && !hasCandidate) {
|
|
@@ -692,6 +778,8 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
692
778
|
approval_url: approvalUrl,
|
|
693
779
|
};
|
|
694
780
|
}
|
|
781
|
+
if (approvalExpired())
|
|
782
|
+
return expiredApprovalResult();
|
|
695
783
|
let candidateCardBytes;
|
|
696
784
|
let candidateCard;
|
|
697
785
|
try {
|
|
@@ -731,6 +819,9 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
731
819
|
}
|
|
732
820
|
catch (error) {
|
|
733
821
|
candidateCardBytes.fill(0);
|
|
822
|
+
if (isPaymentApprovalDeniedError(error)) {
|
|
823
|
+
return terminalApprovalResult("denied");
|
|
824
|
+
}
|
|
734
825
|
const failureReason = error instanceof Error && /404|409/.test(error.message)
|
|
735
826
|
? "confirm_status"
|
|
736
827
|
: "confirm_failed";
|
|
@@ -767,33 +858,30 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
767
858
|
throw new Error("confirm_status");
|
|
768
859
|
}
|
|
769
860
|
catch (error) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
throw error;
|
|
774
|
-
}
|
|
775
|
-
catch {
|
|
776
|
-
candidateCardBytes.fill(0);
|
|
777
|
-
// The initial confirm may have actually gone through server-side (its
|
|
778
|
-
// response was merely lost) even though reconciliation couldn't prove
|
|
779
|
-
// it. Throwing here would leave resumableState live, and the outer
|
|
780
|
-
// catch would resurrect this candidate as still-awaiting — letting a
|
|
781
|
-
// later call re-confirm and re-charge a purchase that may already be
|
|
782
|
-
// spent. Fail closed with a terminal result instead, mirroring the
|
|
783
|
-
// review-candidate branch above.
|
|
784
|
-
const failureReason = error instanceof Error && /404|409/.test(error.message)
|
|
785
|
-
? "confirm_status"
|
|
786
|
-
: "confirm_failed";
|
|
787
|
-
logPaymentCandidateLifecycle(approvalId, "approval", "confirmation_failed", failureReason);
|
|
788
|
-
return {
|
|
789
|
-
status: "payment_confirmation_failed",
|
|
790
|
-
reason: failureReason,
|
|
791
|
-
candidate_kind: "approval",
|
|
792
|
-
approval_url: approvalUrl,
|
|
793
|
-
};
|
|
861
|
+
candidateCardBytes.fill(0);
|
|
862
|
+
if (isPaymentApprovalDeniedError(error)) {
|
|
863
|
+
return terminalApprovalResult("denied");
|
|
794
864
|
}
|
|
865
|
+
const failureReason = error instanceof Error && /404|409/.test(error.message)
|
|
866
|
+
? "confirm_status"
|
|
867
|
+
: "confirm_failed";
|
|
868
|
+
const state = resumableState();
|
|
869
|
+
deps.onApprovalTerminal(state, "payment_confirmation_failed");
|
|
870
|
+
resumableState = undefined;
|
|
871
|
+
keypairHandedOff = false;
|
|
872
|
+
logPaymentCandidateLifecycle(approvalId, "approval", "confirmation_failed", failureReason);
|
|
873
|
+
return {
|
|
874
|
+
status: "payment_confirmation_failed",
|
|
875
|
+
reason: failureReason,
|
|
876
|
+
candidate_kind: "approval",
|
|
877
|
+
approval_url: approvalUrl,
|
|
878
|
+
};
|
|
795
879
|
}
|
|
796
880
|
}
|
|
881
|
+
if (approvalExpired()) {
|
|
882
|
+
candidateCardBytes.fill(0);
|
|
883
|
+
return expiredApprovalResult();
|
|
884
|
+
}
|
|
797
885
|
logPaymentCandidateLifecycle(approvalId, "approval", "ready_to_charge");
|
|
798
886
|
cardBytes = candidateCardBytes;
|
|
799
887
|
card = candidateCard;
|
|
@@ -824,10 +912,10 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
824
912
|
candidate_kind: "review",
|
|
825
913
|
ready_to_charge: false,
|
|
826
914
|
next: {
|
|
827
|
-
tool: "
|
|
828
|
-
wait_seconds: 15,
|
|
915
|
+
tool: "operate_pay",
|
|
829
916
|
message: "The review signature was verified, but final payment approval is still required. " +
|
|
830
|
-
"Refresh the approval page if it does not advance to the final approval prompt
|
|
917
|
+
"Refresh the approval page if it does not advance to the final approval prompt, " +
|
|
918
|
+
"then call operate_pay again with the same arguments; it resumes this approval and waits.",
|
|
831
919
|
},
|
|
832
920
|
};
|
|
833
921
|
}
|
|
@@ -846,7 +934,12 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
846
934
|
merchant: checkout.merchant,
|
|
847
935
|
candidate_kind: "none",
|
|
848
936
|
ready_to_charge: false,
|
|
849
|
-
next: {
|
|
937
|
+
next: {
|
|
938
|
+
tool: "operate_pay",
|
|
939
|
+
message: "The bounded server wait ended before the human responded. Call operate_pay again " +
|
|
940
|
+
"with the same arguments; it resumes this approval and continues waiting without " +
|
|
941
|
+
"creating another approval.",
|
|
942
|
+
},
|
|
850
943
|
};
|
|
851
944
|
}
|
|
852
945
|
if (jit) {
|
|
@@ -856,15 +949,14 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
856
949
|
}
|
|
857
950
|
catch { }
|
|
858
951
|
}
|
|
859
|
-
|
|
860
|
-
keypairHandedOff = false;
|
|
861
|
-
return timeoutResult();
|
|
952
|
+
return expiredApprovalResult();
|
|
862
953
|
}
|
|
863
954
|
if (claims === undefined || card === undefined) {
|
|
864
955
|
resumableState = undefined;
|
|
865
956
|
keypairHandedOff = false;
|
|
866
957
|
return timeoutResult();
|
|
867
958
|
}
|
|
959
|
+
terminalApprovalState = resumableState?.();
|
|
868
960
|
resumableState = undefined;
|
|
869
961
|
keypairHandedOff = false;
|
|
870
962
|
const cardRef = cardRefArg ?? approved.card_ref;
|
|
@@ -875,7 +967,6 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
875
967
|
approval_url: approvalUrl,
|
|
876
968
|
};
|
|
877
969
|
}
|
|
878
|
-
deps.onCardResolved(cardRef);
|
|
879
970
|
const last4 = card.pan.slice(-4);
|
|
880
971
|
const mandateId = typeof claims.mandate_id === "string"
|
|
881
972
|
? claims.mandate_id
|
|
@@ -943,10 +1034,16 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
943
1034
|
...(liveOrigin !== null ? { live_checkout_origin: liveOrigin } : {}),
|
|
944
1035
|
};
|
|
945
1036
|
}
|
|
1037
|
+
if (approvalExpired())
|
|
1038
|
+
return expiredApprovalResult();
|
|
1039
|
+
deps.onCardResolved(cardRef);
|
|
946
1040
|
try {
|
|
947
|
-
await browser.fillCheckoutCardFields(card);
|
|
1041
|
+
await browser.fillCheckoutCardFields(card, { deadline });
|
|
948
1042
|
}
|
|
949
1043
|
catch (error) {
|
|
1044
|
+
if (error instanceof Error && error.message === "payment_approval_expired") {
|
|
1045
|
+
return expiredApprovalResult();
|
|
1046
|
+
}
|
|
950
1047
|
const frameOrigin = error instanceof UnrecognizedPaymentFrameError
|
|
951
1048
|
? error.frameOrigin
|
|
952
1049
|
: error instanceof PaymentCardFillCleanupError
|
|
@@ -1007,26 +1104,49 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
1007
1104
|
"does not need to happen before you place the order.",
|
|
1008
1105
|
};
|
|
1009
1106
|
}
|
|
1107
|
+
if (approvalExpired())
|
|
1108
|
+
return expiredApprovalResult();
|
|
1109
|
+
deps.onCardResolved(cardRef);
|
|
1110
|
+
const pendingThreeDsHandoff = {
|
|
1111
|
+
approval_id: approvalId,
|
|
1112
|
+
approval_url: approvalUrl,
|
|
1113
|
+
checkout,
|
|
1114
|
+
last4,
|
|
1115
|
+
...(mandateId !== undefined ? { mandate_id: mandateId } : {}),
|
|
1116
|
+
deadline: deps.now() + THREE_DS_RESUME_WINDOW_MS,
|
|
1117
|
+
outcome: "unknown",
|
|
1118
|
+
};
|
|
1119
|
+
deps.onThreeDsHandoffArmed(pendingThreeDsHandoff);
|
|
1120
|
+
let retainedPendingThreeDs = null;
|
|
1010
1121
|
const retainPendingThreeDs = () => {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
});
|
|
1122
|
+
const firstRetention = retainedPendingThreeDs === null;
|
|
1123
|
+
if (retainedPendingThreeDs === null) {
|
|
1124
|
+
retainedPendingThreeDs = pendingThreeDsHandoff;
|
|
1125
|
+
}
|
|
1126
|
+
if (submitResult.payment_instrument_mismatch !== undefined) {
|
|
1127
|
+
retainedPendingThreeDs.payment_instrument_mismatch =
|
|
1128
|
+
submitResult.payment_instrument_mismatch;
|
|
1129
|
+
}
|
|
1130
|
+
if (firstRetention)
|
|
1131
|
+
deps.onThreeDsPending(retainedPendingThreeDs);
|
|
1022
1132
|
};
|
|
1023
|
-
const
|
|
1133
|
+
const clearPendingThreeDs = () => {
|
|
1134
|
+
if (retainedPendingThreeDs === null)
|
|
1135
|
+
return;
|
|
1136
|
+
deps.onThreeDsCleared(retainedPendingThreeDs);
|
|
1137
|
+
retainedPendingThreeDs = null;
|
|
1138
|
+
};
|
|
1139
|
+
const pendingOutcomeNext = () => ({
|
|
1024
1140
|
tool: "operate_payment_status",
|
|
1025
1141
|
wait_seconds: 15,
|
|
1026
|
-
hint:
|
|
1027
|
-
"
|
|
1028
|
-
|
|
1029
|
-
|
|
1142
|
+
hint: pendingThreeDsHandoff.outcome === "three_ds"
|
|
1143
|
+
? "The 3-D Secure challenge has not resolved. Call operate_payment_status to keep " +
|
|
1144
|
+
"checking the same submitted charge — this does not re-release the card or create a " +
|
|
1145
|
+
"new approval."
|
|
1146
|
+
: "The submitted payment has no confirmed merchant outcome or 3-D Secure evidence. " +
|
|
1147
|
+
"Call operate_payment_status to keep checking the same attempt; its outcome remains " +
|
|
1148
|
+
"unknown until terminal merchant or authentication evidence appears.",
|
|
1149
|
+
});
|
|
1030
1150
|
let paymentStatus = "payment_submitted";
|
|
1031
1151
|
let submitResult = { three_ds_required: false, order_confirmed: false };
|
|
1032
1152
|
try {
|
|
@@ -1037,48 +1157,104 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
1037
1157
|
...(args.card_issuer !== undefined
|
|
1038
1158
|
? { issuer: args.card_issuer, issuer_source: "bin_metadata" }
|
|
1039
1159
|
: {}),
|
|
1160
|
+
}, {
|
|
1161
|
+
onSubmitDispatched: retainPendingThreeDs,
|
|
1162
|
+
beforeSubmitDispatch: () => {
|
|
1163
|
+
if (approvalExpired())
|
|
1164
|
+
throw new Error("payment_approval_expired");
|
|
1165
|
+
return deadline - deps.now();
|
|
1166
|
+
},
|
|
1040
1167
|
});
|
|
1041
1168
|
if (submitResult.three_ds_required)
|
|
1042
1169
|
paymentStatus = "payment_3ds_required";
|
|
1043
1170
|
else if (!submitResult.order_confirmed)
|
|
1044
1171
|
paymentStatus = "payment_outcome_unknown";
|
|
1172
|
+
if (submitResult.three_ds_required)
|
|
1173
|
+
pendingThreeDsHandoff.outcome = "three_ds";
|
|
1045
1174
|
}
|
|
1046
1175
|
catch (error) {
|
|
1176
|
+
if (error instanceof Error && error.message === "payment_approval_expired") {
|
|
1177
|
+
clearPendingThreeDs();
|
|
1178
|
+
return expiredApprovalResult();
|
|
1179
|
+
}
|
|
1047
1180
|
const outcomeUnknown = error instanceof PaymentSubmitOutcomeUnknownError;
|
|
1048
1181
|
paymentStatus = outcomeUnknown ? "payment_outcome_unknown" : "payment_checkout_failed";
|
|
1182
|
+
let terminalSubmitOutcome = false;
|
|
1049
1183
|
if (outcomeUnknown) {
|
|
1050
|
-
await browser.waitForThreeDsResolution(0).catch(() => undefined);
|
|
1184
|
+
const resolution = await browser.waitForThreeDsResolution(0).catch(() => undefined);
|
|
1185
|
+
if (resolution !== undefined) {
|
|
1186
|
+
paymentStatus = statusAfterThreeDsResolution(paymentStatus, resolution);
|
|
1187
|
+
terminalSubmitOutcome = resolution === "succeeded" || resolution === "failed";
|
|
1188
|
+
if (resolution === "challenge_pending") {
|
|
1189
|
+
pendingThreeDsHandoff.outcome = "three_ds";
|
|
1190
|
+
submitResult.three_ds_required = true;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1051
1193
|
const mismatch = browser.paymentInstrumentMismatch?.();
|
|
1052
1194
|
if (mismatch !== undefined)
|
|
1053
1195
|
submitResult.payment_instrument_mismatch = mismatch;
|
|
1054
1196
|
}
|
|
1055
1197
|
let audit_recorded = true;
|
|
1056
1198
|
try {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1199
|
+
const recordAudit = async () => {
|
|
1200
|
+
await api.auditPayment({
|
|
1201
|
+
...checkout,
|
|
1202
|
+
last4,
|
|
1203
|
+
status: paymentStatus,
|
|
1204
|
+
...(mandateId !== undefined ? { mandate_id: mandateId } : {}),
|
|
1205
|
+
});
|
|
1206
|
+
};
|
|
1207
|
+
if (retainedPendingThreeDs === null)
|
|
1208
|
+
await recordAudit();
|
|
1209
|
+
else
|
|
1210
|
+
await deps.coordinateThreeDsAudit(retainedPendingThreeDs, recordAudit);
|
|
1063
1211
|
}
|
|
1064
1212
|
catch {
|
|
1065
1213
|
audit_recorded = false;
|
|
1066
1214
|
}
|
|
1067
|
-
if (outcomeUnknown)
|
|
1215
|
+
if (outcomeUnknown && !terminalSubmitOutcome && retainedPendingThreeDs !== null) {
|
|
1068
1216
|
retainPendingThreeDs();
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
clearPendingThreeDs();
|
|
1220
|
+
}
|
|
1069
1221
|
return {
|
|
1070
1222
|
status: paymentStatus,
|
|
1071
1223
|
audit_recorded,
|
|
1072
|
-
|
|
1073
|
-
?
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1224
|
+
...(!terminalSubmitOutcome
|
|
1225
|
+
? {
|
|
1226
|
+
reason: outcomeUnknown
|
|
1227
|
+
? paymentStatus === "payment_3ds_required"
|
|
1228
|
+
? "payment_3ds_required"
|
|
1229
|
+
: "payment_submit_outcome_unknown"
|
|
1230
|
+
: error instanceof Error && /^payment_[a-z_]+(?::[a-z_]+)?$/.test(error.message)
|
|
1231
|
+
? error.message
|
|
1232
|
+
: "payment_checkout_failed",
|
|
1233
|
+
}
|
|
1234
|
+
: {}),
|
|
1077
1235
|
approval_url: approvalUrl,
|
|
1078
1236
|
...(submitResult.payment_instrument_mismatch !== undefined
|
|
1079
1237
|
? { warning: submitResult.payment_instrument_mismatch }
|
|
1080
1238
|
: {}),
|
|
1081
|
-
...(outcomeUnknown
|
|
1239
|
+
...(outcomeUnknown && !terminalSubmitOutcome && retainedPendingThreeDs !== null
|
|
1240
|
+
? { next: pendingOutcomeNext() }
|
|
1241
|
+
: {}),
|
|
1242
|
+
...(paymentStatus === "payment_3ds_required"
|
|
1243
|
+
? {
|
|
1244
|
+
needs_user: {
|
|
1245
|
+
wall: "3ds",
|
|
1246
|
+
message: threeDsHandoffMessage(submitResult, undefined),
|
|
1247
|
+
resume: "checkout",
|
|
1248
|
+
},
|
|
1249
|
+
}
|
|
1250
|
+
: {}),
|
|
1251
|
+
...(paymentStatus === "payment_submitted"
|
|
1252
|
+
? {
|
|
1253
|
+
merchant: checkout.merchant,
|
|
1254
|
+
amount_cents: checkout.amount_cents,
|
|
1255
|
+
currency: checkout.currency,
|
|
1256
|
+
}
|
|
1257
|
+
: {}),
|
|
1082
1258
|
};
|
|
1083
1259
|
}
|
|
1084
1260
|
finally {
|
|
@@ -1090,20 +1266,32 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
1090
1266
|
if (submitResult.payment_instrument_mismatch === undefined &&
|
|
1091
1267
|
!submitResult.order_confirmed &&
|
|
1092
1268
|
threeDsWaitMs > 0) {
|
|
1093
|
-
|
|
1269
|
+
const challengeKnownBeforeWait = pendingThreeDsHandoff.outcome === "three_ds";
|
|
1270
|
+
if (challengeKnownBeforeWait) {
|
|
1271
|
+
getThreeDsTelegramSent = trackThreeDsNotification(api.notifyThreeDs(approvalId, threeDsNotificationMode(submitResult)));
|
|
1272
|
+
}
|
|
1094
1273
|
const resolution = await browser.waitForThreeDsResolution(threeDsWaitMs);
|
|
1095
1274
|
const mismatch = browser.paymentInstrumentMismatch?.();
|
|
1096
1275
|
if (mismatch !== undefined)
|
|
1097
1276
|
submitResult.payment_instrument_mismatch ??= mismatch;
|
|
1098
1277
|
paymentStatus = statusAfterThreeDsResolution(paymentStatus, resolution);
|
|
1278
|
+
if (resolution === "challenge_pending") {
|
|
1279
|
+
pendingThreeDsHandoff.outcome = "three_ds";
|
|
1280
|
+
submitResult.three_ds_required = true;
|
|
1281
|
+
if (!challengeKnownBeforeWait) {
|
|
1282
|
+
getThreeDsTelegramSent = trackThreeDsNotification(api.notifyThreeDs(approvalId, "detected_challenge"));
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1099
1285
|
}
|
|
1100
1286
|
let auditRecorded = true;
|
|
1101
1287
|
try {
|
|
1102
|
-
await
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1288
|
+
await deps.coordinateThreeDsAudit(pendingThreeDsHandoff, async () => {
|
|
1289
|
+
await api.auditPayment({
|
|
1290
|
+
...checkout,
|
|
1291
|
+
last4,
|
|
1292
|
+
status: paymentStatus,
|
|
1293
|
+
...(mandateId !== undefined ? { mandate_id: mandateId } : {}),
|
|
1294
|
+
});
|
|
1107
1295
|
});
|
|
1108
1296
|
}
|
|
1109
1297
|
catch {
|
|
@@ -1121,16 +1309,23 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
1121
1309
|
...(submitResult.payment_instrument_mismatch !== undefined
|
|
1122
1310
|
? { warning: submitResult.payment_instrument_mismatch }
|
|
1123
1311
|
: {}),
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1312
|
+
...(paymentStatus === "payment_3ds_required"
|
|
1313
|
+
? {
|
|
1314
|
+
needs_user: {
|
|
1315
|
+
wall: "3ds",
|
|
1316
|
+
message: threeDsHandoffMessage(submitResult, getThreeDsTelegramSent()),
|
|
1317
|
+
resume: "checkout",
|
|
1318
|
+
...(submitResult.challenge_url !== undefined
|
|
1319
|
+
? { url: submitResult.challenge_url }
|
|
1320
|
+
: {}),
|
|
1321
|
+
},
|
|
1322
|
+
}
|
|
1323
|
+
: {}),
|
|
1324
|
+
next: pendingOutcomeNext(),
|
|
1131
1325
|
};
|
|
1132
1326
|
}
|
|
1133
1327
|
if (paymentStatus === "payment_declined") {
|
|
1328
|
+
clearPendingThreeDs();
|
|
1134
1329
|
return {
|
|
1135
1330
|
status: paymentStatus,
|
|
1136
1331
|
audit_recorded: auditRecorded,
|
|
@@ -1140,6 +1335,7 @@ export async function executeOperatePay(args, api, browser, overrides = {}) {
|
|
|
1140
1335
|
: {}),
|
|
1141
1336
|
};
|
|
1142
1337
|
}
|
|
1338
|
+
clearPendingThreeDs();
|
|
1143
1339
|
return {
|
|
1144
1340
|
status: paymentStatus,
|
|
1145
1341
|
audit_recorded: auditRecorded,
|