@devlab.group/agent-commerce 2.4.1 → 2.5.0
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 +115 -15
- package/dist/ap2.d.ts +24 -12
- package/dist/ap2.js +31 -13
- package/dist/ap2.js.map +1 -1
- package/dist/{chunk-DTKU3Z2M.js → chunk-3OTCFFFT.js} +37 -4
- package/dist/chunk-3OTCFFFT.js.map +1 -0
- package/dist/{chunk-QPV3EGJO.js → chunk-4K7FIBJM.js} +25 -4
- package/dist/chunk-4K7FIBJM.js.map +1 -0
- package/dist/{chunk-CXWC5LWM.js → chunk-FKQK5XZA.js} +652 -68
- package/dist/chunk-FKQK5XZA.js.map +1 -0
- package/dist/chunk-GA3O2A7R.js +92 -0
- package/dist/chunk-GA3O2A7R.js.map +1 -0
- package/dist/{chunk-WZW5ORZH.js → chunk-VUCMBGUR.js} +3 -3
- package/dist/{chunk-WZW5ORZH.js.map → chunk-VUCMBGUR.js.map} +1 -1
- package/dist/cli/{ap2-GWSP4LYY.js → ap2-YAPLERSC.js} +34 -15
- package/dist/cli/ap2-YAPLERSC.js.map +1 -0
- package/dist/cli/{chunk-G2JEY7MB.js → chunk-2J2IQQ4X.js} +8 -7
- package/dist/cli/chunk-2J2IQQ4X.js.map +1 -0
- package/dist/cli/chunk-2UPPX3FJ.js +311 -0
- package/dist/cli/chunk-2UPPX3FJ.js.map +1 -0
- package/dist/cli/{chunk-WYOGIANL.js → chunk-4H2QLHQI.js} +4 -4
- package/dist/cli/{chunk-WYOGIANL.js.map → chunk-4H2QLHQI.js.map} +1 -1
- package/dist/cli/{chunk-OZ4LAWBK.js → chunk-5R7DM5LM.js} +25 -4
- package/dist/cli/chunk-5R7DM5LM.js.map +1 -0
- package/dist/cli/chunk-663ALDZA.js +92 -0
- package/dist/cli/chunk-663ALDZA.js.map +1 -0
- package/dist/cli/{chunk-QQ3WY5L3.js → chunk-IDY2735M.js} +36 -104
- package/dist/cli/chunk-IDY2735M.js.map +1 -0
- package/dist/cli/chunk-JFVRG2HH.js +107 -0
- package/dist/cli/chunk-JFVRG2HH.js.map +1 -0
- package/dist/cli/chunk-M7NXVRU5.js +8 -0
- package/dist/cli/{chunk-ITWNSS2B.js.map → chunk-M7NXVRU5.js.map} +1 -1
- package/dist/cli/chunk-NIU5EMSB.js +119 -0
- package/dist/cli/chunk-NIU5EMSB.js.map +1 -0
- package/dist/cli/{chunk-TNMLDPUO.js → chunk-QA2OPHBF.js} +655 -70
- package/dist/cli/chunk-QA2OPHBF.js.map +1 -0
- package/dist/cli/{chunk-GJIMDMYT.js → chunk-QLUCNPRT.js} +404 -161
- package/dist/cli/chunk-QLUCNPRT.js.map +1 -0
- package/dist/cli/chunk-SYQLJXIU.js +105 -0
- package/dist/cli/chunk-SYQLJXIU.js.map +1 -0
- package/dist/cli/{chunk-3MGAJ5FX.js → chunk-VJ4EO4LE.js} +3 -3
- package/dist/cli/{chunk-3MGAJ5FX.js.map → chunk-VJ4EO4LE.js.map} +1 -1
- package/dist/cli/{chunk-PIMFPCNZ.js → chunk-Z34UMZBB.js} +234 -85
- package/dist/cli/chunk-Z34UMZBB.js.map +1 -0
- package/dist/cli/config-BWFMN2ZB.js +12 -0
- package/dist/cli/{config-UI4EDG4L.js.map → config-BWFMN2ZB.js.map} +1 -1
- package/dist/cli/index.js +258 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-PGBRWZVA.js → mcp-KZEMXLHR.js} +5 -4
- package/dist/cli/mcp-KZEMXLHR.js.map +1 -0
- package/dist/cli/{near-5AY3HWYF.js → near-U6365VFX.js} +15 -4
- package/dist/cli/near-U6365VFX.js.map +1 -0
- package/dist/cli/payment-providers-SZL36H3Y.js +6 -0
- package/dist/cli/payment-providers-SZL36H3Y.js.map +1 -0
- package/dist/cli/{provider-I2ESL6RS.js → provider-6TKB5ADJ.js} +26 -15
- package/dist/cli/provider-6TKB5ADJ.js.map +1 -0
- package/dist/cli/receipts-ZGLJWMQG.js +8 -0
- package/dist/cli/receipts-ZGLJWMQG.js.map +1 -0
- package/dist/cli/reconcile-IC22MJD5.js +4 -0
- package/dist/cli/reconcile-IC22MJD5.js.map +1 -0
- package/dist/cli/{run-JK4ERPJV.js → run-ZTYPQI4D.js} +151 -115
- package/dist/cli/run-ZTYPQI4D.js.map +1 -0
- package/dist/cli/svm-KDSPDFJG.js +48 -0
- package/dist/cli/svm-KDSPDFJG.js.map +1 -0
- package/dist/cli/{x402-7WBFZQXV.js → x402-WLYBLODI.js} +10 -9
- package/dist/cli/x402-WLYBLODI.js.map +1 -0
- package/dist/index.d.ts +112 -12
- package/dist/index.js +948 -157
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +1 -2
- package/dist/mcp.js +1 -1
- package/dist/mpp.js +20 -10
- package/dist/mpp.js.map +1 -1
- package/dist/{near-D6C42VPE.js → near-FH7HPZV3.js} +15 -4
- package/dist/near-FH7HPZV3.js.map +1 -0
- package/dist/{protocol-adapter-C9odYl6l.d.ts → protocol-adapter-DEXpdp8m.d.ts} +102 -48
- package/dist/{runtime-Cl_BLVZV.d.ts → runtime-Dz2fSO-P.d.ts} +220 -2
- package/dist/svm-KNC3PHMS.js +48 -0
- package/dist/svm-KNC3PHMS.js.map +1 -0
- package/dist/x402.js +5 -5
- package/package.json +1 -1
- package/dist/authorization-DjAecwuW.d.ts +0 -115
- package/dist/chunk-CXWC5LWM.js.map +0 -1
- package/dist/chunk-DTKU3Z2M.js.map +0 -1
- package/dist/chunk-KA2AX5FH.js +0 -44
- package/dist/chunk-KA2AX5FH.js.map +0 -1
- package/dist/chunk-QPV3EGJO.js.map +0 -1
- package/dist/cli/ap2-GWSP4LYY.js.map +0 -1
- package/dist/cli/chunk-G2JEY7MB.js.map +0 -1
- package/dist/cli/chunk-GJIMDMYT.js.map +0 -1
- package/dist/cli/chunk-ITWNSS2B.js +0 -8
- package/dist/cli/chunk-OSTCU6ZK.js +0 -44
- package/dist/cli/chunk-OSTCU6ZK.js.map +0 -1
- package/dist/cli/chunk-OZ4LAWBK.js.map +0 -1
- package/dist/cli/chunk-PIMFPCNZ.js.map +0 -1
- package/dist/cli/chunk-QQ3WY5L3.js.map +0 -1
- package/dist/cli/chunk-TNMLDPUO.js.map +0 -1
- package/dist/cli/config-UI4EDG4L.js +0 -11
- package/dist/cli/mcp-PGBRWZVA.js.map +0 -1
- package/dist/cli/near-5AY3HWYF.js.map +0 -1
- package/dist/cli/provider-I2ESL6RS.js.map +0 -1
- package/dist/cli/run-JK4ERPJV.js.map +0 -1
- package/dist/cli/svm-3MMYFPC7.js +0 -33
- package/dist/cli/svm-3MMYFPC7.js.map +0 -1
- package/dist/cli/x402-7WBFZQXV.js.map +0 -1
- package/dist/near-D6C42VPE.js.map +0 -1
- package/dist/svm-QEUAUNPQ.js +0 -33
- package/dist/svm-QEUAUNPQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { openSqliteDatabase, AP2_SPEC_VERSION, AP2_MODES, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_JWK_MEMBERS, AP2_KEY_TYPE, AP2_SIGNING_ALGORITHM, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, AP2_MAX_CLOCK_SKEW_SECONDS } from './chunk-
|
|
1
|
+
import { openSqliteDatabase, AP2_SPEC_VERSION, AP2_MODES, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_JWK_MEMBERS, AP2_KEY_TYPE, AP2_SIGNING_ALGORITHM, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, AP2_MAX_CLOCK_SKEW_SECONDS } from './chunk-VUCMBGUR.js';
|
|
2
2
|
import { readCappedBody, createBearerCheck, MCP_TOOL_NAME_PATTERN, settlementResponse, joinUrl, settlementResponseFromDetails, settlementFailure, x402ErrorCode, x402RefusalStatus } from './chunk-ES3V6ECS.js';
|
|
3
3
|
import { MPP_DEFAULT_NETWORK, isMppNetwork, MPP_NETWORKS, MPP_DEFAULT_PAYMENT_FLOW, MPP_MIN_CHALLENGE_SECRET_LENGTH, MPP_PROFILE } from './chunk-6Z2H53CG.js';
|
|
4
4
|
import { contentDigest, CONTENT_DIGEST_METADATA_KEY, mppProblem, mppProblemDetail } from './chunk-5ZYXBCDX.js';
|
|
5
|
-
import { X402_PAYMENT_FLOWS, validateCustomNetworks, findX402Network, X402_DEFAULT_PAYMENT_FLOW, resolveX402Deployment,
|
|
6
|
-
import { RESERVED_INPUT_FIELDS, PROTOCOL_NAMES, PAYMENT_METHOD_NAMES, PAYMENT_HEADER, AUTHORIZATION_HEADER, CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION, toCommerceError, DEFAULT_BACKEND_TIMEOUT_MS, isCommerceError, describeError, isHttpProtocolAdapter, toLogInfo, extractReservedInputFields, parseAuthorizationHeader, toPaymentRequiredEnvelope, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, toErrorEnvelope, DELIVERY_SUMMARY_META_KEY, toDeliverySummary, PAYMENT_INPUT_FIELD, AUTHORIZATION_INPUT_FIELD, parseAuthorizationSubmission } from './chunk-
|
|
7
|
-
export { AUTHORIZATION_HEADER, AUTHORIZATION_INPUT_FIELD, COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, MAX_AUTHORIZATION_HEADER_BYTES, NOOP_LOGGER, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_METHOD_NAMES, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PROTOCOL_NAMES, RESERVED_INPUT_FIELDS, RETRYABLE_ERROR_CODES, extractReservedInputFields, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, parseAuthorizationHeader, parseAuthorizationSubmission, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope } from './chunk-
|
|
5
|
+
import { X402_PAYMENT_FLOWS, validateCustomNetworks, findX402Network, X402_DEFAULT_PAYMENT_FLOW, resolveX402Deployment, resolveDeploymentMode, requireX402Network, resolveX402Option, isEvmAddress, isEvmZeroAddress, SETTLE_AFTER_BACKEND_METADATA_KEY, x402OfferName, REUSE_REJECTED_AUTHORIZATION_METADATA_KEY, canonicalJson } from './chunk-4K7FIBJM.js';
|
|
6
|
+
import { RESERVED_INPUT_FIELDS, PROTOCOL_NAMES, PAYMENT_METHOD_NAMES, PAYMENT_HEADER, AUTHORIZATION_HEADER, CommerceError, systemClock, NOOP_LOGGER, transactionState, PACKAGE_VERSION, toCommerceError, DEFAULT_BACKEND_TIMEOUT_MS, isCommerceError, describeError, isHttpProtocolAdapter, toLogInfo, extractReservedInputFields, parseAuthorizationHeader, toPaymentRequiredEnvelope, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, toErrorEnvelope, DELIVERY_SUMMARY_META_KEY, toDeliverySummary, PAYMENT_INPUT_FIELD, AUTHORIZATION_INPUT_FIELD, parseAuthorizationSubmission, RECONCILIATION_METADATA_KEY, TRANSACTION_STATES, redactedErrorText } from './chunk-3OTCFFFT.js';
|
|
7
|
+
export { AUTHORIZATION_HEADER, AUTHORIZATION_INPUT_FIELD, COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, MAX_AUTHORIZATION_HEADER_BYTES, NOOP_LOGGER, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_METHOD_NAMES, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PROTOCOL_NAMES, RECONCILIATION_METADATA_KEY, RESERVED_INPUT_FIELDS, RETRYABLE_ERROR_CODES, TRANSACTION_STATES, extractReservedInputFields, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, parseAuthorizationHeader, parseAuthorizationSubmission, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope, transactionState } from './chunk-3OTCFFFT.js';
|
|
8
8
|
import { isRecord } from './chunk-2CJNA7MS.js';
|
|
9
9
|
import { promises, accessSync, constants } from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { parse, YAMLError } from 'yaml';
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { randomUUID, createHash } from 'crypto';
|
|
13
14
|
import { isDeepStrictEqual } from 'util';
|
|
14
15
|
import Fastify from 'fastify';
|
|
15
|
-
import { randomUUID, createHash } from 'crypto';
|
|
16
16
|
import { createRequire } from 'module';
|
|
17
17
|
import pino from 'pino';
|
|
18
18
|
import { Transform } from 'stream';
|
|
@@ -1169,7 +1169,7 @@ var TRAVERSAL_PATH_VALUES = /* @__PURE__ */ new Set(["", ".", ".."]);
|
|
|
1169
1169
|
function applyPathTemplate(template, input, context) {
|
|
1170
1170
|
const remaining = { ...input };
|
|
1171
1171
|
let missing;
|
|
1172
|
-
let
|
|
1172
|
+
let invalid3;
|
|
1173
1173
|
const url = template.replace(PATH_PARAM_PATTERN, (_match, key) => {
|
|
1174
1174
|
if (!Object.hasOwn(remaining, key)) {
|
|
1175
1175
|
missing = key;
|
|
@@ -1179,7 +1179,7 @@ function applyPathTemplate(template, input, context) {
|
|
|
1179
1179
|
delete remaining[key];
|
|
1180
1180
|
const raw = stringifyPrimitive(value);
|
|
1181
1181
|
if (TRAVERSAL_PATH_VALUES.has(raw)) {
|
|
1182
|
-
|
|
1182
|
+
invalid3 = key;
|
|
1183
1183
|
return "";
|
|
1184
1184
|
}
|
|
1185
1185
|
return encodeURIComponent(raw);
|
|
@@ -1190,10 +1190,10 @@ function applyPathTemplate(template, input, context) {
|
|
|
1190
1190
|
details: { field: missing }
|
|
1191
1191
|
});
|
|
1192
1192
|
}
|
|
1193
|
-
if (
|
|
1194
|
-
throw new CommerceError("INPUT_INVALID", `Path parameter "${
|
|
1193
|
+
if (invalid3 !== void 0) {
|
|
1194
|
+
throw new CommerceError("INPUT_INVALID", `Path parameter "${invalid3}" is not a valid value`, {
|
|
1195
1195
|
...context,
|
|
1196
|
-
details: { field:
|
|
1196
|
+
details: { field: invalid3 }
|
|
1197
1197
|
});
|
|
1198
1198
|
}
|
|
1199
1199
|
return { url, remaining };
|
|
@@ -1446,6 +1446,56 @@ function safeStringify(value) {
|
|
|
1446
1446
|
function isAbortError(error) {
|
|
1447
1447
|
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
1448
1448
|
}
|
|
1449
|
+
function createFlow(request, transactionId) {
|
|
1450
|
+
return { request, transactionId, persistenceFailures: [], journaled: false };
|
|
1451
|
+
}
|
|
1452
|
+
function failuresOf(flow) {
|
|
1453
|
+
return flow.persistenceFailures.length > 0 ? { persistenceFailures: [...flow.persistenceFailures] } : {};
|
|
1454
|
+
}
|
|
1455
|
+
function sha256(text) {
|
|
1456
|
+
return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
|
|
1457
|
+
}
|
|
1458
|
+
function inputDigest(input) {
|
|
1459
|
+
const json = canonicalJson(input);
|
|
1460
|
+
return json !== void 0 ? sha256(json) : void 0;
|
|
1461
|
+
}
|
|
1462
|
+
function configDigest(resource, requirement) {
|
|
1463
|
+
const terms = {
|
|
1464
|
+
resourceId: resource.id,
|
|
1465
|
+
pricing: resource.pricing,
|
|
1466
|
+
paymentMethods: resource.paymentMethods,
|
|
1467
|
+
authorization: resource.authorization?.required ?? [],
|
|
1468
|
+
requirement: {
|
|
1469
|
+
provider: requirement.provider,
|
|
1470
|
+
amount: requirement.amount,
|
|
1471
|
+
currency: requirement.currency,
|
|
1472
|
+
destination: requirement.destination ?? null,
|
|
1473
|
+
network: requirement.network ?? null,
|
|
1474
|
+
asset: requirement.asset ?? null,
|
|
1475
|
+
settleAfterBackend: requirement.metadata?.[SETTLE_AFTER_BACKEND_METADATA_KEY] === true
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
return sha256(canonicalJson(terms) ?? JSON.stringify(terms));
|
|
1479
|
+
}
|
|
1480
|
+
function newTransaction(flow, resource, requirement, verification, validInput, authorization) {
|
|
1481
|
+
const raw = verification.metadata?.[RECONCILIATION_METADATA_KEY];
|
|
1482
|
+
const locator = isRecord(raw) ? raw : void 0;
|
|
1483
|
+
const expiresAt = typeof locator?.["expiresAt"] === "string" ? locator["expiresAt"] : void 0;
|
|
1484
|
+
const network = verification.network ?? requirement.network;
|
|
1485
|
+
const asset = verification.asset ?? requirement.asset;
|
|
1486
|
+
const digest = inputDigest(validInput);
|
|
1487
|
+
return {
|
|
1488
|
+
id: flow.transactionId,
|
|
1489
|
+
protocol: flow.request.protocol,
|
|
1490
|
+
...network !== void 0 ? { network } : {},
|
|
1491
|
+
...asset !== void 0 ? { asset } : {},
|
|
1492
|
+
...locator !== void 0 ? { locator } : {},
|
|
1493
|
+
...expiresAt !== void 0 ? { expiresAt } : {},
|
|
1494
|
+
...authorization !== void 0 ? { authorization } : {},
|
|
1495
|
+
...digest !== void 0 ? { inputDigest: digest } : {},
|
|
1496
|
+
configDigest: configDigest(resource, requirement)
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1449
1499
|
var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
1450
1500
|
"object",
|
|
1451
1501
|
"string",
|
|
@@ -1588,16 +1638,15 @@ function resolveAuthorizationProviders(providers, resource, requestId) {
|
|
|
1588
1638
|
}
|
|
1589
1639
|
function createAuthorizeAndReserve(ctx) {
|
|
1590
1640
|
const { options, safeEmit, buildEvent } = ctx;
|
|
1591
|
-
async function authorizeAndReserve(request, resource, providers, requirement, input) {
|
|
1641
|
+
async function authorizeAndReserve(flow, request, resource, providers, requirement, input) {
|
|
1592
1642
|
if (providers.length === 0) return void 0;
|
|
1593
1643
|
const submission = request.authorization;
|
|
1594
1644
|
const provider = providers.find((candidate) => candidate.name === submission?.method);
|
|
1595
1645
|
const unmet = providers.filter((candidate) => candidate !== provider).map((c) => c.name);
|
|
1596
1646
|
if (submission === void 0 || provider === void 0 || unmet.length > 0) {
|
|
1597
1647
|
await safeEmit(
|
|
1598
|
-
buildEvent({
|
|
1648
|
+
buildEvent(flow, {
|
|
1599
1649
|
type: "authorization.rejected",
|
|
1600
|
-
requestId: request.requestId,
|
|
1601
1650
|
resourceId: resource.id,
|
|
1602
1651
|
adapter: request.protocol,
|
|
1603
1652
|
status: "error",
|
|
@@ -1634,9 +1683,8 @@ function createAuthorizeAndReserve(ctx) {
|
|
|
1634
1683
|
}
|
|
1635
1684
|
);
|
|
1636
1685
|
await safeEmit(
|
|
1637
|
-
buildEvent({
|
|
1686
|
+
buildEvent(flow, {
|
|
1638
1687
|
type: "authorization.rejected",
|
|
1639
|
-
requestId: request.requestId,
|
|
1640
1688
|
resourceId: resource.id,
|
|
1641
1689
|
adapter: request.protocol,
|
|
1642
1690
|
status: "error",
|
|
@@ -1646,9 +1694,8 @@ function createAuthorizeAndReserve(ctx) {
|
|
|
1646
1694
|
throw mapped;
|
|
1647
1695
|
}
|
|
1648
1696
|
await safeEmit(
|
|
1649
|
-
buildEvent({
|
|
1697
|
+
buildEvent(flow, {
|
|
1650
1698
|
type: "authorization.verified",
|
|
1651
|
-
requestId: request.requestId,
|
|
1652
1699
|
resourceId: resource.id,
|
|
1653
1700
|
adapter: request.protocol,
|
|
1654
1701
|
status: "ok",
|
|
@@ -1662,14 +1709,17 @@ function createAuthorizeAndReserve(ctx) {
|
|
|
1662
1709
|
reference: verification.reference,
|
|
1663
1710
|
...verification.metadata !== void 0 ? { metadata: verification.metadata } : {}
|
|
1664
1711
|
},
|
|
1712
|
+
reservationId: verification.reservationId,
|
|
1665
1713
|
async finalize(action) {
|
|
1666
1714
|
try {
|
|
1667
1715
|
await provider[action](verification.reservationId, context);
|
|
1716
|
+
return true;
|
|
1668
1717
|
} catch (error) {
|
|
1669
1718
|
options.logger.error(
|
|
1670
1719
|
{ err: describeError(error), requestId: request.requestId, action },
|
|
1671
1720
|
"Authorization finalization failed; the reservation stays reserved"
|
|
1672
1721
|
);
|
|
1722
|
+
return false;
|
|
1673
1723
|
}
|
|
1674
1724
|
}
|
|
1675
1725
|
};
|
|
@@ -1710,7 +1760,7 @@ function replayError(error, requirement) {
|
|
|
1710
1760
|
// src/core/execution/pipeline/admission.ts
|
|
1711
1761
|
function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
1712
1762
|
const { options, safeEmit, buildEvent } = ctx;
|
|
1713
|
-
return async function admitPayment(request, resource, pricing, validInput) {
|
|
1763
|
+
return async function admitPayment(flow, request, resource, pricing, validInput) {
|
|
1714
1764
|
const provider = pickProvider(options.paymentProviders, resource.paymentMethods);
|
|
1715
1765
|
if (!provider) {
|
|
1716
1766
|
throw new CommerceError(
|
|
@@ -1749,9 +1799,8 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1749
1799
|
}
|
|
1750
1800
|
if (!request.payment) {
|
|
1751
1801
|
await safeEmit(
|
|
1752
|
-
buildEvent({
|
|
1802
|
+
buildEvent(flow, {
|
|
1753
1803
|
type: "payment.required",
|
|
1754
|
-
requestId: request.requestId,
|
|
1755
1804
|
resourceId: resource.id,
|
|
1756
1805
|
adapter: request.protocol,
|
|
1757
1806
|
paymentProvider: provider.name
|
|
@@ -1760,6 +1809,7 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1760
1809
|
return {
|
|
1761
1810
|
kind: "payment-required",
|
|
1762
1811
|
requestId: request.requestId,
|
|
1812
|
+
transactionId: flow.transactionId,
|
|
1763
1813
|
resourceId: resource.id,
|
|
1764
1814
|
requirement,
|
|
1765
1815
|
...authProviders.length > 0 ? { authorization: authProviders.map((p) => p.requirement) } : {}
|
|
@@ -1790,9 +1840,8 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1790
1840
|
...retryChallenge(requirement)
|
|
1791
1841
|
});
|
|
1792
1842
|
await safeEmit(
|
|
1793
|
-
buildEvent({
|
|
1843
|
+
buildEvent(flow, {
|
|
1794
1844
|
type: "payment.rejected",
|
|
1795
|
-
requestId: request.requestId,
|
|
1796
1845
|
resourceId: resource.id,
|
|
1797
1846
|
adapter: request.protocol,
|
|
1798
1847
|
paymentProvider: provider.name,
|
|
@@ -1804,9 +1853,8 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1804
1853
|
}
|
|
1805
1854
|
if (verification.status !== "verified") {
|
|
1806
1855
|
await safeEmit(
|
|
1807
|
-
buildEvent({
|
|
1856
|
+
buildEvent(flow, {
|
|
1808
1857
|
type: "payment.rejected",
|
|
1809
|
-
requestId: request.requestId,
|
|
1810
1858
|
resourceId: resource.id,
|
|
1811
1859
|
adapter: request.protocol,
|
|
1812
1860
|
paymentProvider: provider.name,
|
|
@@ -1826,9 +1874,8 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1826
1874
|
}
|
|
1827
1875
|
if (!verification.replayKey) {
|
|
1828
1876
|
await safeEmit(
|
|
1829
|
-
buildEvent({
|
|
1877
|
+
buildEvent(flow, {
|
|
1830
1878
|
type: "payment.rejected",
|
|
1831
|
-
requestId: request.requestId,
|
|
1832
1879
|
resourceId: resource.id,
|
|
1833
1880
|
adapter: request.protocol,
|
|
1834
1881
|
paymentProvider: provider.name,
|
|
@@ -1843,6 +1890,7 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1843
1890
|
}
|
|
1844
1891
|
const replayKey = verification.replayKey;
|
|
1845
1892
|
const hold = await authorizeAndReserve(
|
|
1893
|
+
flow,
|
|
1846
1894
|
request,
|
|
1847
1895
|
resource,
|
|
1848
1896
|
authProviders,
|
|
@@ -1850,7 +1898,7 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1850
1898
|
validInput
|
|
1851
1899
|
);
|
|
1852
1900
|
try {
|
|
1853
|
-
await options.store.reservePaymentAttempt({
|
|
1901
|
+
const attempt = await options.store.reservePaymentAttempt({
|
|
1854
1902
|
requestId: request.requestId,
|
|
1855
1903
|
resourceId: resource.id,
|
|
1856
1904
|
provider: provider.name,
|
|
@@ -1859,8 +1907,17 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1859
1907
|
currency: verification.currency,
|
|
1860
1908
|
...verification.payer !== void 0 ? { payer: verification.payer } : {},
|
|
1861
1909
|
...verification.payee !== void 0 ? { payee: verification.payee } : {},
|
|
1862
|
-
...requirement.metadata?.[REUSE_REJECTED_AUTHORIZATION_METADATA_KEY] === true ? { reclaimRejected: true } : {}
|
|
1910
|
+
...requirement.metadata?.[REUSE_REJECTED_AUTHORIZATION_METADATA_KEY] === true ? { reclaimRejected: true } : {},
|
|
1911
|
+
transaction: newTransaction(
|
|
1912
|
+
flow,
|
|
1913
|
+
resource,
|
|
1914
|
+
requirement,
|
|
1915
|
+
verification,
|
|
1916
|
+
validInput,
|
|
1917
|
+
hold !== void 0 ? { ...hold.record, reservationId: hold.reservationId } : void 0
|
|
1918
|
+
)
|
|
1863
1919
|
});
|
|
1920
|
+
flow.journaled = attempt.transactionId === flow.transactionId && options.store.recordTransactionStep !== void 0;
|
|
1864
1921
|
} catch (error) {
|
|
1865
1922
|
const mapped = toCommerceError(
|
|
1866
1923
|
error,
|
|
@@ -1869,9 +1926,8 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1869
1926
|
);
|
|
1870
1927
|
await hold?.finalize("release");
|
|
1871
1928
|
await safeEmit(
|
|
1872
|
-
buildEvent({
|
|
1929
|
+
buildEvent(flow, {
|
|
1873
1930
|
type: "payment.rejected",
|
|
1874
|
-
requestId: request.requestId,
|
|
1875
1931
|
resourceId: resource.id,
|
|
1876
1932
|
adapter: request.protocol,
|
|
1877
1933
|
paymentProvider: provider.name,
|
|
@@ -1882,9 +1938,8 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1882
1938
|
throw replayError(mapped, requirement);
|
|
1883
1939
|
}
|
|
1884
1940
|
await safeEmit(
|
|
1885
|
-
buildEvent({
|
|
1941
|
+
buildEvent(flow, {
|
|
1886
1942
|
type: "payment.verified",
|
|
1887
|
-
requestId: request.requestId,
|
|
1888
1943
|
resourceId: resource.id,
|
|
1889
1944
|
adapter: request.protocol,
|
|
1890
1945
|
paymentProvider: provider.name,
|
|
@@ -1894,6 +1949,7 @@ function createPaymentAdmission(ctx, authorizeAndReserve) {
|
|
|
1894
1949
|
return {
|
|
1895
1950
|
kind: "admitted",
|
|
1896
1951
|
settleArgs: {
|
|
1952
|
+
flow,
|
|
1897
1953
|
request,
|
|
1898
1954
|
resource,
|
|
1899
1955
|
provider,
|
|
@@ -1915,6 +1971,11 @@ function pickProvider(providers, methods) {
|
|
|
1915
1971
|
}
|
|
1916
1972
|
|
|
1917
1973
|
// src/core/execution/pipeline/context.ts
|
|
1974
|
+
var HOLD_STATES = {
|
|
1975
|
+
consume: "consumed",
|
|
1976
|
+
release: "released",
|
|
1977
|
+
markUncertain: "uncertain"
|
|
1978
|
+
};
|
|
1918
1979
|
function createPipelineContext(options) {
|
|
1919
1980
|
async function safeEmit(event) {
|
|
1920
1981
|
try {
|
|
@@ -1926,27 +1987,73 @@ function createPipelineContext(options) {
|
|
|
1926
1987
|
);
|
|
1927
1988
|
}
|
|
1928
1989
|
}
|
|
1929
|
-
async function safePersist(op, label,
|
|
1990
|
+
async function safePersist(flow, op, label, failure) {
|
|
1930
1991
|
try {
|
|
1931
1992
|
await op();
|
|
1993
|
+
return true;
|
|
1932
1994
|
} catch (error) {
|
|
1933
1995
|
options.logger.error(
|
|
1934
|
-
{
|
|
1996
|
+
{
|
|
1997
|
+
err: describeError(error),
|
|
1998
|
+
requestId: flow.request.requestId,
|
|
1999
|
+
transactionId: flow.transactionId
|
|
2000
|
+
},
|
|
1935
2001
|
`Persistence failed: ${label}`
|
|
1936
2002
|
);
|
|
2003
|
+
if (failure !== void 0 && !flow.persistenceFailures.includes(failure)) {
|
|
2004
|
+
flow.persistenceFailures.push(failure);
|
|
2005
|
+
}
|
|
2006
|
+
return false;
|
|
1937
2007
|
}
|
|
1938
2008
|
}
|
|
1939
|
-
function
|
|
1940
|
-
|
|
2009
|
+
async function recordStep(flow, step) {
|
|
2010
|
+
const record = options.store.recordTransactionStep;
|
|
2011
|
+
if (!flow.journaled || record === void 0) return;
|
|
2012
|
+
const full = {
|
|
2013
|
+
...step,
|
|
2014
|
+
transactionId: flow.transactionId,
|
|
2015
|
+
at: options.clock.nowIso()
|
|
2016
|
+
};
|
|
2017
|
+
await safePersist(
|
|
2018
|
+
flow,
|
|
2019
|
+
() => record.call(options.store, full),
|
|
2020
|
+
`recordTransactionStep(${step.kind})`,
|
|
2021
|
+
"journal"
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
async function finalizeHold(flow, hold, action) {
|
|
2025
|
+
if (hold !== void 0 && await hold.finalize(action)) {
|
|
2026
|
+
await recordStep(flow, { kind: "hold", hold: HOLD_STATES[action] });
|
|
2027
|
+
}
|
|
1941
2028
|
}
|
|
1942
|
-
|
|
2029
|
+
function buildEvent(flow, partial) {
|
|
2030
|
+
return {
|
|
2031
|
+
id: options.ids.next("evt"),
|
|
2032
|
+
at: options.clock.nowIso(),
|
|
2033
|
+
requestId: flow.request.requestId,
|
|
2034
|
+
transactionId: flow.transactionId,
|
|
2035
|
+
...partial
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
return { options, safeEmit, safePersist, recordStep, finalizeHold, buildEvent };
|
|
1943
2039
|
}
|
|
1944
2040
|
|
|
1945
2041
|
// src/core/execution/pipeline/settlement.ts
|
|
1946
2042
|
function createSettlement(ctx) {
|
|
1947
|
-
const { options, safeEmit, safePersist, buildEvent } = ctx;
|
|
2043
|
+
const { options, safeEmit, safePersist, recordStep, finalizeHold, buildEvent } = ctx;
|
|
1948
2044
|
async function settle(args) {
|
|
1949
|
-
const {
|
|
2045
|
+
const {
|
|
2046
|
+
flow,
|
|
2047
|
+
request,
|
|
2048
|
+
resource,
|
|
2049
|
+
provider,
|
|
2050
|
+
requirement,
|
|
2051
|
+
payment,
|
|
2052
|
+
verification,
|
|
2053
|
+
replayKey,
|
|
2054
|
+
hold
|
|
2055
|
+
} = args;
|
|
2056
|
+
await recordStep(flow, { kind: "settling" });
|
|
1950
2057
|
let settlement;
|
|
1951
2058
|
try {
|
|
1952
2059
|
settlement = await provider.settle({
|
|
@@ -1959,25 +2066,29 @@ function createSettlement(ctx) {
|
|
|
1959
2066
|
} catch (error) {
|
|
1960
2067
|
const notSubmitted = notSubmittedReason(error);
|
|
1961
2068
|
if (notSubmitted !== void 0) {
|
|
1962
|
-
await hold
|
|
2069
|
+
await finalizeHold(flow, hold, "release");
|
|
1963
2070
|
await safePersist(
|
|
2071
|
+
flow,
|
|
1964
2072
|
() => options.store.updatePaymentAttempt({
|
|
1965
2073
|
replayKey,
|
|
1966
2074
|
status: "rejected",
|
|
1967
2075
|
rejectionReason: notSubmitted
|
|
1968
2076
|
}),
|
|
1969
2077
|
"updatePaymentAttempt(not-submitted)",
|
|
1970
|
-
|
|
2078
|
+
"payment-attempt"
|
|
1971
2079
|
);
|
|
1972
2080
|
await safeEmit(
|
|
1973
|
-
buildEvent({
|
|
2081
|
+
buildEvent(flow, {
|
|
1974
2082
|
type: "payment.rejected",
|
|
1975
|
-
requestId: request.requestId,
|
|
1976
2083
|
resourceId: resource.id,
|
|
1977
2084
|
adapter: request.protocol,
|
|
1978
2085
|
paymentProvider: provider.name,
|
|
1979
2086
|
status: "error",
|
|
1980
|
-
data: {
|
|
2087
|
+
data: {
|
|
2088
|
+
reason: "settlement-not-submitted",
|
|
2089
|
+
providerReason: notSubmitted,
|
|
2090
|
+
...failuresOf(flow)
|
|
2091
|
+
}
|
|
1981
2092
|
})
|
|
1982
2093
|
);
|
|
1983
2094
|
throw new CommerceError(
|
|
@@ -1987,8 +2098,9 @@ function createSettlement(ctx) {
|
|
|
1987
2098
|
);
|
|
1988
2099
|
}
|
|
1989
2100
|
const txHash = settlementTxHash(error);
|
|
1990
|
-
await hold
|
|
2101
|
+
await finalizeHold(flow, hold, "markUncertain");
|
|
1991
2102
|
await safePersist(
|
|
2103
|
+
flow,
|
|
1992
2104
|
() => options.store.updatePaymentAttempt({
|
|
1993
2105
|
replayKey,
|
|
1994
2106
|
status: "settlement-uncertain",
|
|
@@ -1996,19 +2108,19 @@ function createSettlement(ctx) {
|
|
|
1996
2108
|
...error instanceof Error ? { rejectionReason: error.message } : {}
|
|
1997
2109
|
}),
|
|
1998
2110
|
"updatePaymentAttempt(settlement-uncertain)",
|
|
1999
|
-
|
|
2111
|
+
"payment-attempt"
|
|
2000
2112
|
);
|
|
2001
2113
|
await safeEmit(
|
|
2002
|
-
buildEvent({
|
|
2114
|
+
buildEvent(flow, {
|
|
2003
2115
|
type: "payment.rejected",
|
|
2004
|
-
requestId: request.requestId,
|
|
2005
2116
|
resourceId: resource.id,
|
|
2006
2117
|
adapter: request.protocol,
|
|
2007
2118
|
paymentProvider: provider.name,
|
|
2008
2119
|
status: "error",
|
|
2009
2120
|
data: {
|
|
2010
2121
|
reason: "settlement-uncertain",
|
|
2011
|
-
...txHash !== void 0 ? { transactionHash: txHash } : {}
|
|
2122
|
+
...txHash !== void 0 ? { transactionHash: txHash } : {},
|
|
2123
|
+
...failuresOf(flow)
|
|
2012
2124
|
}
|
|
2013
2125
|
})
|
|
2014
2126
|
);
|
|
@@ -2024,8 +2136,9 @@ function createSettlement(ctx) {
|
|
|
2024
2136
|
});
|
|
2025
2137
|
}
|
|
2026
2138
|
if (settlement.status !== "settled") {
|
|
2027
|
-
await hold
|
|
2139
|
+
await finalizeHold(flow, hold, "release");
|
|
2028
2140
|
await safePersist(
|
|
2141
|
+
flow,
|
|
2029
2142
|
() => options.store.updatePaymentAttempt({
|
|
2030
2143
|
replayKey,
|
|
2031
2144
|
status: "rejected",
|
|
@@ -2033,17 +2146,19 @@ function createSettlement(ctx) {
|
|
|
2033
2146
|
...settlement.externalReference !== void 0 ? { externalReference: settlement.externalReference } : {}
|
|
2034
2147
|
}),
|
|
2035
2148
|
"updatePaymentAttempt(rejected)",
|
|
2036
|
-
|
|
2149
|
+
"payment-attempt"
|
|
2037
2150
|
);
|
|
2038
2151
|
await safeEmit(
|
|
2039
|
-
buildEvent({
|
|
2152
|
+
buildEvent(flow, {
|
|
2040
2153
|
type: "payment.rejected",
|
|
2041
|
-
requestId: request.requestId,
|
|
2042
2154
|
resourceId: resource.id,
|
|
2043
2155
|
adapter: request.protocol,
|
|
2044
2156
|
paymentProvider: provider.name,
|
|
2045
2157
|
status: "error",
|
|
2046
|
-
data:
|
|
2158
|
+
data: {
|
|
2159
|
+
...settlement.rejectionReason ? { reason: settlement.rejectionReason } : {},
|
|
2160
|
+
...failuresOf(flow)
|
|
2161
|
+
}
|
|
2047
2162
|
})
|
|
2048
2163
|
);
|
|
2049
2164
|
const rejectionDetails = retryChallenge(requirement, settlement.rejectionReason).details;
|
|
@@ -2061,20 +2176,20 @@ function createSettlement(ctx) {
|
|
|
2061
2176
|
}
|
|
2062
2177
|
);
|
|
2063
2178
|
}
|
|
2064
|
-
await hold
|
|
2179
|
+
await finalizeHold(flow, hold, "consume");
|
|
2065
2180
|
await safePersist(
|
|
2181
|
+
flow,
|
|
2066
2182
|
() => options.store.updatePaymentAttempt({
|
|
2067
2183
|
replayKey,
|
|
2068
2184
|
status: "settled",
|
|
2069
2185
|
...settlement.externalReference !== void 0 ? { externalReference: settlement.externalReference } : {}
|
|
2070
2186
|
}),
|
|
2071
2187
|
"updatePaymentAttempt(settled)",
|
|
2072
|
-
|
|
2188
|
+
"payment-attempt"
|
|
2073
2189
|
);
|
|
2074
2190
|
await safeEmit(
|
|
2075
|
-
buildEvent({
|
|
2191
|
+
buildEvent(flow, {
|
|
2076
2192
|
type: "payment.settled",
|
|
2077
|
-
requestId: request.requestId,
|
|
2078
2193
|
resourceId: resource.id,
|
|
2079
2194
|
adapter: request.protocol,
|
|
2080
2195
|
paymentProvider: provider.name,
|
|
@@ -2084,26 +2199,26 @@ function createSettlement(ctx) {
|
|
|
2084
2199
|
return settlement;
|
|
2085
2200
|
}
|
|
2086
2201
|
async function abandonSettlement(args, backendErrorCode) {
|
|
2087
|
-
const { request, resource, provider, replayKey, hold } = args;
|
|
2088
|
-
await hold
|
|
2202
|
+
const { flow, request, resource, provider, replayKey, hold } = args;
|
|
2203
|
+
await finalizeHold(flow, hold, "release");
|
|
2089
2204
|
await safePersist(
|
|
2205
|
+
flow,
|
|
2090
2206
|
() => options.store.updatePaymentAttempt({
|
|
2091
2207
|
replayKey,
|
|
2092
2208
|
status: "rejected",
|
|
2093
2209
|
rejectionReason: "backend_failed"
|
|
2094
2210
|
}),
|
|
2095
2211
|
"updatePaymentAttempt(backend-failed)",
|
|
2096
|
-
|
|
2212
|
+
"payment-attempt"
|
|
2097
2213
|
);
|
|
2098
2214
|
await safeEmit(
|
|
2099
|
-
buildEvent({
|
|
2215
|
+
buildEvent(flow, {
|
|
2100
2216
|
type: "payment.rejected",
|
|
2101
|
-
requestId: request.requestId,
|
|
2102
2217
|
resourceId: resource.id,
|
|
2103
2218
|
adapter: request.protocol,
|
|
2104
2219
|
paymentProvider: provider.name,
|
|
2105
2220
|
status: "error",
|
|
2106
|
-
data: { reason: "backend-failed", backendErrorCode }
|
|
2221
|
+
data: { reason: "backend-failed", backendErrorCode, ...failuresOf(flow) }
|
|
2107
2222
|
})
|
|
2108
2223
|
);
|
|
2109
2224
|
}
|
|
@@ -2140,7 +2255,7 @@ function createExecutionPipeline(options) {
|
|
|
2140
2255
|
return compiled;
|
|
2141
2256
|
}
|
|
2142
2257
|
const ctx = createPipelineContext(options);
|
|
2143
|
-
const { safeEmit, safePersist, buildEvent } = ctx;
|
|
2258
|
+
const { safeEmit, safePersist, recordStep, buildEvent } = ctx;
|
|
2144
2259
|
const { settle, abandonSettlement } = createSettlement(ctx);
|
|
2145
2260
|
const admitPayment = createPaymentAdmission(ctx, createAuthorizeAndReserve(ctx));
|
|
2146
2261
|
async function execute(request) {
|
|
@@ -2156,10 +2271,10 @@ function createExecutionPipeline(options) {
|
|
|
2156
2271
|
}
|
|
2157
2272
|
);
|
|
2158
2273
|
}
|
|
2274
|
+
const flow = createFlow(request, options.ids.next("txn"));
|
|
2159
2275
|
await safeEmit(
|
|
2160
|
-
buildEvent({
|
|
2276
|
+
buildEvent(flow, {
|
|
2161
2277
|
type: "resource.requested",
|
|
2162
|
-
requestId: request.requestId,
|
|
2163
2278
|
resourceId: resource.id,
|
|
2164
2279
|
adapter: request.protocol
|
|
2165
2280
|
})
|
|
@@ -2204,20 +2319,34 @@ function createExecutionPipeline(options) {
|
|
|
2204
2319
|
}
|
|
2205
2320
|
);
|
|
2206
2321
|
}
|
|
2207
|
-
let
|
|
2208
|
-
let authorization;
|
|
2209
|
-
let deferredSettlement;
|
|
2322
|
+
let settleArgs;
|
|
2210
2323
|
if (resource.pricing.type === "fixed") {
|
|
2211
|
-
const admission = await admitPayment(request, resource, resource.pricing, validInput);
|
|
2324
|
+
const admission = await admitPayment(flow, request, resource, resource.pricing, validInput);
|
|
2212
2325
|
if (admission.kind === "payment-required") return admission;
|
|
2213
|
-
|
|
2214
|
-
|
|
2326
|
+
settleArgs = admission.settleArgs;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
return await settleAndDeliver(flow, resource, validInput, settleArgs, pipelineStart);
|
|
2330
|
+
} finally {
|
|
2331
|
+
await recordStep(flow, { kind: "completed", ...failuresOf(flow) });
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
async function settleAndDeliver(flow, resource, validInput, settleArgs, pipelineStart) {
|
|
2335
|
+
const { request } = flow;
|
|
2336
|
+
const authorization = settleArgs?.hold?.record;
|
|
2337
|
+
let paymentResult;
|
|
2338
|
+
let deferredSettlement;
|
|
2339
|
+
if (settleArgs !== void 0) {
|
|
2215
2340
|
if (settleArgs.requirement.metadata?.[SETTLE_AFTER_BACKEND_METADATA_KEY] === true) {
|
|
2216
2341
|
deferredSettlement = settleArgs;
|
|
2217
2342
|
} else {
|
|
2218
2343
|
paymentResult = await settle(settleArgs);
|
|
2219
2344
|
}
|
|
2220
2345
|
}
|
|
2346
|
+
await recordStep(flow, {
|
|
2347
|
+
kind: "backend-dispatched",
|
|
2348
|
+
deadlineAt: backendDeadline(options.clock.now(), resource)
|
|
2349
|
+
});
|
|
2221
2350
|
const backendStart = options.clock.monotonicMs();
|
|
2222
2351
|
let backendResponse;
|
|
2223
2352
|
try {
|
|
@@ -2230,18 +2359,23 @@ function createExecutionPipeline(options) {
|
|
|
2230
2359
|
});
|
|
2231
2360
|
} catch (error) {
|
|
2232
2361
|
const commerceError = toCommerceError(error, "BACKEND_ERROR", "Backend call failed");
|
|
2233
|
-
await
|
|
2234
|
-
|
|
2362
|
+
await recordStep(flow, {
|
|
2363
|
+
kind: "backend-result",
|
|
2364
|
+
status: backendErrorStatus(commerceError),
|
|
2365
|
+
errorCode: commerceError.code
|
|
2366
|
+
});
|
|
2367
|
+
const backendFailed = (data = {}) => safeEmit(
|
|
2368
|
+
buildEvent(flow, {
|
|
2235
2369
|
type: "backend.failed",
|
|
2236
|
-
requestId: request.requestId,
|
|
2237
2370
|
resourceId: resource.id,
|
|
2238
2371
|
adapter: request.protocol,
|
|
2239
2372
|
status: "error",
|
|
2240
2373
|
durationMs: Math.round(options.clock.monotonicMs() - backendStart),
|
|
2241
|
-
data: { code: commerceError.code }
|
|
2374
|
+
data: { code: commerceError.code, ...data }
|
|
2242
2375
|
})
|
|
2243
2376
|
);
|
|
2244
2377
|
if (deferredSettlement !== void 0) {
|
|
2378
|
+
await backendFailed();
|
|
2245
2379
|
await abandonSettlement(deferredSettlement, commerceError.code);
|
|
2246
2380
|
throw commerceError;
|
|
2247
2381
|
}
|
|
@@ -2255,13 +2389,11 @@ function createExecutionPipeline(options) {
|
|
|
2255
2389
|
protocol: request.protocol,
|
|
2256
2390
|
payment: paymentResult,
|
|
2257
2391
|
...authorization !== void 0 ? { authorization } : {},
|
|
2258
|
-
metadata: { delivered: false, backendErrorCode: commerceError.code }
|
|
2392
|
+
metadata: { delivered: false, backendErrorCode: commerceError.code },
|
|
2393
|
+
transactionId: flow.transactionId
|
|
2259
2394
|
};
|
|
2260
|
-
await
|
|
2261
|
-
|
|
2262
|
-
"saveReceipt(backend-failed-after-settlement)",
|
|
2263
|
-
request.requestId
|
|
2264
|
-
);
|
|
2395
|
+
await saveReceipt(flow, failedReceipt, "saveReceipt(backend-failed-after-settlement)");
|
|
2396
|
+
await backendFailed(failuresOf(flow));
|
|
2265
2397
|
throw new CommerceError(
|
|
2266
2398
|
"BACKEND_ERROR",
|
|
2267
2399
|
"Payment settled but the backend call failed; the payment was not refunded automatically",
|
|
@@ -2285,12 +2417,13 @@ function createExecutionPipeline(options) {
|
|
|
2285
2417
|
}
|
|
2286
2418
|
);
|
|
2287
2419
|
}
|
|
2420
|
+
await backendFailed();
|
|
2288
2421
|
throw commerceError;
|
|
2289
2422
|
}
|
|
2423
|
+
await recordStep(flow, { kind: "backend-result", status: backendResponse.status });
|
|
2290
2424
|
await safeEmit(
|
|
2291
|
-
buildEvent({
|
|
2425
|
+
buildEvent(flow, {
|
|
2292
2426
|
type: "backend.called",
|
|
2293
|
-
requestId: request.requestId,
|
|
2294
2427
|
resourceId: resource.id,
|
|
2295
2428
|
adapter: request.protocol,
|
|
2296
2429
|
status: "ok",
|
|
@@ -2310,34 +2443,48 @@ function createExecutionPipeline(options) {
|
|
|
2310
2443
|
durationMs: backendResponse.durationMs,
|
|
2311
2444
|
protocol: request.protocol,
|
|
2312
2445
|
...paymentResult !== void 0 ? { payment: paymentResult } : {},
|
|
2313
|
-
...authorization !== void 0 ? { authorization } : {}
|
|
2446
|
+
...authorization !== void 0 ? { authorization } : {},
|
|
2447
|
+
transactionId: flow.transactionId
|
|
2314
2448
|
};
|
|
2315
|
-
await
|
|
2449
|
+
await saveReceipt(flow, receipt, "saveReceipt");
|
|
2316
2450
|
const totalDurationMs = Math.round(options.clock.monotonicMs() - pipelineStart);
|
|
2451
|
+
const failures = failuresOf(flow);
|
|
2317
2452
|
await safeEmit(
|
|
2318
|
-
buildEvent({
|
|
2453
|
+
buildEvent(flow, {
|
|
2319
2454
|
type: "resource.delivered",
|
|
2320
|
-
requestId: request.requestId,
|
|
2321
2455
|
resourceId: resource.id,
|
|
2322
2456
|
adapter: request.protocol,
|
|
2323
2457
|
status: "ok",
|
|
2324
|
-
durationMs: totalDurationMs
|
|
2458
|
+
durationMs: totalDurationMs,
|
|
2459
|
+
...failures.persistenceFailures !== void 0 ? { data: failures } : {}
|
|
2325
2460
|
})
|
|
2326
2461
|
);
|
|
2327
2462
|
return {
|
|
2328
2463
|
kind: "delivered",
|
|
2329
2464
|
requestId: request.requestId,
|
|
2465
|
+
transactionId: flow.transactionId,
|
|
2330
2466
|
resourceId: resource.id,
|
|
2331
2467
|
backendStatus: backendResponse.status,
|
|
2332
2468
|
body: backendResponse.body,
|
|
2333
2469
|
headers: backendResponse.headers,
|
|
2334
2470
|
...paymentResult !== void 0 ? { payment: paymentResult } : {},
|
|
2335
2471
|
receipt,
|
|
2336
|
-
durationMs: totalDurationMs
|
|
2472
|
+
durationMs: totalDurationMs,
|
|
2473
|
+
...failures
|
|
2337
2474
|
};
|
|
2338
2475
|
}
|
|
2476
|
+
async function saveReceipt(flow, receipt, label) {
|
|
2477
|
+
if (await safePersist(flow, () => options.store.saveReceipt(receipt), label, "receipt")) {
|
|
2478
|
+
await recordStep(flow, { kind: "receipt", receiptId: receipt.id });
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2339
2481
|
return { execute };
|
|
2340
2482
|
}
|
|
2483
|
+
var BACKEND_DEADLINE_MARGIN_MS = 5e3;
|
|
2484
|
+
function backendDeadline(dispatchedAt, resource) {
|
|
2485
|
+
const timeoutMs = resource.handler.timeoutMs ?? DEFAULT_BACKEND_TIMEOUT_MS;
|
|
2486
|
+
return new Date(dispatchedAt.getTime() + timeoutMs + BACKEND_DEADLINE_MARGIN_MS).toISOString();
|
|
2487
|
+
}
|
|
2341
2488
|
function backendErrorStatus(error) {
|
|
2342
2489
|
const status = error.details?.["status"];
|
|
2343
2490
|
return typeof status === "number" ? status : 0;
|
|
@@ -3468,6 +3615,168 @@ function wrapPino(instance) {
|
|
|
3468
3615
|
return logger;
|
|
3469
3616
|
}
|
|
3470
3617
|
|
|
3618
|
+
// src/core/execution/reconcile.ts
|
|
3619
|
+
var DEFAULT_RECONCILE_MIN_AGE_MS = 15 * 60 * 1e3;
|
|
3620
|
+
function journalOf(store) {
|
|
3621
|
+
const {
|
|
3622
|
+
listTransactions,
|
|
3623
|
+
getTransaction,
|
|
3624
|
+
getPaymentAttempt,
|
|
3625
|
+
resolvePaymentAttempt,
|
|
3626
|
+
recordTransactionStep
|
|
3627
|
+
} = store;
|
|
3628
|
+
if (!listTransactions || !getTransaction || !getPaymentAttempt || !resolvePaymentAttempt || !recordTransactionStep) {
|
|
3629
|
+
throw new CommerceError("STORAGE_ERROR", "The receipt store has no transaction journal");
|
|
3630
|
+
}
|
|
3631
|
+
return {
|
|
3632
|
+
listTransactions: listTransactions.bind(store),
|
|
3633
|
+
getTransaction: getTransaction.bind(store),
|
|
3634
|
+
getPaymentAttempt: getPaymentAttempt.bind(store),
|
|
3635
|
+
resolvePaymentAttempt: resolvePaymentAttempt.bind(store),
|
|
3636
|
+
recordTransactionStep: recordTransactionStep.bind(store)
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
var REFUSAL_MESSAGES = {
|
|
3640
|
+
already_resolved: "The payment attempt already has a resolution",
|
|
3641
|
+
not_resolvable: "Only reserved, settlement-uncertain and failed payment attempts can be resolved",
|
|
3642
|
+
too_recent: "The payment attempt is reserved and may still be in flight; retry once it is 15 minutes old",
|
|
3643
|
+
not_expired: "The recorded authorization expiry is still in the future",
|
|
3644
|
+
expiry_unknown: "No expiry is recorded for this authorization; confirm that it has expired"
|
|
3645
|
+
};
|
|
3646
|
+
var MAX_ACTOR_LENGTH = 256;
|
|
3647
|
+
var MAX_REASON_LENGTH = 1024;
|
|
3648
|
+
var MAX_REFERENCE_LENGTH = 256;
|
|
3649
|
+
var RESOLVABLE = [
|
|
3650
|
+
"reserved",
|
|
3651
|
+
"settlement-uncertain",
|
|
3652
|
+
"failed"
|
|
3653
|
+
];
|
|
3654
|
+
async function resolveManually(options) {
|
|
3655
|
+
const { clock, logger, attemptId, status, externalReference } = options;
|
|
3656
|
+
if (status !== "settled" && status !== "rejected") {
|
|
3657
|
+
throw invalid("status", 'status must be "settled" or "rejected"');
|
|
3658
|
+
}
|
|
3659
|
+
const actor = requiredText(options.actor, "actor", MAX_ACTOR_LENGTH);
|
|
3660
|
+
const reason = requiredText(options.reason, "reason", MAX_REASON_LENGTH);
|
|
3661
|
+
if (externalReference !== void 0) {
|
|
3662
|
+
requiredText(externalReference, "externalReference", MAX_REFERENCE_LENGTH);
|
|
3663
|
+
}
|
|
3664
|
+
const journal = journalOf(options.store);
|
|
3665
|
+
const attempt = await journal.getPaymentAttempt(attemptId);
|
|
3666
|
+
if (attempt === void 0) {
|
|
3667
|
+
throw new CommerceError("RESOURCE_NOT_FOUND", `Payment attempt "${attemptId}" was not found`);
|
|
3668
|
+
}
|
|
3669
|
+
const refuse = (refusal, current2 = attempt) => ({
|
|
3670
|
+
resolved: false,
|
|
3671
|
+
refusal,
|
|
3672
|
+
message: REFUSAL_MESSAGES[refusal],
|
|
3673
|
+
attempt: current2
|
|
3674
|
+
});
|
|
3675
|
+
if (attempt.resolution !== void 0) return refuse("already_resolved");
|
|
3676
|
+
if (!RESOLVABLE.includes(attempt.status)) return refuse("not_resolvable");
|
|
3677
|
+
if (attempt.status === "reserved" && !(clock.now().getTime() - Date.parse(attempt.createdAt) >= DEFAULT_RECONCILE_MIN_AGE_MS)) {
|
|
3678
|
+
return refuse("too_recent");
|
|
3679
|
+
}
|
|
3680
|
+
const transaction = attempt.transactionId !== void 0 ? await journal.getTransaction(attempt.transactionId) : void 0;
|
|
3681
|
+
if (status === "rejected") {
|
|
3682
|
+
const expiresAt = Date.parse(transaction?.payment.expiresAt ?? "");
|
|
3683
|
+
if (expiresAt > clock.now().getTime()) return refuse("not_expired");
|
|
3684
|
+
if (Number.isNaN(expiresAt) && options.confirmExpired !== true) {
|
|
3685
|
+
return refuse("expiry_unknown");
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
const written = await journal.resolvePaymentAttempt({
|
|
3689
|
+
attemptId,
|
|
3690
|
+
expectedStatus: attempt.status,
|
|
3691
|
+
status,
|
|
3692
|
+
...status === "rejected" ? { rejectionReason: "not_settled" } : {},
|
|
3693
|
+
resolution: {
|
|
3694
|
+
method: "manual",
|
|
3695
|
+
actor,
|
|
3696
|
+
reason,
|
|
3697
|
+
at: clock.nowIso(),
|
|
3698
|
+
...externalReference !== void 0 ? { externalReference } : {}
|
|
3699
|
+
}
|
|
3700
|
+
});
|
|
3701
|
+
const current = await journal.getPaymentAttempt(attemptId) ?? attempt;
|
|
3702
|
+
if (!written) {
|
|
3703
|
+
return refuse(
|
|
3704
|
+
current.resolution !== void 0 ? "already_resolved" : "not_resolvable",
|
|
3705
|
+
current
|
|
3706
|
+
);
|
|
3707
|
+
}
|
|
3708
|
+
const resolved = transaction !== void 0 ? await journal.getTransaction(transaction.id) : void 0;
|
|
3709
|
+
const target = resolved !== void 0 ? holdTarget(resolved) : void 0;
|
|
3710
|
+
const hold = resolved !== void 0 && target !== void 0 ? await alignHold(resolved, target, {
|
|
3711
|
+
journal,
|
|
3712
|
+
authorizationProviders: options.authorizationProviders,
|
|
3713
|
+
clock,
|
|
3714
|
+
logger
|
|
3715
|
+
}) : void 0;
|
|
3716
|
+
return { resolved: true, attempt: current, ...hold !== void 0 ? { hold } : {} };
|
|
3717
|
+
}
|
|
3718
|
+
function requiredText(value, field, max) {
|
|
3719
|
+
if (value.trim() === "" || value.length > max) {
|
|
3720
|
+
throw invalid(field, `${field} must be non-empty and at most ${max} characters`);
|
|
3721
|
+
}
|
|
3722
|
+
return value;
|
|
3723
|
+
}
|
|
3724
|
+
function invalid(field, message) {
|
|
3725
|
+
return new CommerceError("INPUT_INVALID", message, { details: { field } });
|
|
3726
|
+
}
|
|
3727
|
+
function holdTarget(transaction) {
|
|
3728
|
+
const hold = transaction.authorization?.hold;
|
|
3729
|
+
if (hold !== "reserved" && hold !== "uncertain") return void 0;
|
|
3730
|
+
if (transaction.payment.status === "settled") return "consumed";
|
|
3731
|
+
if (transaction.payment.status === "rejected") return "released";
|
|
3732
|
+
return void 0;
|
|
3733
|
+
}
|
|
3734
|
+
function holdEntry(transaction, hold, written, reason) {
|
|
3735
|
+
return {
|
|
3736
|
+
transactionId: transaction.id,
|
|
3737
|
+
attemptId: transaction.payment.attemptId,
|
|
3738
|
+
method: transaction.authorization?.method ?? "",
|
|
3739
|
+
hold,
|
|
3740
|
+
written,
|
|
3741
|
+
...reason !== void 0 ? { reason } : {}
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
async function alignHold(transaction, target, deps) {
|
|
3745
|
+
const authorization = transaction.authorization;
|
|
3746
|
+
const provider = deps.authorizationProviders.find(
|
|
3747
|
+
(candidate) => candidate.name === authorization?.method
|
|
3748
|
+
);
|
|
3749
|
+
if (authorization === void 0 || provider === void 0) {
|
|
3750
|
+
return holdEntry(transaction, target, false, "provider_not_enabled");
|
|
3751
|
+
}
|
|
3752
|
+
const context = { requestId: transaction.requestId, resourceId: transaction.resourceId };
|
|
3753
|
+
try {
|
|
3754
|
+
if (target === "consumed") await provider.consume(authorization.reservationId, context);
|
|
3755
|
+
else await provider.release(authorization.reservationId, context);
|
|
3756
|
+
} catch (err) {
|
|
3757
|
+
deps.logger?.warn(
|
|
3758
|
+
{ transactionId: transaction.id, method: provider.name, err: redactedErrorText(err) },
|
|
3759
|
+
"reconcile: authorization provider threw"
|
|
3760
|
+
);
|
|
3761
|
+
return holdEntry(transaction, target, false, "provider_error");
|
|
3762
|
+
}
|
|
3763
|
+
try {
|
|
3764
|
+
await deps.journal.recordTransactionStep({
|
|
3765
|
+
transactionId: transaction.id,
|
|
3766
|
+
at: deps.clock.nowIso(),
|
|
3767
|
+
kind: "hold",
|
|
3768
|
+
hold: target
|
|
3769
|
+
});
|
|
3770
|
+
} catch (err) {
|
|
3771
|
+
deps.logger?.warn(
|
|
3772
|
+
{ transactionId: transaction.id, err: redactedErrorText(err) },
|
|
3773
|
+
"reconcile: could not record the hold update"
|
|
3774
|
+
);
|
|
3775
|
+
return holdEntry(transaction, target, false, "journal_error");
|
|
3776
|
+
}
|
|
3777
|
+
return holdEntry(transaction, target, true);
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3471
3780
|
// src/gateway/public-resource.ts
|
|
3472
3781
|
function toPublicResource(resource) {
|
|
3473
3782
|
return {
|
|
@@ -3721,6 +4030,15 @@ function buildWellKnownDocument(options) {
|
|
|
3721
4030
|
|
|
3722
4031
|
// src/gateway/routes.ts
|
|
3723
4032
|
var X402_V1_PAYMENT_HEADER = "x-payment";
|
|
4033
|
+
var RESOLVE_BODY_LIMIT_BYTES = 4096;
|
|
4034
|
+
var RESOLVE_FIELDS = [
|
|
4035
|
+
"status",
|
|
4036
|
+
"actor",
|
|
4037
|
+
"reason",
|
|
4038
|
+
"externalReference",
|
|
4039
|
+
"confirmExpired"
|
|
4040
|
+
];
|
|
4041
|
+
var TRANSACTION_EVENT_LIMIT = 50;
|
|
3724
4042
|
function registerRoutes(options) {
|
|
3725
4043
|
const { server } = options;
|
|
3726
4044
|
const readinessProbe = createReadinessProbe({
|
|
@@ -3788,6 +4106,113 @@ function registerRoutes(options) {
|
|
|
3788
4106
|
)
|
|
3789
4107
|
)
|
|
3790
4108
|
);
|
|
4109
|
+
const { store, clock } = options;
|
|
4110
|
+
const journal = () => journalOf(store);
|
|
4111
|
+
const withAge = (transaction) => ({
|
|
4112
|
+
...transaction,
|
|
4113
|
+
ageSeconds: Math.max(
|
|
4114
|
+
0,
|
|
4115
|
+
Math.floor((clock.now().getTime() - Date.parse(transaction.createdAt)) / 1e3)
|
|
4116
|
+
)
|
|
4117
|
+
});
|
|
4118
|
+
server.get(
|
|
4119
|
+
"/api/transactions",
|
|
4120
|
+
{ onRequest: tokenHook },
|
|
4121
|
+
operatorHandler(async (request) => {
|
|
4122
|
+
const query = request.query;
|
|
4123
|
+
const limit = parseLimit(query["limit"]);
|
|
4124
|
+
const state = parseText(query["state"]);
|
|
4125
|
+
if (state !== void 0 && !TRANSACTION_STATES.includes(state)) {
|
|
4126
|
+
throw new CommerceError("INPUT_INVALID", `Unknown transaction state "${state}"`, {
|
|
4127
|
+
details: { field: "state" }
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
const requestId = parseText(query["requestId"]);
|
|
4131
|
+
const cursor = parseText(query["cursor"]);
|
|
4132
|
+
const page = await journal().listTransactions({
|
|
4133
|
+
...limit !== void 0 ? { limit } : {},
|
|
4134
|
+
...parseFlag(query["unresolved"], "unresolved") ? { unresolved: true } : {},
|
|
4135
|
+
...state !== void 0 ? { state } : {},
|
|
4136
|
+
...requestId !== void 0 ? { requestId } : {},
|
|
4137
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
4138
|
+
});
|
|
4139
|
+
return {
|
|
4140
|
+
transactions: page.transactions.map(withAge),
|
|
4141
|
+
...page.nextCursor !== void 0 ? { nextCursor: page.nextCursor } : {}
|
|
4142
|
+
};
|
|
4143
|
+
})
|
|
4144
|
+
);
|
|
4145
|
+
server.get(
|
|
4146
|
+
"/api/transactions/:id",
|
|
4147
|
+
{ onRequest: tokenHook },
|
|
4148
|
+
operatorHandler(async (request) => {
|
|
4149
|
+
const id = request.params.id;
|
|
4150
|
+
const transaction = await journal().getTransaction(id);
|
|
4151
|
+
if (transaction === void 0) {
|
|
4152
|
+
throw new CommerceError("RESOURCE_NOT_FOUND", `Transaction "${id}" was not found`);
|
|
4153
|
+
}
|
|
4154
|
+
const [attempt, receipt, events] = await Promise.all([
|
|
4155
|
+
journal().getPaymentAttempt(transaction.payment.attemptId),
|
|
4156
|
+
transaction.receiptId !== void 0 ? store.getReceipt(transaction.receiptId) : void 0,
|
|
4157
|
+
store.listEvents({ requestId: transaction.requestId, limit: TRANSACTION_EVENT_LIMIT })
|
|
4158
|
+
]);
|
|
4159
|
+
return {
|
|
4160
|
+
transaction: withAge(transaction),
|
|
4161
|
+
...attempt !== void 0 ? { attempt } : {},
|
|
4162
|
+
...receipt !== void 0 ? { receipt } : {},
|
|
4163
|
+
events
|
|
4164
|
+
};
|
|
4165
|
+
})
|
|
4166
|
+
);
|
|
4167
|
+
server.post(
|
|
4168
|
+
"/api/payment-attempts/:id/resolve",
|
|
4169
|
+
{ onRequest: tokenHook, bodyLimit: RESOLVE_BODY_LIMIT_BYTES },
|
|
4170
|
+
operatorHandler(async (request) => {
|
|
4171
|
+
const result = await resolveManually({
|
|
4172
|
+
store,
|
|
4173
|
+
authorizationProviders: options.authorizationProviders,
|
|
4174
|
+
clock,
|
|
4175
|
+
logger: options.logger,
|
|
4176
|
+
attemptId: request.params.id,
|
|
4177
|
+
...parseResolveBody(request.body)
|
|
4178
|
+
});
|
|
4179
|
+
if (!result.resolved) {
|
|
4180
|
+
throw new CommerceError("INPUT_INVALID", result.message, {
|
|
4181
|
+
httpStatus: 409,
|
|
4182
|
+
details: { reason: result.refusal, attemptStatus: result.attempt.status }
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
return {
|
|
4186
|
+
attempt: result.attempt,
|
|
4187
|
+
...result.hold !== void 0 ? { hold: result.hold } : {}
|
|
4188
|
+
};
|
|
4189
|
+
})
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
function parseResolveBody(body) {
|
|
4193
|
+
if (!isRecord(body)) {
|
|
4194
|
+
throw new CommerceError("INPUT_INVALID", "The request body must be a JSON object");
|
|
4195
|
+
}
|
|
4196
|
+
const field = (name, type, required) => {
|
|
4197
|
+
const value = body[name];
|
|
4198
|
+
if (value === void 0 && required || value !== void 0 && typeof value !== type) {
|
|
4199
|
+
throw new CommerceError("INPUT_INVALID", `Field "${name}" must be a ${type}`, {
|
|
4200
|
+
details: { field: name }
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
4203
|
+
};
|
|
4204
|
+
const unknownField = Object.keys(body).find((name) => !RESOLVE_FIELDS.includes(name));
|
|
4205
|
+
if (unknownField !== void 0) {
|
|
4206
|
+
throw new CommerceError("INPUT_INVALID", `Unknown field "${unknownField}"`, {
|
|
4207
|
+
details: { field: unknownField }
|
|
4208
|
+
});
|
|
4209
|
+
}
|
|
4210
|
+
field("status", "string", true);
|
|
4211
|
+
field("actor", "string", true);
|
|
4212
|
+
field("reason", "string", true);
|
|
4213
|
+
field("externalReference", "string", false);
|
|
4214
|
+
field("confirmExpired", "boolean", false);
|
|
4215
|
+
return body;
|
|
3791
4216
|
}
|
|
3792
4217
|
var bodyDigests = /* @__PURE__ */ new WeakMap();
|
|
3793
4218
|
function digestingStream(request) {
|
|
@@ -3803,13 +4228,10 @@ function digestingStream(request) {
|
|
|
3803
4228
|
}
|
|
3804
4229
|
});
|
|
3805
4230
|
}
|
|
3806
|
-
function
|
|
4231
|
+
function operatorHandler(handle) {
|
|
3807
4232
|
return async (request, reply) => {
|
|
3808
4233
|
try {
|
|
3809
|
-
|
|
3810
|
-
const limit = parseLimit(query["limit"]);
|
|
3811
|
-
const page = await list(limit !== void 0 ? { limit } : {}, query);
|
|
3812
|
-
return { [key]: page };
|
|
4234
|
+
return await handle(request, reply);
|
|
3813
4235
|
} catch (error) {
|
|
3814
4236
|
const commerceError = toCommerceError(error);
|
|
3815
4237
|
reply.status(commerceError.httpStatus).send(toErrorEnvelope(commerceError));
|
|
@@ -3817,6 +4239,14 @@ function ledgerHandler(key, list) {
|
|
|
3817
4239
|
}
|
|
3818
4240
|
};
|
|
3819
4241
|
}
|
|
4242
|
+
function ledgerHandler(key, list) {
|
|
4243
|
+
return operatorHandler(async (request) => {
|
|
4244
|
+
const query = request.query;
|
|
4245
|
+
const limit = parseLimit(query["limit"]);
|
|
4246
|
+
const page = await list(limit !== void 0 ? { limit } : {}, query);
|
|
4247
|
+
return { [key]: page };
|
|
4248
|
+
});
|
|
4249
|
+
}
|
|
3820
4250
|
async function handleInvoke(request, reply, options) {
|
|
3821
4251
|
const resourceId = request.params.id;
|
|
3822
4252
|
let paymentMethod;
|
|
@@ -3997,6 +4427,10 @@ function parseLimit(value) {
|
|
|
3997
4427
|
}
|
|
3998
4428
|
return n;
|
|
3999
4429
|
}
|
|
4430
|
+
function parseText(value) {
|
|
4431
|
+
if (Array.isArray(value)) return parseText(value[0]);
|
|
4432
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
4433
|
+
}
|
|
4000
4434
|
function parseFlag(value, field) {
|
|
4001
4435
|
if (Array.isArray(value)) return parseFlag(value[0], field);
|
|
4002
4436
|
if (value === void 0 || value === "false") return false;
|
|
@@ -4258,21 +4692,20 @@ var A2A_CALL_SHAPE = 'one data part {"resource": "<skill id>", "input": {...}}';
|
|
|
4258
4692
|
function userRoleProblem(role) {
|
|
4259
4693
|
return role === A2A_USER_ROLE ? void 0 : `Unsupported message role "${String(role)}": only ${A2A_USER_ROLE} is accepted.`;
|
|
4260
4694
|
}
|
|
4261
|
-
var UnknownRecord = z.record(z.string(), z.unknown());
|
|
4262
4695
|
var MessageSchema = z.object({
|
|
4263
4696
|
role: z.string(),
|
|
4264
4697
|
messageId: z.string().optional(),
|
|
4265
|
-
parts: z.array(
|
|
4698
|
+
parts: z.array(z.unknown()),
|
|
4266
4699
|
taskId: z.string().optional(),
|
|
4267
4700
|
contextId: z.string().optional(),
|
|
4268
|
-
referenceTaskIds: z.array(z.
|
|
4701
|
+
referenceTaskIds: z.array(z.unknown()).optional()
|
|
4269
4702
|
});
|
|
4270
4703
|
var ParamsSchema = z.object({
|
|
4271
4704
|
message: MessageSchema,
|
|
4272
4705
|
taskId: z.string().optional(),
|
|
4273
4706
|
contextId: z.string().optional()
|
|
4274
4707
|
});
|
|
4275
|
-
function
|
|
4708
|
+
function invalid2(message) {
|
|
4276
4709
|
return new CommerceError("INPUT_INVALID", message);
|
|
4277
4710
|
}
|
|
4278
4711
|
function unsupported(message, a2aErrorCode) {
|
|
@@ -4313,7 +4746,7 @@ function assertSupportedPart(part) {
|
|
|
4313
4746
|
);
|
|
4314
4747
|
}
|
|
4315
4748
|
if (!("data" in part)) {
|
|
4316
|
-
throw
|
|
4749
|
+
throw invalid2('Message part carries no "data": send a structured data part.');
|
|
4317
4750
|
}
|
|
4318
4751
|
const mediaType = part["mediaType"];
|
|
4319
4752
|
if (mediaType !== void 0 && mediaType !== A2A_JSON_MEDIA_TYPE) {
|
|
@@ -4326,21 +4759,21 @@ function assertSupportedPart(part) {
|
|
|
4326
4759
|
function parseInvocation(rawParams) {
|
|
4327
4760
|
const parsed = ParamsSchema.safeParse(rawParams);
|
|
4328
4761
|
if (!parsed.success) {
|
|
4329
|
-
throw
|
|
4762
|
+
throw invalid2(
|
|
4330
4763
|
'Request must carry a "message" with a "role" and a "parts" array of structured data parts.'
|
|
4331
4764
|
);
|
|
4332
4765
|
}
|
|
4333
4766
|
const params = parsed.data;
|
|
4334
4767
|
const message = params.message;
|
|
4335
4768
|
if (message.messageId === void 0 || message.messageId.length === 0) {
|
|
4336
|
-
throw
|
|
4769
|
+
throw invalid2('Message must carry a non-empty "messageId".');
|
|
4337
4770
|
}
|
|
4338
4771
|
const messageId = message.messageId;
|
|
4339
4772
|
assertNoContinuation(params);
|
|
4340
4773
|
const roleProblem = userRoleProblem(message.role);
|
|
4341
|
-
if (roleProblem !== void 0) throw
|
|
4774
|
+
if (roleProblem !== void 0) throw invalid2(roleProblem);
|
|
4342
4775
|
if (message.parts.length === 0) {
|
|
4343
|
-
throw
|
|
4776
|
+
throw invalid2("Message carries no parts: send exactly one structured data part.");
|
|
4344
4777
|
}
|
|
4345
4778
|
if (message.parts.length > 1) {
|
|
4346
4779
|
throw unsupported(
|
|
@@ -4348,22 +4781,23 @@ function parseInvocation(rawParams) {
|
|
|
4348
4781
|
);
|
|
4349
4782
|
}
|
|
4350
4783
|
const part = message.parts[0];
|
|
4351
|
-
if (part === void 0) throw
|
|
4784
|
+
if (part === void 0) throw invalid2("Message carries no parts.");
|
|
4785
|
+
if (!isRecord(part)) throw invalid2("Message part must be a JSON object.");
|
|
4352
4786
|
assertSupportedPart(part);
|
|
4353
4787
|
const data = part["data"];
|
|
4354
4788
|
if (!isRecord(data)) {
|
|
4355
|
-
throw
|
|
4789
|
+
throw invalid2('Message part "data" must be a JSON object.');
|
|
4356
4790
|
}
|
|
4357
4791
|
const resourceId = data["resource"];
|
|
4358
4792
|
if (typeof resourceId !== "string") {
|
|
4359
|
-
throw
|
|
4793
|
+
throw invalid2('Message part data must carry a "resource" string naming a canonical resource.');
|
|
4360
4794
|
}
|
|
4361
4795
|
if (resourceId.length === 0) {
|
|
4362
|
-
throw
|
|
4796
|
+
throw invalid2('Message part data "resource" must not be empty.');
|
|
4363
4797
|
}
|
|
4364
4798
|
const rawInput = data["input"];
|
|
4365
4799
|
if (rawInput !== void 0 && !isRecord(rawInput)) {
|
|
4366
|
-
throw
|
|
4800
|
+
throw invalid2('Message part data "input" must be a JSON object.');
|
|
4367
4801
|
}
|
|
4368
4802
|
return { resourceId, input: rawInput ?? {}, messageId };
|
|
4369
4803
|
}
|
|
@@ -5273,10 +5707,10 @@ function writeAcpFailure(res, failure, headers = {}) {
|
|
|
5273
5707
|
writeAcpJson(res, failure.status, failure.error, headers);
|
|
5274
5708
|
}
|
|
5275
5709
|
function operationKey(scope) {
|
|
5276
|
-
return
|
|
5710
|
+
return sha2562(`acp-operation:${scope.deployment}:${scope.endpoint}:${scope.key}`);
|
|
5277
5711
|
}
|
|
5278
5712
|
function requestFingerprint(body) {
|
|
5279
|
-
return
|
|
5713
|
+
return sha2562(canonicalize(body));
|
|
5280
5714
|
}
|
|
5281
5715
|
function canonicalize(value) {
|
|
5282
5716
|
if (value === null) return "null";
|
|
@@ -5287,7 +5721,7 @@ function canonicalize(value) {
|
|
|
5287
5721
|
}
|
|
5288
5722
|
return JSON.stringify(value) ?? "null";
|
|
5289
5723
|
}
|
|
5290
|
-
function
|
|
5724
|
+
function sha2562(value) {
|
|
5291
5725
|
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
5292
5726
|
}
|
|
5293
5727
|
|
|
@@ -10002,12 +10436,12 @@ var AcpProtocolAdapter = class {
|
|
|
10002
10436
|
mountPath: this.mountPath,
|
|
10003
10437
|
...this.discoveryMetadata !== void 0 ? { metadata: this.discoveryMetadata } : {}
|
|
10004
10438
|
});
|
|
10005
|
-
const
|
|
10006
|
-
if (
|
|
10439
|
+
const invalid3 = validateAcpDocument("discoveryResponse", document);
|
|
10440
|
+
if (invalid3 !== void 0) {
|
|
10007
10441
|
throw new CommerceError(
|
|
10008
10442
|
"CONFIG_INVALID",
|
|
10009
|
-
`ACP discovery document does not conform to the ${this.descriptor.supportedSpec} snapshot at "${
|
|
10010
|
-
{ details: { path:
|
|
10443
|
+
`ACP discovery document does not conform to the ${this.descriptor.supportedSpec} snapshot at "${invalid3.path ?? "$"}" (${invalid3.code}) - check protocols.acp.discovery`,
|
|
10444
|
+
{ details: { path: invalid3.path ?? "$", code: invalid3.code } }
|
|
10011
10445
|
);
|
|
10012
10446
|
}
|
|
10013
10447
|
const exposed = new Map(
|
|
@@ -10314,7 +10748,8 @@ function receiptToRow(receipt) {
|
|
|
10314
10748
|
duration_ms: receipt.durationMs ?? null,
|
|
10315
10749
|
protocol: receipt.protocol ?? null,
|
|
10316
10750
|
metadata_json: receipt.metadata !== void 0 ? JSON.stringify(redact(receipt.metadata)) : null,
|
|
10317
|
-
authorization_json: receipt.authorization !== void 0 ? JSON.stringify(redact(receipt.authorization)) : null
|
|
10751
|
+
authorization_json: receipt.authorization !== void 0 ? JSON.stringify(redact(receipt.authorization)) : null,
|
|
10752
|
+
transaction_id: receipt.transactionId ?? null
|
|
10318
10753
|
};
|
|
10319
10754
|
}
|
|
10320
10755
|
function rowToReceipt(row) {
|
|
@@ -10331,7 +10766,8 @@ function rowToReceipt(row) {
|
|
|
10331
10766
|
...row.duration_ms !== null ? { durationMs: row.duration_ms } : {},
|
|
10332
10767
|
...row.protocol !== null ? { protocol: row.protocol } : {},
|
|
10333
10768
|
...metadata !== void 0 ? { metadata } : {},
|
|
10334
|
-
...authorization !== void 0 ? { authorization } : {}
|
|
10769
|
+
...authorization !== void 0 ? { authorization } : {},
|
|
10770
|
+
...row.transaction_id !== null ? { transactionId: row.transaction_id } : {}
|
|
10335
10771
|
};
|
|
10336
10772
|
}
|
|
10337
10773
|
function eventToRow(event) {
|
|
@@ -10345,7 +10781,8 @@ function eventToRow(event) {
|
|
|
10345
10781
|
payment_provider: event.paymentProvider ?? null,
|
|
10346
10782
|
duration_ms: event.durationMs ?? null,
|
|
10347
10783
|
status: event.status ?? null,
|
|
10348
|
-
data_json: event.data !== void 0 ? JSON.stringify(redact(event.data)) : null
|
|
10784
|
+
data_json: event.data !== void 0 ? JSON.stringify(redact(event.data)) : null,
|
|
10785
|
+
transaction_id: event.transactionId ?? null
|
|
10349
10786
|
};
|
|
10350
10787
|
}
|
|
10351
10788
|
function rowToEvent(row) {
|
|
@@ -10360,7 +10797,8 @@ function rowToEvent(row) {
|
|
|
10360
10797
|
...row.payment_provider !== null ? { paymentProvider: row.payment_provider } : {},
|
|
10361
10798
|
...row.duration_ms !== null ? { durationMs: row.duration_ms } : {},
|
|
10362
10799
|
...row.status !== null ? { status: row.status } : {},
|
|
10363
|
-
...data !== void 0 ? { data } : {}
|
|
10800
|
+
...data !== void 0 ? { data } : {},
|
|
10801
|
+
...row.transaction_id !== null ? { transactionId: row.transaction_id } : {}
|
|
10364
10802
|
};
|
|
10365
10803
|
}
|
|
10366
10804
|
function rowToPaymentAttempt(row) {
|
|
@@ -10378,7 +10816,61 @@ function rowToPaymentAttempt(row) {
|
|
|
10378
10816
|
...row.external_reference !== null ? { externalReference: row.external_reference } : {},
|
|
10379
10817
|
...row.rejection_reason !== null ? { rejectionReason: row.rejection_reason } : {},
|
|
10380
10818
|
createdAt: row.created_at,
|
|
10381
|
-
updatedAt: row.updated_at
|
|
10819
|
+
updatedAt: row.updated_at,
|
|
10820
|
+
...row.transaction_id !== null ? { transactionId: row.transaction_id } : {},
|
|
10821
|
+
...row.resolution_json !== null ? { resolution: JSON.parse(row.resolution_json) } : {}
|
|
10822
|
+
};
|
|
10823
|
+
}
|
|
10824
|
+
function rowToTransaction(row, now) {
|
|
10825
|
+
const status = row.payment_status;
|
|
10826
|
+
const backend = row.backend_dispatched_at !== null && row.backend_deadline_at !== null ? {
|
|
10827
|
+
dispatchedAt: row.backend_dispatched_at,
|
|
10828
|
+
deadlineAt: row.backend_deadline_at,
|
|
10829
|
+
...row.backend_status !== null ? { status: row.backend_status } : {},
|
|
10830
|
+
...row.backend_error_code !== null ? { errorCode: row.backend_error_code } : {}
|
|
10831
|
+
} : void 0;
|
|
10832
|
+
const completedAt = row.completed_at ?? void 0;
|
|
10833
|
+
const authorization = row.authorization_json !== null && row.reservation_id !== null ? {
|
|
10834
|
+
...JSON.parse(row.authorization_json),
|
|
10835
|
+
reservationId: row.reservation_id
|
|
10836
|
+
} : void 0;
|
|
10837
|
+
return {
|
|
10838
|
+
id: row.id,
|
|
10839
|
+
requestId: row.request_id,
|
|
10840
|
+
resourceId: row.resource_id,
|
|
10841
|
+
protocol: row.protocol,
|
|
10842
|
+
state: transactionState(
|
|
10843
|
+
{
|
|
10844
|
+
payment: { status },
|
|
10845
|
+
...backend !== void 0 ? { backend } : {},
|
|
10846
|
+
...completedAt !== void 0 ? { completedAt } : {}
|
|
10847
|
+
},
|
|
10848
|
+
now
|
|
10849
|
+
),
|
|
10850
|
+
payment: {
|
|
10851
|
+
provider: row.provider,
|
|
10852
|
+
attemptId: row.attempt_id,
|
|
10853
|
+
replayKey: row.replay_key,
|
|
10854
|
+
status,
|
|
10855
|
+
amount: row.payment_amount,
|
|
10856
|
+
currency: row.payment_currency,
|
|
10857
|
+
...row.network !== null ? { network: row.network } : {},
|
|
10858
|
+
...row.asset !== null ? { asset: row.asset } : {},
|
|
10859
|
+
...row.payment_payee !== null ? { payee: row.payment_payee } : {},
|
|
10860
|
+
...row.payment_external_reference !== null ? { externalReference: row.payment_external_reference } : {},
|
|
10861
|
+
...row.locator_json !== null ? { locator: JSON.parse(row.locator_json) } : {},
|
|
10862
|
+
...row.settling_at !== null ? { settlingAt: row.settling_at } : {},
|
|
10863
|
+
...row.expires_at !== null ? { expiresAt: row.expires_at } : {}
|
|
10864
|
+
},
|
|
10865
|
+
...authorization !== void 0 ? { authorization } : {},
|
|
10866
|
+
...backend !== void 0 ? { backend } : {},
|
|
10867
|
+
...row.receipt_id !== null ? { receiptId: row.receipt_id } : {},
|
|
10868
|
+
...row.input_digest !== null ? { inputDigest: row.input_digest } : {},
|
|
10869
|
+
configDigest: row.config_digest,
|
|
10870
|
+
...row.persistence_failures_json !== null ? { persistenceFailures: JSON.parse(row.persistence_failures_json) } : {},
|
|
10871
|
+
createdAt: row.created_at,
|
|
10872
|
+
updatedAt: row.updated_at,
|
|
10873
|
+
...completedAt !== void 0 ? { completedAt } : {}
|
|
10382
10874
|
};
|
|
10383
10875
|
}
|
|
10384
10876
|
|
|
@@ -10473,13 +10965,60 @@ var MIGRATIONS = [
|
|
|
10473
10965
|
CREATE INDEX IF NOT EXISTS idx_payment_attempt_history_request_id ON payment_attempt_history(request_id);
|
|
10474
10966
|
`);
|
|
10475
10967
|
}
|
|
10968
|
+
},
|
|
10969
|
+
{
|
|
10970
|
+
version: 4,
|
|
10971
|
+
up(db) {
|
|
10972
|
+
db.exec(`
|
|
10973
|
+
CREATE TABLE IF NOT EXISTS transactions (
|
|
10974
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10975
|
+
id TEXT NOT NULL UNIQUE,
|
|
10976
|
+
request_id TEXT NOT NULL,
|
|
10977
|
+
resource_id TEXT NOT NULL,
|
|
10978
|
+
protocol TEXT NOT NULL,
|
|
10979
|
+
state TEXT NOT NULL,
|
|
10980
|
+
provider TEXT NOT NULL,
|
|
10981
|
+
attempt_id TEXT NOT NULL,
|
|
10982
|
+
replay_key TEXT NOT NULL,
|
|
10983
|
+
network TEXT,
|
|
10984
|
+
asset TEXT,
|
|
10985
|
+
authorization_json TEXT,
|
|
10986
|
+
reservation_id TEXT,
|
|
10987
|
+
locator_json TEXT,
|
|
10988
|
+
expires_at TEXT,
|
|
10989
|
+
input_digest TEXT,
|
|
10990
|
+
config_digest TEXT NOT NULL,
|
|
10991
|
+
settling_at TEXT,
|
|
10992
|
+
backend_dispatched_at TEXT,
|
|
10993
|
+
backend_deadline_at TEXT,
|
|
10994
|
+
backend_status INTEGER,
|
|
10995
|
+
backend_error_code TEXT,
|
|
10996
|
+
receipt_id TEXT,
|
|
10997
|
+
persistence_failures_json TEXT,
|
|
10998
|
+
completed_at TEXT,
|
|
10999
|
+
created_at TEXT NOT NULL,
|
|
11000
|
+
updated_at TEXT NOT NULL
|
|
11001
|
+
);
|
|
11002
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_state_seq ON transactions(state, seq);
|
|
11003
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_request_id ON transactions(request_id);
|
|
11004
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_replay_key ON transactions(replay_key);
|
|
11005
|
+
|
|
11006
|
+
ALTER TABLE receipts ADD COLUMN transaction_id TEXT;
|
|
11007
|
+
ALTER TABLE events ADD COLUMN transaction_id TEXT;
|
|
11008
|
+
ALTER TABLE payment_attempts ADD COLUMN transaction_id TEXT;
|
|
11009
|
+
ALTER TABLE payment_attempts ADD COLUMN resolution_json TEXT;
|
|
11010
|
+
ALTER TABLE payment_attempt_history ADD COLUMN transaction_id TEXT;
|
|
11011
|
+
ALTER TABLE payment_attempt_history ADD COLUMN resolution_json TEXT;
|
|
11012
|
+
CREATE INDEX IF NOT EXISTS idx_payment_attempt_history_id ON payment_attempt_history(id);
|
|
11013
|
+
`);
|
|
11014
|
+
}
|
|
10476
11015
|
}
|
|
10477
11016
|
];
|
|
10478
11017
|
var SCHEMA_VERSION2 = MIGRATIONS.at(-1)?.version ?? 0;
|
|
10479
|
-
function migrate2(db) {
|
|
11018
|
+
function migrate2(db, target = SCHEMA_VERSION2) {
|
|
10480
11019
|
const current = db.pragma("user_version", { simple: true });
|
|
10481
11020
|
for (const migration of MIGRATIONS) {
|
|
10482
|
-
if (migration.version <= current) continue;
|
|
11021
|
+
if (migration.version <= current || migration.version > target) continue;
|
|
10483
11022
|
const apply = db.transaction(() => {
|
|
10484
11023
|
migration.up(db);
|
|
10485
11024
|
db.pragma(`user_version = ${migration.version}`);
|
|
@@ -10497,6 +11036,22 @@ function clampListLimit(limit) {
|
|
|
10497
11036
|
}
|
|
10498
11037
|
var UNDELIVERED = "backend_status NOT BETWEEN 200 AND 299";
|
|
10499
11038
|
var UNCERTAIN = "status IN ('settlement-uncertain', 'failed')";
|
|
11039
|
+
var TRANSACTION_SELECT = `
|
|
11040
|
+
SELECT t.*,
|
|
11041
|
+
COALESCE(a.status, h.status) AS payment_status,
|
|
11042
|
+
COALESCE(a.amount, h.amount) AS payment_amount,
|
|
11043
|
+
COALESCE(a.currency, h.currency) AS payment_currency,
|
|
11044
|
+
COALESCE(a.payee, h.payee) AS payment_payee,
|
|
11045
|
+
COALESCE(a.external_reference, h.external_reference) AS payment_external_reference
|
|
11046
|
+
FROM transactions t
|
|
11047
|
+
LEFT JOIN payment_attempts a ON a.id = t.attempt_id
|
|
11048
|
+
LEFT JOIN payment_attempt_history h ON a.id IS NULL AND h.seq = (
|
|
11049
|
+
SELECT MAX(seq) FROM payment_attempt_history
|
|
11050
|
+
WHERE replay_key = t.replay_key AND id = t.attempt_id
|
|
11051
|
+
)`;
|
|
11052
|
+
var TIME_DEPENDENT = "t.backend_deadline_at IS NOT NULL AND t.completed_at IS NULL";
|
|
11053
|
+
var RESOLVED_STATES = ["delivered", "payment-failed"];
|
|
11054
|
+
var CURSOR_PATTERN = /^[1-9][0-9]{0,15}$/;
|
|
10500
11055
|
var defaultIds = {
|
|
10501
11056
|
next: (prefix) => prefix !== void 0 ? `${prefix}_${randomUUID()}` : randomUUID()
|
|
10502
11057
|
};
|
|
@@ -10512,8 +11067,8 @@ function createSqliteReceiptStore(options) {
|
|
|
10512
11067
|
const db = openSqliteDatabase({ path: path2, label: "Receipt database", logger });
|
|
10513
11068
|
migrate2(db);
|
|
10514
11069
|
const insertReceiptStmt = db.prepare(
|
|
10515
|
-
`INSERT INTO receipts (id, request_id, resource_id, payment_json, delivered_at, backend_status, duration_ms, protocol, metadata_json, authorization_json)
|
|
10516
|
-
VALUES (@id, @request_id, @resource_id, @payment_json, @delivered_at, @backend_status, @duration_ms, @protocol, @metadata_json, @authorization_json)`
|
|
11070
|
+
`INSERT INTO receipts (id, request_id, resource_id, payment_json, delivered_at, backend_status, duration_ms, protocol, metadata_json, authorization_json, transaction_id)
|
|
11071
|
+
VALUES (@id, @request_id, @resource_id, @payment_json, @delivered_at, @backend_status, @duration_ms, @protocol, @metadata_json, @authorization_json, @transaction_id)`
|
|
10517
11072
|
);
|
|
10518
11073
|
const getReceiptStmt = db.prepare("SELECT * FROM receipts WHERE id = ?");
|
|
10519
11074
|
const listReceiptsStmt = db.prepare(
|
|
@@ -10535,8 +11090,8 @@ function createSqliteReceiptStore(options) {
|
|
|
10535
11090
|
`SELECT COUNT(*) AS count FROM receipts WHERE ${UNDELIVERED}`
|
|
10536
11091
|
);
|
|
10537
11092
|
const insertEventStmt = db.prepare(
|
|
10538
|
-
`INSERT INTO events (id, type, request_id, resource_id, at, adapter, payment_provider, duration_ms, status, data_json)
|
|
10539
|
-
VALUES (@id, @type, @request_id, @resource_id, @at, @adapter, @payment_provider, @duration_ms, @status, @data_json)`
|
|
11093
|
+
`INSERT INTO events (id, type, request_id, resource_id, at, adapter, payment_provider, duration_ms, status, data_json, transaction_id)
|
|
11094
|
+
VALUES (@id, @type, @request_id, @resource_id, @at, @adapter, @payment_provider, @duration_ms, @status, @data_json, @transaction_id)`
|
|
10540
11095
|
);
|
|
10541
11096
|
const listEventsStmt = db.prepare(
|
|
10542
11097
|
"SELECT * FROM events ORDER BY at DESC, seq DESC LIMIT ?"
|
|
@@ -10545,13 +11100,13 @@ function createSqliteReceiptStore(options) {
|
|
|
10545
11100
|
"SELECT * FROM events WHERE request_id = ? ORDER BY at DESC, seq DESC LIMIT ?"
|
|
10546
11101
|
);
|
|
10547
11102
|
const insertPaymentAttemptStmt = db.prepare(
|
|
10548
|
-
`INSERT INTO payment_attempts (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at)
|
|
10549
|
-
VALUES (@id, @request_id, @resource_id, @provider, @replay_key, @status, @amount, @currency, @payer, @payee, @external_reference, @rejection_reason, @created_at, @updated_at)
|
|
11103
|
+
`INSERT INTO payment_attempts (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, transaction_id)
|
|
11104
|
+
VALUES (@id, @request_id, @resource_id, @provider, @replay_key, @status, @amount, @currency, @payer, @payee, @external_reference, @rejection_reason, @created_at, @updated_at, @transaction_id)
|
|
10550
11105
|
RETURNING *`
|
|
10551
11106
|
);
|
|
10552
11107
|
const archiveRejectedAttemptStmt = db.prepare(
|
|
10553
|
-
`INSERT INTO payment_attempt_history (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, superseded_at)
|
|
10554
|
-
SELECT id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, @created_at
|
|
11108
|
+
`INSERT INTO payment_attempt_history (id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, superseded_at, transaction_id, resolution_json)
|
|
11109
|
+
SELECT id, request_id, resource_id, provider, replay_key, status, amount, currency, payer, payee, external_reference, rejection_reason, created_at, updated_at, @created_at, transaction_id, resolution_json
|
|
10555
11110
|
FROM payment_attempts
|
|
10556
11111
|
WHERE replay_key = @replay_key AND provider = @provider AND status = 'rejected'`
|
|
10557
11112
|
);
|
|
@@ -10559,7 +11114,8 @@ function createSqliteReceiptStore(options) {
|
|
|
10559
11114
|
`UPDATE payment_attempts SET id = @id, request_id = @request_id, resource_id = @resource_id,
|
|
10560
11115
|
status = 'reserved', amount = @amount, currency = @currency,
|
|
10561
11116
|
payer = @payer, payee = @payee, external_reference = NULL, rejection_reason = NULL,
|
|
10562
|
-
created_at = @created_at, updated_at = @updated_at
|
|
11117
|
+
created_at = @created_at, updated_at = @updated_at,
|
|
11118
|
+
transaction_id = @transaction_id, resolution_json = NULL
|
|
10563
11119
|
WHERE replay_key = @replay_key AND provider = @provider AND status = 'rejected'
|
|
10564
11120
|
RETURNING *`
|
|
10565
11121
|
);
|
|
@@ -10568,7 +11124,7 @@ function createSqliteReceiptStore(options) {
|
|
|
10568
11124
|
external_reference = COALESCE(@external_reference, external_reference),
|
|
10569
11125
|
rejection_reason = COALESCE(@rejection_reason, rejection_reason),
|
|
10570
11126
|
updated_at = @updated_at
|
|
10571
|
-
WHERE replay_key = @replay_key`
|
|
11127
|
+
WHERE replay_key = @replay_key AND resolution_json IS NULL`
|
|
10572
11128
|
);
|
|
10573
11129
|
const listPaymentAttemptsStmt = db.prepare(
|
|
10574
11130
|
"SELECT * FROM payment_attempts ORDER BY created_at DESC, seq DESC LIMIT ?"
|
|
@@ -10585,13 +11141,176 @@ function createSqliteReceiptStore(options) {
|
|
|
10585
11141
|
const countUncertainPaymentAttemptsStmt = db.prepare(
|
|
10586
11142
|
`SELECT COUNT(*) AS count FROM payment_attempts WHERE ${UNCERTAIN}`
|
|
10587
11143
|
);
|
|
10588
|
-
const paymentAttemptStatusStmt = db.prepare(
|
|
10589
|
-
|
|
11144
|
+
const paymentAttemptStatusStmt = db.prepare("SELECT id, status, resolution_json FROM payment_attempts WHERE replay_key = ?");
|
|
11145
|
+
const insertTransactionStmt = db.prepare(
|
|
11146
|
+
`INSERT INTO transactions (id, request_id, resource_id, protocol, state, provider, attempt_id, replay_key, network, asset, authorization_json, reservation_id, locator_json, expires_at, input_digest, config_digest, created_at, updated_at)
|
|
11147
|
+
VALUES (@id, @request_id, @resource_id, @protocol, @state, @provider, @attempt_id, @replay_key, @network, @asset, @authorization_json, @reservation_id, @locator_json, @expires_at, @input_digest, @config_digest, @created_at, @created_at)`
|
|
10590
11148
|
);
|
|
10591
|
-
const
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
11149
|
+
const getTransactionStmt = db.prepare(
|
|
11150
|
+
`${TRANSACTION_SELECT} WHERE t.id = ?`
|
|
11151
|
+
);
|
|
11152
|
+
const transactionsOfAttemptStmt = db.prepare(
|
|
11153
|
+
`SELECT t.id FROM payment_attempts a
|
|
11154
|
+
JOIN transactions t ON t.replay_key = a.replay_key AND t.attempt_id = a.id
|
|
11155
|
+
WHERE a.replay_key = ?`
|
|
11156
|
+
);
|
|
11157
|
+
const transactionsOfAttemptIdStmt = db.prepare(
|
|
11158
|
+
`SELECT t.id FROM payment_attempts a
|
|
11159
|
+
JOIN transactions t ON t.replay_key = a.replay_key AND t.attempt_id = a.id
|
|
11160
|
+
WHERE a.id = ?`
|
|
11161
|
+
);
|
|
11162
|
+
const setTransactionStateStmt = db.prepare("UPDATE transactions SET state = ? WHERE id = ?");
|
|
11163
|
+
const stepStmts = {
|
|
11164
|
+
settling: db.prepare(
|
|
11165
|
+
"UPDATE transactions SET settling_at = @at, updated_at = @at WHERE id = @id"
|
|
11166
|
+
),
|
|
11167
|
+
// A transaction without an authorization has no hold to record
|
|
11168
|
+
hold: db.prepare(
|
|
11169
|
+
`UPDATE transactions SET authorization_json = json_set(authorization_json, '$.hold', @hold), updated_at = @at
|
|
11170
|
+
WHERE id = @id AND authorization_json IS NOT NULL`
|
|
11171
|
+
),
|
|
11172
|
+
"backend-dispatched": db.prepare(
|
|
11173
|
+
`UPDATE transactions SET backend_dispatched_at = @at, backend_deadline_at = @deadline_at, updated_at = @at
|
|
11174
|
+
WHERE id = @id`
|
|
11175
|
+
),
|
|
11176
|
+
// Preserve a backend result even when the dispatch step was not saved:
|
|
11177
|
+
// use the result time for missing dispatch and deadline timestamps
|
|
11178
|
+
"backend-result": db.prepare(
|
|
11179
|
+
`UPDATE transactions SET backend_status = @status, backend_error_code = @error_code,
|
|
11180
|
+
backend_dispatched_at = COALESCE(backend_dispatched_at, @at),
|
|
11181
|
+
backend_deadline_at = COALESCE(backend_deadline_at, @at), updated_at = @at
|
|
11182
|
+
WHERE id = @id`
|
|
11183
|
+
),
|
|
11184
|
+
receipt: db.prepare(
|
|
11185
|
+
"UPDATE transactions SET receipt_id = @receipt_id, updated_at = @at WHERE id = @id"
|
|
11186
|
+
),
|
|
11187
|
+
completed: db.prepare(
|
|
11188
|
+
`UPDATE transactions SET completed_at = @at, persistence_failures_json = @persistence_failures_json, updated_at = @at
|
|
11189
|
+
WHERE id = @id`
|
|
11190
|
+
)
|
|
11191
|
+
};
|
|
11192
|
+
const getPaymentAttemptStmt = db.prepare(
|
|
11193
|
+
"SELECT * FROM payment_attempts WHERE id = ?"
|
|
11194
|
+
);
|
|
11195
|
+
const getArchivedPaymentAttemptStmt = db.prepare(
|
|
11196
|
+
"SELECT * FROM payment_attempt_history WHERE id = ? ORDER BY seq DESC LIMIT 1"
|
|
11197
|
+
);
|
|
11198
|
+
const resolvePaymentAttemptStmt = db.prepare(
|
|
11199
|
+
`UPDATE payment_attempts SET status = @status,
|
|
11200
|
+
rejection_reason = COALESCE(@rejection_reason, rejection_reason),
|
|
11201
|
+
external_reference = COALESCE(@external_reference, external_reference),
|
|
11202
|
+
resolution_json = @resolution_json, updated_at = @updated_at
|
|
11203
|
+
WHERE id = @id AND status = @expected_status AND resolution_json IS NULL`
|
|
11204
|
+
);
|
|
11205
|
+
function refreshTransactionState(id, now) {
|
|
11206
|
+
const row = getTransactionStmt.get(id);
|
|
11207
|
+
if (row !== void 0) setTransactionStateStmt.run(rowToTransaction(row, now).state, id);
|
|
11208
|
+
}
|
|
11209
|
+
const reserve = db.transaction(
|
|
11210
|
+
(reservation, params) => {
|
|
11211
|
+
let row;
|
|
11212
|
+
if (reservation.reclaimRejected === true) {
|
|
11213
|
+
archiveRejectedAttemptStmt.run(params);
|
|
11214
|
+
row = reclaimPaymentAttemptStmt.get(params);
|
|
11215
|
+
}
|
|
11216
|
+
row ??= insertPaymentAttemptStmt.get({
|
|
11217
|
+
...params,
|
|
11218
|
+
status: "reserved",
|
|
11219
|
+
external_reference: null,
|
|
11220
|
+
rejection_reason: null
|
|
11221
|
+
});
|
|
11222
|
+
const txn = reservation.transaction;
|
|
11223
|
+
if (txn !== void 0) {
|
|
11224
|
+
const { reservationId, ...authorization } = txn.authorization ?? {};
|
|
11225
|
+
insertTransactionStmt.run({
|
|
11226
|
+
id: txn.id,
|
|
11227
|
+
request_id: reservation.requestId,
|
|
11228
|
+
resource_id: reservation.resourceId,
|
|
11229
|
+
protocol: txn.protocol,
|
|
11230
|
+
state: transactionState({ payment: { status: "reserved" } }, row.created_at),
|
|
11231
|
+
provider: reservation.provider,
|
|
11232
|
+
attempt_id: row.id,
|
|
11233
|
+
replay_key: reservation.replayKey,
|
|
11234
|
+
network: txn.network ?? null,
|
|
11235
|
+
asset: txn.asset ?? null,
|
|
11236
|
+
authorization_json: reservationId !== void 0 ? JSON.stringify(redact({ ...authorization, hold: "reserved" })) : null,
|
|
11237
|
+
reservation_id: reservationId ?? null,
|
|
11238
|
+
locator_json: txn.locator !== void 0 ? JSON.stringify(redact(txn.locator)) : null,
|
|
11239
|
+
expires_at: txn.expiresAt ?? null,
|
|
11240
|
+
input_digest: txn.inputDigest ?? null,
|
|
11241
|
+
config_digest: txn.configDigest,
|
|
11242
|
+
created_at: row.created_at
|
|
11243
|
+
});
|
|
11244
|
+
}
|
|
11245
|
+
return row;
|
|
11246
|
+
}
|
|
11247
|
+
);
|
|
11248
|
+
const updateAttempt = db.transaction((update, now) => {
|
|
11249
|
+
const { changes } = updatePaymentAttemptStmt.run({
|
|
11250
|
+
replay_key: update.replayKey,
|
|
11251
|
+
status: update.status,
|
|
11252
|
+
external_reference: update.externalReference ?? null,
|
|
11253
|
+
rejection_reason: update.rejectionReason ?? null,
|
|
11254
|
+
updated_at: now
|
|
11255
|
+
});
|
|
11256
|
+
for (const { id } of transactionsOfAttemptStmt.all(update.replayKey)) {
|
|
11257
|
+
refreshTransactionState(id, now);
|
|
11258
|
+
}
|
|
11259
|
+
return changes;
|
|
11260
|
+
});
|
|
11261
|
+
const recordStep = db.transaction((step) => {
|
|
11262
|
+
const base = { id: step.transactionId, at: step.at };
|
|
11263
|
+
let changes;
|
|
11264
|
+
switch (step.kind) {
|
|
11265
|
+
case "settling":
|
|
11266
|
+
changes = stepStmts.settling.run(base).changes;
|
|
11267
|
+
break;
|
|
11268
|
+
case "hold":
|
|
11269
|
+
changes = stepStmts.hold.run({ ...base, hold: step.hold }).changes;
|
|
11270
|
+
break;
|
|
11271
|
+
case "backend-dispatched":
|
|
11272
|
+
changes = stepStmts["backend-dispatched"].run({
|
|
11273
|
+
...base,
|
|
11274
|
+
deadline_at: step.deadlineAt
|
|
11275
|
+
}).changes;
|
|
11276
|
+
break;
|
|
11277
|
+
case "backend-result":
|
|
11278
|
+
changes = stepStmts["backend-result"].run({
|
|
11279
|
+
...base,
|
|
11280
|
+
status: step.status,
|
|
11281
|
+
error_code: step.errorCode ?? null
|
|
11282
|
+
}).changes;
|
|
11283
|
+
break;
|
|
11284
|
+
case "receipt":
|
|
11285
|
+
changes = stepStmts.receipt.run({ ...base, receipt_id: step.receiptId }).changes;
|
|
11286
|
+
break;
|
|
11287
|
+
case "completed":
|
|
11288
|
+
changes = stepStmts.completed.run({
|
|
11289
|
+
...base,
|
|
11290
|
+
persistence_failures_json: step.persistenceFailures !== void 0 && step.persistenceFailures.length > 0 ? JSON.stringify(step.persistenceFailures) : null
|
|
11291
|
+
}).changes;
|
|
11292
|
+
break;
|
|
11293
|
+
}
|
|
11294
|
+
if (changes > 0) refreshTransactionState(step.transactionId, step.at);
|
|
11295
|
+
return changes;
|
|
11296
|
+
});
|
|
11297
|
+
const resolveAttempt = db.transaction(
|
|
11298
|
+
(request, now) => {
|
|
11299
|
+
const { changes } = resolvePaymentAttemptStmt.run({
|
|
11300
|
+
id: request.attemptId,
|
|
11301
|
+
expected_status: request.expectedStatus,
|
|
11302
|
+
status: request.status,
|
|
11303
|
+
rejection_reason: request.rejectionReason ?? null,
|
|
11304
|
+
// An operator's reference is unverified; it stays in resolution_json only
|
|
11305
|
+
external_reference: request.resolution.method === "reconcile" ? request.resolution.externalReference ?? null : null,
|
|
11306
|
+
resolution_json: JSON.stringify(redact(request.resolution)),
|
|
11307
|
+
updated_at: now
|
|
11308
|
+
});
|
|
11309
|
+
if (changes === 0) return false;
|
|
11310
|
+
for (const { id } of transactionsOfAttemptIdStmt.all(request.attemptId)) {
|
|
11311
|
+
refreshTransactionState(id, now);
|
|
11312
|
+
}
|
|
11313
|
+
return true;
|
|
10595
11314
|
}
|
|
10596
11315
|
);
|
|
10597
11316
|
let closed = false;
|
|
@@ -10600,7 +11319,7 @@ function createSqliteReceiptStore(options) {
|
|
|
10600
11319
|
kind: "storage",
|
|
10601
11320
|
implementationVersion: PACKAGE_VERSION,
|
|
10602
11321
|
supportedSpec: `sqlite-schema-v${SCHEMA_VERSION2}`,
|
|
10603
|
-
capabilities: ["receipts", "events", "payment-attempts"],
|
|
11322
|
+
capabilities: ["receipts", "events", "payment-attempts", "transactions"],
|
|
10604
11323
|
status: "stable"
|
|
10605
11324
|
};
|
|
10606
11325
|
const store = {
|
|
@@ -10640,17 +11359,12 @@ function createSqliteReceiptStore(options) {
|
|
|
10640
11359
|
payer: reservation.payer ?? null,
|
|
10641
11360
|
payee: reservation.payee ?? null,
|
|
10642
11361
|
created_at: now,
|
|
10643
|
-
updated_at: now
|
|
11362
|
+
updated_at: now,
|
|
11363
|
+
transaction_id: reservation.transaction?.id ?? null
|
|
10644
11364
|
};
|
|
10645
11365
|
let row;
|
|
10646
11366
|
try {
|
|
10647
|
-
row = (reservation
|
|
10648
|
-
insertPaymentAttemptStmt.get({
|
|
10649
|
-
...params,
|
|
10650
|
-
status: "reserved",
|
|
10651
|
-
external_reference: null,
|
|
10652
|
-
rejection_reason: null
|
|
10653
|
-
});
|
|
11367
|
+
row = reserve.immediate(reservation, params);
|
|
10654
11368
|
} catch (err) {
|
|
10655
11369
|
if (isUniqueConstraintOn(err, "payment_attempts.replay_key")) {
|
|
10656
11370
|
const existing = paymentAttemptStatusStmt.get(reservation.replayKey);
|
|
@@ -10670,14 +11384,23 @@ function createSqliteReceiptStore(options) {
|
|
|
10670
11384
|
},
|
|
10671
11385
|
async updatePaymentAttempt(update) {
|
|
10672
11386
|
try {
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
11387
|
+
if (updateAttempt(update, clock.nowIso()) === 0) {
|
|
11388
|
+
const existing = paymentAttemptStatusStmt.get(update.replayKey);
|
|
11389
|
+
if (existing?.resolution_json != null) {
|
|
11390
|
+
logger.warn(
|
|
11391
|
+
{
|
|
11392
|
+
attemptId: existing.id,
|
|
11393
|
+
resolvedStatus: existing.status,
|
|
11394
|
+
status: update.status,
|
|
11395
|
+
...update.externalReference !== void 0 ? { externalReference: update.externalReference } : {}
|
|
11396
|
+
},
|
|
11397
|
+
"receipt-store: payment attempt already resolved; update not applied"
|
|
11398
|
+
);
|
|
11399
|
+
throw new CommerceError(
|
|
11400
|
+
"STORAGE_ERROR",
|
|
11401
|
+
`Payment attempt ${existing.id} is already resolved; the update was not applied`
|
|
11402
|
+
);
|
|
11403
|
+
}
|
|
10681
11404
|
throw new CommerceError(
|
|
10682
11405
|
"STORAGE_ERROR",
|
|
10683
11406
|
`No payment attempt found for replayKey=${update.replayKey}`
|
|
@@ -10729,6 +11452,74 @@ function createSqliteReceiptStore(options) {
|
|
|
10729
11452
|
const rows = listOptions.requestId !== void 0 ? (uncertain ? listUncertainPaymentAttemptsByRequestStmt : listPaymentAttemptsByRequestStmt).all(listOptions.requestId, limit) : (uncertain ? listUncertainPaymentAttemptsStmt : listPaymentAttemptsStmt).all(limit);
|
|
10730
11453
|
return rows.map(rowToPaymentAttempt);
|
|
10731
11454
|
},
|
|
11455
|
+
async recordTransactionStep(step) {
|
|
11456
|
+
try {
|
|
11457
|
+
if (recordStep(step) === 0) {
|
|
11458
|
+
throw new CommerceError(
|
|
11459
|
+
"STORAGE_ERROR",
|
|
11460
|
+
step.kind === "hold" ? `No transaction with an authorization hold found for transactionId=${step.transactionId}` : `No transaction found for transactionId=${step.transactionId}`
|
|
11461
|
+
);
|
|
11462
|
+
}
|
|
11463
|
+
} catch (err) {
|
|
11464
|
+
throw toCommerceError(err, "STORAGE_ERROR", "Failed to record transaction step");
|
|
11465
|
+
}
|
|
11466
|
+
},
|
|
11467
|
+
async getTransaction(id) {
|
|
11468
|
+
const row = getTransactionStmt.get(id);
|
|
11469
|
+
return row !== void 0 ? rowToTransaction(row, clock.nowIso()) : void 0;
|
|
11470
|
+
},
|
|
11471
|
+
async listTransactions(listOptions = {}) {
|
|
11472
|
+
const { cursor, state, unresolved, pendingHold, requestId } = listOptions;
|
|
11473
|
+
if (cursor !== void 0 && !CURSOR_PATTERN.test(cursor)) {
|
|
11474
|
+
throw new CommerceError("INPUT_INVALID", "Malformed transaction list cursor");
|
|
11475
|
+
}
|
|
11476
|
+
const limit = clampListLimit(listOptions.limit);
|
|
11477
|
+
const where = [];
|
|
11478
|
+
const params = [];
|
|
11479
|
+
if (cursor !== void 0) {
|
|
11480
|
+
where.push("t.seq < ?");
|
|
11481
|
+
params.push(Number(cursor));
|
|
11482
|
+
}
|
|
11483
|
+
if (requestId !== void 0) {
|
|
11484
|
+
where.push("t.request_id = ?");
|
|
11485
|
+
params.push(requestId);
|
|
11486
|
+
}
|
|
11487
|
+
if (state !== void 0) {
|
|
11488
|
+
where.push(`(t.state = ? OR (${TIME_DEPENDENT}))`);
|
|
11489
|
+
params.push(state);
|
|
11490
|
+
}
|
|
11491
|
+
if (pendingHold === true) {
|
|
11492
|
+
where.push("json_extract(t.authorization_json, '$.hold') IN ('reserved', 'uncertain')");
|
|
11493
|
+
}
|
|
11494
|
+
if (unresolved === true) {
|
|
11495
|
+
where.push(`t.state NOT IN (${RESOLVED_STATES.map(() => "?").join(", ")})`);
|
|
11496
|
+
params.push(...RESOLVED_STATES);
|
|
11497
|
+
}
|
|
11498
|
+
const rows = db.prepare(
|
|
11499
|
+
`${TRANSACTION_SELECT}${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY t.seq DESC LIMIT ?`
|
|
11500
|
+
).all(...params, limit);
|
|
11501
|
+
const now = clock.nowIso();
|
|
11502
|
+
const resolved = RESOLVED_STATES;
|
|
11503
|
+
const transactions = rows.map((row) => rowToTransaction(row, now)).filter(
|
|
11504
|
+
(t) => (state === void 0 || t.state === state) && (unresolved !== true || !resolved.includes(t.state))
|
|
11505
|
+
);
|
|
11506
|
+
const last = rows.at(-1);
|
|
11507
|
+
return {
|
|
11508
|
+
transactions,
|
|
11509
|
+
...rows.length === limit && last !== void 0 ? { nextCursor: String(last.seq) } : {}
|
|
11510
|
+
};
|
|
11511
|
+
},
|
|
11512
|
+
async getPaymentAttempt(id) {
|
|
11513
|
+
const row = getPaymentAttemptStmt.get(id) ?? getArchivedPaymentAttemptStmt.get(id);
|
|
11514
|
+
return row !== void 0 ? rowToPaymentAttempt(row) : void 0;
|
|
11515
|
+
},
|
|
11516
|
+
async resolvePaymentAttempt(request) {
|
|
11517
|
+
try {
|
|
11518
|
+
return resolveAttempt(request, clock.nowIso());
|
|
11519
|
+
} catch (err) {
|
|
11520
|
+
throw toCommerceError(err, "STORAGE_ERROR", "Failed to resolve payment attempt");
|
|
11521
|
+
}
|
|
11522
|
+
},
|
|
10732
11523
|
descriptor,
|
|
10733
11524
|
async health() {
|
|
10734
11525
|
const startedAt = clock.monotonicMs();
|