xoonya 1.6.0 → 1.8.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 +22 -0
- package/package.json +1 -1
- package/proof_sdk.js +268 -0
- package/server.js +42 -0
package/README.md
CHANGED
|
@@ -28,6 +28,18 @@ The current `1.6.0` companion slice adds guided default proof loops on top of th
|
|
|
28
28
|
|
|
29
29
|
This keeps `xoonya` narrow while making the intended proof path easier to inspect alongside the newer guided transaction-loop work in `xytara`.
|
|
30
30
|
|
|
31
|
+
The current `1.7.0` companion slice adds a lightweight transaction proof-handoff surface:
|
|
32
|
+
|
|
33
|
+
- a transaction proof-handoff center that states the expected handoff contract
|
|
34
|
+
- a direct handoff run path that turns transaction-side proof handoff data into a proof-native bundle
|
|
35
|
+
- summary helpers that make the recommended proof follow-through explicit without widening proof scope
|
|
36
|
+
|
|
37
|
+
The current `1.8.0` companion slice extends that same proof-side carry to richer grouped interaction spine handoffs:
|
|
38
|
+
|
|
39
|
+
- an interaction-spine handoff center and summary surface
|
|
40
|
+
- a direct interaction-spine handoff run path for composed transaction spines
|
|
41
|
+
- proof-side summaries that preserve both pre-commit and committed-ref context without importing commerce logic
|
|
42
|
+
|
|
31
43
|
## Proof Model
|
|
32
44
|
|
|
33
45
|
The primary proof model is layered:
|
|
@@ -80,6 +92,7 @@ The package quickstart demonstrates:
|
|
|
80
92
|
- canonical receipt emission
|
|
81
93
|
- proof bundle construction
|
|
82
94
|
- proof bundle verification
|
|
95
|
+
- transaction proof-handoff follow-through
|
|
83
96
|
|
|
84
97
|
## Service Quickstart
|
|
85
98
|
|
|
@@ -106,6 +119,15 @@ Environment example:
|
|
|
106
119
|
Primary proof-model routes:
|
|
107
120
|
|
|
108
121
|
- `GET /health`
|
|
122
|
+
- `GET /v1/proof-center`
|
|
123
|
+
- `GET /v1/proof-center/summary`
|
|
124
|
+
- `GET /v1/proof-center/default-loops`
|
|
125
|
+
- `GET /v1/proof-center/default-loops/summary`
|
|
126
|
+
- `GET /v1/proof-center/default-loop`
|
|
127
|
+
- `POST /v1/proof-center/default-loops/run`
|
|
128
|
+
- `GET /v1/proof-center/transaction-handoff`
|
|
129
|
+
- `GET /v1/proof-center/transaction-handoff/summary`
|
|
130
|
+
- `POST /v1/proof-center/transaction-handoff/run`
|
|
109
131
|
- `POST /v1/events/canonicalize`
|
|
110
132
|
- `POST /v1/events/hash`
|
|
111
133
|
- `POST /v1/receipts/emit`
|
package/package.json
CHANGED
package/proof_sdk.js
CHANGED
|
@@ -877,6 +877,262 @@ function summarizeGuidedProofLoopBundle(bundle) {
|
|
|
877
877
|
};
|
|
878
878
|
}
|
|
879
879
|
|
|
880
|
+
function buildTransactionProofHandoffCenter() {
|
|
881
|
+
return {
|
|
882
|
+
ok: true,
|
|
883
|
+
brand: "xoonya",
|
|
884
|
+
product: "transaction proof handoff center",
|
|
885
|
+
supported_source_refs: [
|
|
886
|
+
"transaction.registry.anchor",
|
|
887
|
+
"transaction.runtime.consequence",
|
|
888
|
+
"transaction.trust.receipt",
|
|
889
|
+
"transaction.admission.settlement",
|
|
890
|
+
"transaction.job.commitment"
|
|
891
|
+
],
|
|
892
|
+
recommended_loop: "default.proof.bundle.loop",
|
|
893
|
+
required_fields: ["transaction_id", "receipt_id", "proof_ref"],
|
|
894
|
+
optional_fields: ["source_ref", "task_refs", "delivery_count", "payment_ledger_count"]
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function buildInteractionSpineProofHandoffCenter() {
|
|
899
|
+
return {
|
|
900
|
+
ok: true,
|
|
901
|
+
brand: "xoonya",
|
|
902
|
+
product: "interaction spine proof handoff center",
|
|
903
|
+
supported_source_refs: [
|
|
904
|
+
"spine.a2a.settlement",
|
|
905
|
+
"spine.a2c.tooling",
|
|
906
|
+
"spine.job.commitment"
|
|
907
|
+
],
|
|
908
|
+
required_fields: ["transaction_id", "receipt_id", "proof_ref", "source_ref"],
|
|
909
|
+
optional_fields: ["task_refs", "delivery_count", "payment_ledger_count", "committed_ref", "precommit_pack_ref"],
|
|
910
|
+
recommended_loop: "default.proof.bundle.loop"
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function summarizeTransactionProofHandoffCenter() {
|
|
915
|
+
const center = buildTransactionProofHandoffCenter();
|
|
916
|
+
return {
|
|
917
|
+
ok: true,
|
|
918
|
+
brand: center.brand,
|
|
919
|
+
product: center.product,
|
|
920
|
+
source_ref_count: Array.isArray(center.supported_source_refs) ? center.supported_source_refs.length : 0,
|
|
921
|
+
supported_source_refs: center.supported_source_refs,
|
|
922
|
+
required_field_count: Array.isArray(center.required_fields) ? center.required_fields.length : 0,
|
|
923
|
+
required_fields: center.required_fields,
|
|
924
|
+
recommended_loop: center.recommended_loop
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function summarizeInteractionSpineProofHandoffCenter() {
|
|
929
|
+
const center = buildInteractionSpineProofHandoffCenter();
|
|
930
|
+
return {
|
|
931
|
+
ok: true,
|
|
932
|
+
brand: center.brand,
|
|
933
|
+
product: center.product,
|
|
934
|
+
source_ref_count: Array.isArray(center.supported_source_refs) ? center.supported_source_refs.length : 0,
|
|
935
|
+
supported_source_refs: center.supported_source_refs,
|
|
936
|
+
required_field_count: Array.isArray(center.required_fields) ? center.required_fields.length : 0,
|
|
937
|
+
required_fields: center.required_fields,
|
|
938
|
+
recommended_loop: center.recommended_loop
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function canonicalizeTransactionProofHandoff(input) {
|
|
943
|
+
const body = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
944
|
+
const handoff = body.handoff_bundle && typeof body.handoff_bundle === "object" && !Array.isArray(body.handoff_bundle)
|
|
945
|
+
? body.handoff_bundle
|
|
946
|
+
: body;
|
|
947
|
+
const transactionId = normalizeString(handoff.transaction_id);
|
|
948
|
+
const receiptId = normalizeString(handoff.receipt_id);
|
|
949
|
+
const proofRef = normalizeString(handoff.proof_ref);
|
|
950
|
+
if (!transactionId) throw new Error("handoff_bundle.transaction_id required");
|
|
951
|
+
if (!receiptId) throw new Error("handoff_bundle.receipt_id required");
|
|
952
|
+
if (!proofRef) throw new Error("handoff_bundle.proof_ref required");
|
|
953
|
+
const sourceRef = normalizeString(handoff.source_ref || body.source_ref || "transaction.proof_handoff");
|
|
954
|
+
const taskRefs = normalizeStringArray(handoff.task_refs, "handoff_bundle.task_refs") || [];
|
|
955
|
+
const deliveryCount = Number.isFinite(Number(handoff.delivery_count)) ? Number(handoff.delivery_count) : 0;
|
|
956
|
+
const paymentLedgerCount = Number.isFinite(Number(handoff.payment_ledger_count)) ? Number(handoff.payment_ledger_count) : 0;
|
|
957
|
+
return {
|
|
958
|
+
source_ref: sourceRef,
|
|
959
|
+
transaction_id: transactionId,
|
|
960
|
+
receipt_id: receiptId,
|
|
961
|
+
proof_ref: proofRef,
|
|
962
|
+
task_refs: taskRefs,
|
|
963
|
+
delivery_count: deliveryCount,
|
|
964
|
+
payment_ledger_count: paymentLedgerCount
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function canonicalizeInteractionSpineProofHandoff(input) {
|
|
969
|
+
const handoff = canonicalizeTransactionProofHandoff(input);
|
|
970
|
+
const body = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
971
|
+
const sourceRef = normalizeString(handoff.source_ref);
|
|
972
|
+
const center = buildInteractionSpineProofHandoffCenter();
|
|
973
|
+
if (!center.supported_source_refs.includes(sourceRef)) {
|
|
974
|
+
throw new Error("handoff_bundle.source_ref must be a supported interaction spine ref");
|
|
975
|
+
}
|
|
976
|
+
const raw = body.handoff_bundle && typeof body.handoff_bundle === "object" && !Array.isArray(body.handoff_bundle)
|
|
977
|
+
? body.handoff_bundle
|
|
978
|
+
: body;
|
|
979
|
+
const committedRef = normalizeString(raw.committed_ref);
|
|
980
|
+
const precommitPackRef = normalizeString(raw.precommit_pack_ref);
|
|
981
|
+
return {
|
|
982
|
+
...handoff,
|
|
983
|
+
committed_ref: committedRef || null,
|
|
984
|
+
precommit_pack_ref: precommitPackRef || null
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function buildTransactionProofHandoffEvent(input) {
|
|
989
|
+
const handoff = canonicalizeTransactionProofHandoff(input);
|
|
990
|
+
const eventHash = canonicalJsonSha256Hex({
|
|
991
|
+
source_ref: handoff.source_ref,
|
|
992
|
+
transaction_id: handoff.transaction_id,
|
|
993
|
+
receipt_id: handoff.receipt_id,
|
|
994
|
+
proof_ref: handoff.proof_ref,
|
|
995
|
+
task_refs: handoff.task_refs,
|
|
996
|
+
delivery_count: handoff.delivery_count,
|
|
997
|
+
payment_ledger_count: handoff.payment_ledger_count
|
|
998
|
+
});
|
|
999
|
+
return canonicalizeEvent({
|
|
1000
|
+
version: CANONICAL_EVENT_BUNDLE_VERSION,
|
|
1001
|
+
domain_id: "xytara.transaction_handoff",
|
|
1002
|
+
sequence: handoff.transaction_id,
|
|
1003
|
+
actor_id: "xytara.transaction_center",
|
|
1004
|
+
adapter_source: "xytara.proof_handoff",
|
|
1005
|
+
event_type: `${handoff.source_ref}.proof_handoff`,
|
|
1006
|
+
event_hash: eventHash,
|
|
1007
|
+
semantic_slots: handoff
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function buildInteractionSpineProofHandoffEvent(input) {
|
|
1012
|
+
const handoff = canonicalizeInteractionSpineProofHandoff(input);
|
|
1013
|
+
const eventHash = canonicalJsonSha256Hex({
|
|
1014
|
+
source_ref: handoff.source_ref,
|
|
1015
|
+
transaction_id: handoff.transaction_id,
|
|
1016
|
+
receipt_id: handoff.receipt_id,
|
|
1017
|
+
proof_ref: handoff.proof_ref,
|
|
1018
|
+
task_refs: handoff.task_refs,
|
|
1019
|
+
delivery_count: handoff.delivery_count,
|
|
1020
|
+
payment_ledger_count: handoff.payment_ledger_count,
|
|
1021
|
+
committed_ref: handoff.committed_ref,
|
|
1022
|
+
precommit_pack_ref: handoff.precommit_pack_ref
|
|
1023
|
+
});
|
|
1024
|
+
return canonicalizeEvent({
|
|
1025
|
+
version: CANONICAL_EVENT_BUNDLE_VERSION,
|
|
1026
|
+
domain_id: "xytara.interaction_spine_handoff",
|
|
1027
|
+
sequence: handoff.transaction_id,
|
|
1028
|
+
actor_id: "xytara.interaction_spine",
|
|
1029
|
+
adapter_source: "xytara.spine_handoff",
|
|
1030
|
+
event_type: `${handoff.source_ref}.proof_handoff`,
|
|
1031
|
+
event_hash: eventHash,
|
|
1032
|
+
semantic_slots: handoff
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function runTransactionProofHandoff(input) {
|
|
1037
|
+
const handoff = canonicalizeTransactionProofHandoff(input);
|
|
1038
|
+
const event = buildTransactionProofHandoffEvent(input);
|
|
1039
|
+
const options = input && input.options && typeof input.options === "object" && !Array.isArray(input.options)
|
|
1040
|
+
? input.options
|
|
1041
|
+
: {};
|
|
1042
|
+
const receipt = emitReceipt(event, options);
|
|
1043
|
+
const proofBundle = buildProofBundle({
|
|
1044
|
+
ceb: event,
|
|
1045
|
+
receipt,
|
|
1046
|
+
evidence_refs: Array.isArray(input && input.evidence_refs) ? input.evidence_refs : [handoff.proof_ref],
|
|
1047
|
+
metadata: {
|
|
1048
|
+
handoff_kind: "transaction_proof_handoff",
|
|
1049
|
+
source_ref: handoff.source_ref
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
return {
|
|
1053
|
+
ok: true,
|
|
1054
|
+
handoff,
|
|
1055
|
+
canonical_event: event,
|
|
1056
|
+
event_commitment: hashEvent(event),
|
|
1057
|
+
receipt,
|
|
1058
|
+
proof_bundle: proofBundle,
|
|
1059
|
+
bundle_verification: verifyProofBundle(proofBundle),
|
|
1060
|
+
recommended_loop: "default.proof.bundle.loop"
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function runInteractionSpineProofHandoff(input) {
|
|
1065
|
+
const handoff = canonicalizeInteractionSpineProofHandoff(input);
|
|
1066
|
+
const event = buildInteractionSpineProofHandoffEvent(input);
|
|
1067
|
+
const options = input && input.options && typeof input.options === "object" && !Array.isArray(input.options)
|
|
1068
|
+
? input.options
|
|
1069
|
+
: {};
|
|
1070
|
+
const receipt = emitReceipt(event, options);
|
|
1071
|
+
const proofBundle = buildProofBundle({
|
|
1072
|
+
ceb: event,
|
|
1073
|
+
receipt,
|
|
1074
|
+
evidence_refs: Array.isArray(input && input.evidence_refs) ? input.evidence_refs : [handoff.proof_ref],
|
|
1075
|
+
metadata: {
|
|
1076
|
+
handoff_kind: "interaction_spine_proof_handoff",
|
|
1077
|
+
source_ref: handoff.source_ref,
|
|
1078
|
+
committed_ref: handoff.committed_ref || null,
|
|
1079
|
+
precommit_pack_ref: handoff.precommit_pack_ref || null
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
return {
|
|
1083
|
+
ok: true,
|
|
1084
|
+
handoff,
|
|
1085
|
+
canonical_event: event,
|
|
1086
|
+
event_commitment: hashEvent(event),
|
|
1087
|
+
receipt,
|
|
1088
|
+
proof_bundle: proofBundle,
|
|
1089
|
+
bundle_verification: verifyProofBundle(proofBundle),
|
|
1090
|
+
recommended_loop: "default.proof.bundle.loop"
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
function summarizeTransactionProofHandoff(bundle) {
|
|
1095
|
+
const item = bundle && typeof bundle === "object" ? bundle : {};
|
|
1096
|
+
const handoff = item.handoff && typeof item.handoff === "object" ? item.handoff : {};
|
|
1097
|
+
const receipt = item.receipt && typeof item.receipt === "object" ? item.receipt : {};
|
|
1098
|
+
const proofBundle = item.proof_bundle && typeof item.proof_bundle === "object" ? item.proof_bundle : {};
|
|
1099
|
+
const verification = item.bundle_verification && typeof item.bundle_verification === "object" ? item.bundle_verification : {};
|
|
1100
|
+
return {
|
|
1101
|
+
ok: item.ok === true,
|
|
1102
|
+
source_ref: handoff.source_ref || null,
|
|
1103
|
+
transaction_id: handoff.transaction_id || null,
|
|
1104
|
+
receipt_id: handoff.receipt_id || null,
|
|
1105
|
+
proof_ref: handoff.proof_ref || null,
|
|
1106
|
+
task_ref_count: Array.isArray(handoff.task_refs) ? handoff.task_refs.length : 0,
|
|
1107
|
+
proof_bundle_version: proofBundle.version || null,
|
|
1108
|
+
emitted_receipt_id: receipt.receipt_id || null,
|
|
1109
|
+
bundle_valid: verification.valid === true,
|
|
1110
|
+
recommended_loop: item.recommended_loop || null
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function summarizeInteractionSpineProofHandoff(bundle) {
|
|
1115
|
+
const item = bundle && typeof bundle === "object" ? bundle : {};
|
|
1116
|
+
const handoff = item.handoff && typeof item.handoff === "object" ? item.handoff : {};
|
|
1117
|
+
const receipt = item.receipt && typeof item.receipt === "object" ? item.receipt : {};
|
|
1118
|
+
const proofBundle = item.proof_bundle && typeof item.proof_bundle === "object" ? item.proof_bundle : {};
|
|
1119
|
+
const verification = item.bundle_verification && typeof item.bundle_verification === "object" ? item.bundle_verification : {};
|
|
1120
|
+
return {
|
|
1121
|
+
ok: item.ok === true,
|
|
1122
|
+
source_ref: handoff.source_ref || null,
|
|
1123
|
+
transaction_id: handoff.transaction_id || null,
|
|
1124
|
+
receipt_id: handoff.receipt_id || null,
|
|
1125
|
+
proof_ref: handoff.proof_ref || null,
|
|
1126
|
+
committed_ref: handoff.committed_ref || null,
|
|
1127
|
+
precommit_pack_ref: handoff.precommit_pack_ref || null,
|
|
1128
|
+
task_ref_count: Array.isArray(handoff.task_refs) ? handoff.task_refs.length : 0,
|
|
1129
|
+
proof_bundle_version: proofBundle.version || null,
|
|
1130
|
+
emitted_receipt_id: receipt.receipt_id || null,
|
|
1131
|
+
bundle_valid: verification.valid === true,
|
|
1132
|
+
recommended_loop: item.recommended_loop || null
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
880
1136
|
function runDefaultProofLoop(loopRef, input) {
|
|
881
1137
|
const loop = getDefaultProofLoop(loopRef);
|
|
882
1138
|
if (!loop) {
|
|
@@ -920,11 +1176,23 @@ module.exports = {
|
|
|
920
1176
|
buildDefaultProofCenter,
|
|
921
1177
|
buildDefaultProofLoopCatalog,
|
|
922
1178
|
buildDefaultProofLoops,
|
|
1179
|
+
buildInteractionSpineProofHandoffCenter,
|
|
1180
|
+
buildTransactionProofHandoffCenter,
|
|
1181
|
+
buildInteractionSpineProofHandoffEvent,
|
|
1182
|
+
buildTransactionProofHandoffEvent,
|
|
1183
|
+
canonicalizeInteractionSpineProofHandoff,
|
|
923
1184
|
getDefaultProofLoop,
|
|
1185
|
+
canonicalizeTransactionProofHandoff,
|
|
924
1186
|
runDefaultProofLoop,
|
|
1187
|
+
runInteractionSpineProofHandoff,
|
|
1188
|
+
runTransactionProofHandoff,
|
|
925
1189
|
summarizeDefaultProofCenter,
|
|
926
1190
|
summarizeDefaultProofLoops,
|
|
927
1191
|
summarizeGuidedProofLoopBundle,
|
|
1192
|
+
summarizeInteractionSpineProofHandoff,
|
|
1193
|
+
summarizeInteractionSpineProofHandoffCenter,
|
|
1194
|
+
summarizeTransactionProofHandoff,
|
|
1195
|
+
summarizeTransactionProofHandoffCenter,
|
|
928
1196
|
canonicalizeEvent,
|
|
929
1197
|
hashEvent,
|
|
930
1198
|
emitReceipt,
|
package/server.js
CHANGED
|
@@ -97,6 +97,26 @@ async function routeRequest(req, res) {
|
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
if (req.method === 'GET' && url.pathname === '/v1/proof-center/transaction-handoff') {
|
|
101
|
+
sendJson(res, 200, proofSdk.buildTransactionProofHandoffCenter());
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (req.method === 'GET' && url.pathname === '/v1/proof-center/transaction-handoff/summary') {
|
|
106
|
+
sendJson(res, 200, proofSdk.summarizeTransactionProofHandoffCenter());
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (req.method === 'GET' && url.pathname === '/v1/proof-center/interaction-spine-handoff') {
|
|
111
|
+
sendJson(res, 200, proofSdk.buildInteractionSpineProofHandoffCenter());
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (req.method === 'GET' && url.pathname === '/v1/proof-center/interaction-spine-handoff/summary') {
|
|
116
|
+
sendJson(res, 200, proofSdk.summarizeInteractionSpineProofHandoffCenter());
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
if (req.method === 'GET' && url.pathname === '/v1/proof-center/default-loop') {
|
|
101
121
|
const loop = proofSdk.getDefaultProofLoop(url.searchParams.get('ref'));
|
|
102
122
|
if (!loop) {
|
|
@@ -119,6 +139,28 @@ async function routeRequest(req, res) {
|
|
|
119
139
|
return;
|
|
120
140
|
}
|
|
121
141
|
|
|
142
|
+
if (req.method === 'POST' && url.pathname === '/v1/proof-center/transaction-handoff/run') {
|
|
143
|
+
try {
|
|
144
|
+
const body = await readJsonBody(req);
|
|
145
|
+
const result = proofSdk.runTransactionProofHandoff(body);
|
|
146
|
+
sendJson(res, 200, result);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
sendJson(res, 400, { ok: false, reason: error.message });
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (req.method === 'POST' && url.pathname === '/v1/proof-center/interaction-spine-handoff/run') {
|
|
154
|
+
try {
|
|
155
|
+
const body = await readJsonBody(req);
|
|
156
|
+
const result = proofSdk.runInteractionSpineProofHandoff(body);
|
|
157
|
+
sendJson(res, 200, result);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
sendJson(res, 400, { ok: false, reason: error.message });
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
122
164
|
if (req.method === 'POST' && url.pathname === '/v1/receipts/verify') {
|
|
123
165
|
try {
|
|
124
166
|
const body = await readJsonBody(req);
|