tixbit 0.2.0 → 0.2.2
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 +3 -1
- package/dist/cli.js +55 -9
- package/dist/index.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,9 @@ tixbit link start <listing-id> --quantity 1 --email buyer@example.com --max-pric
|
|
|
95
95
|
tixbit link complete <order-reference>
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
The second command checks approval, sends one Stripe MPP credential, and reports whether the ticket was issued. If approval is still pending, it returns the same URL without sending payment.
|
|
98
|
+
The second command checks approval, sends one Stripe MPP credential, and reports whether the ticket was issued. If approval is still pending, it returns the same URL without sending payment. After payment submission, the CLI will not send that payment again. If the result is `pending`, do not start another checkout. Contact TixBit support with the order reference so the charge and ticket can be reconciled. The CLI stores the purchase key in a private file under `~/.local/state/tixbit/link` (or `XDG_STATE_HOME`) and never prints or saves the Link payment token. Keep that file until the order is settled. Each command returns JSON; `approval_required` and payment failures use exit code 2.
|
|
99
|
+
|
|
100
|
+
Each spend request includes its TixBit order reference in the approval details. Link can still rate limit a matching amount and merchant for a short time, even after a request is canceled. If Link rejects a request, the CLI reports a safe error code such as `LINK_DUPLICATE_REQUEST` or `LINK_NETWORK_UNAVAILABLE` without printing Link credentials.
|
|
99
101
|
|
|
100
102
|
`--email` is always required and is never inferred from git or local account state. If a request times out or returns `pending` or `manual_review_required`, reuse the same idempotency key and follow the returned `action`; do not start another payment.
|
|
101
103
|
|
package/dist/cli.js
CHANGED
|
@@ -428,6 +428,20 @@ var TixBitClient = class {
|
|
|
428
428
|
const body = await response.json().catch(() => ({}));
|
|
429
429
|
const orderReference = normalizeOrderReference(body.orderReference);
|
|
430
430
|
const receiptUrl = normalizeReceiptUrl(body.receiptUrl);
|
|
431
|
+
if (response.status >= 500 && body.status !== "manual_review_required") {
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
status: "pending",
|
|
435
|
+
idempotencyKey,
|
|
436
|
+
orderReference,
|
|
437
|
+
receiptUrl,
|
|
438
|
+
error: {
|
|
439
|
+
code: "PURCHASE_OUTCOME_AMBIGUOUS",
|
|
440
|
+
message: "The server failed to confirm the purchase outcome."
|
|
441
|
+
},
|
|
442
|
+
action: "Check the order with the same idempotency key. Do not create a new payment or purchase attempt."
|
|
443
|
+
};
|
|
444
|
+
}
|
|
431
445
|
if (response.status === 402) {
|
|
432
446
|
const order = normalizePurchaseOrder(
|
|
433
447
|
body.order,
|
|
@@ -923,8 +937,30 @@ async function linkCli(args) {
|
|
|
923
937
|
windowsHide: true
|
|
924
938
|
});
|
|
925
939
|
stdout = typeof result === "string" ? result : result.stdout;
|
|
926
|
-
} catch {
|
|
927
|
-
|
|
940
|
+
} catch (error) {
|
|
941
|
+
const output2 = error && typeof error === "object" && "stdout" in error ? String(error.stdout) : "";
|
|
942
|
+
let message = "";
|
|
943
|
+
try {
|
|
944
|
+
const parsed = JSON.parse(output2);
|
|
945
|
+
const data = parsed && typeof parsed === "object" && !Array.isArray(parsed) && "data" in parsed ? parsed.data : parsed;
|
|
946
|
+
const item = Array.isArray(data) ? data[0] : data;
|
|
947
|
+
if (item && typeof item === "object" && "message" in item) {
|
|
948
|
+
message = String(item.message);
|
|
949
|
+
} else {
|
|
950
|
+
const nested = parsed && typeof parsed === "object" && !Array.isArray(parsed) && "error" in parsed ? parsed.error : void 0;
|
|
951
|
+
if (nested && typeof nested === "object" && "message" in nested) {
|
|
952
|
+
message = String(nested.message);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
if (/duplicate spend requests|matching spend request/i.test(message)) {
|
|
958
|
+
throw new LinkCheckoutError("LINK_DUPLICATE_REQUEST", "Link blocked a matching spend request. Wait a few minutes, then start a new checkout.");
|
|
959
|
+
}
|
|
960
|
+
if (/Invalid network_id|could not retrieve merchant information/i.test(message)) {
|
|
961
|
+
throw new LinkCheckoutError("LINK_NETWORK_UNAVAILABLE", "Link could not resolve the merchant profile in this account and mode. No payment was sent.");
|
|
962
|
+
}
|
|
963
|
+
throw new LinkCheckoutError("LINK_CLI_FAILED", "Link could not complete this step. Check Link sign-in and payment methods, then retry.");
|
|
928
964
|
}
|
|
929
965
|
try {
|
|
930
966
|
const parsed = JSON.parse(stdout);
|
|
@@ -984,7 +1020,7 @@ async function startLinkPurchase(input) {
|
|
|
984
1020
|
if (typeof body.orderReference !== "string" || !ORDER_REFERENCE.test(body.orderReference)) {
|
|
985
1021
|
throw new LinkCheckoutError("ORDER_REFERENCE_MISSING", "TixBit did not return an order reference.");
|
|
986
1022
|
}
|
|
987
|
-
const context = `Approve one TixBit ticket purchase for listing ${purchase.listingId}, quantity ${purchase.quantity}. The total charge is USD ${(amountCents / 100).toFixed(2)}. This request permits one machine payment for this selected order only.`;
|
|
1023
|
+
const context = `Approve one TixBit ticket purchase for order ${body.orderReference}, listing ${purchase.listingId}, quantity ${purchase.quantity}. The total charge is USD ${(amountCents / 100).toFixed(2)}. This request permits one machine payment for this selected order only.`;
|
|
988
1024
|
const created = await linkCli([
|
|
989
1025
|
"spend-request",
|
|
990
1026
|
"create",
|
|
@@ -998,6 +1034,8 @@ async function startLinkPurchase(input) {
|
|
|
998
1034
|
"usd",
|
|
999
1035
|
"--context",
|
|
1000
1036
|
context,
|
|
1037
|
+
"--metadata",
|
|
1038
|
+
`order_reference:${body.orderReference}`,
|
|
1001
1039
|
"--request-approval"
|
|
1002
1040
|
]);
|
|
1003
1041
|
if (typeof created.id !== "string" || !SPEND_REQUEST_ID.test(created.id)) {
|
|
@@ -1025,7 +1063,15 @@ async function startLinkPurchase(input) {
|
|
|
1025
1063
|
}
|
|
1026
1064
|
async function completeLinkPurchase(orderReference) {
|
|
1027
1065
|
const state = await loadState(orderReference);
|
|
1028
|
-
if (state.result
|
|
1066
|
+
if (state.result) return state.result;
|
|
1067
|
+
if (state.paymentAttempted) return {
|
|
1068
|
+
success: false,
|
|
1069
|
+
status: "pending",
|
|
1070
|
+
orderReference,
|
|
1071
|
+
amountCents: state.amountCents,
|
|
1072
|
+
error: { code: "PURCHASE_OUTCOME_AMBIGUOUS", message: "Payment may have been submitted, but the result is unknown." },
|
|
1073
|
+
action: "Do not retry payment or start another checkout. Contact TixBit support with this order reference."
|
|
1074
|
+
};
|
|
1029
1075
|
const approval = await linkCli(["spend-request", "retrieve", state.spendRequestId, "--include", "shared_payment_token"]);
|
|
1030
1076
|
if (["denied", "declined", "expired", "canceled", "cancelled"].includes(String(approval.status))) {
|
|
1031
1077
|
return {
|
|
@@ -1073,6 +1119,8 @@ async function completeLinkPurchase(orderReference) {
|
|
|
1073
1119
|
}
|
|
1074
1120
|
});
|
|
1075
1121
|
let decline = false;
|
|
1122
|
+
state.paymentAttempted = true;
|
|
1123
|
+
await saveState(state);
|
|
1076
1124
|
const client2 = new TixBitClient({
|
|
1077
1125
|
paymentEndpoint: paymentEndpoint(),
|
|
1078
1126
|
timeoutMs: 9e4,
|
|
@@ -1099,13 +1147,11 @@ async function completeLinkPurchase(orderReference) {
|
|
|
1099
1147
|
...result.order && !decline ? { order: result.order } : {},
|
|
1100
1148
|
...!result.success ? {
|
|
1101
1149
|
error: decline ? { code: "CARD_DECLINED", message: "Stripe declined the card selected in Link." } : result.error,
|
|
1102
|
-
action: decline ? "Select another card in Link and start a new spend approval. No ticket was issued." : result.action
|
|
1150
|
+
action: decline ? "Select another card in Link and start a new spend approval. No ticket was issued." : result.status === "pending" ? "Payment may have succeeded. Do not retry or start another checkout. Contact TixBit support with this order reference." : result.action
|
|
1103
1151
|
} : {}
|
|
1104
1152
|
};
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
await saveState(state);
|
|
1108
|
-
}
|
|
1153
|
+
state.result = output2;
|
|
1154
|
+
await saveState(state);
|
|
1109
1155
|
return output2;
|
|
1110
1156
|
}
|
|
1111
1157
|
|
package/dist/index.js
CHANGED
|
@@ -408,6 +408,20 @@ var TixBitClient = class {
|
|
|
408
408
|
const body = await response.json().catch(() => ({}));
|
|
409
409
|
const orderReference = normalizeOrderReference(body.orderReference);
|
|
410
410
|
const receiptUrl = normalizeReceiptUrl(body.receiptUrl);
|
|
411
|
+
if (response.status >= 500 && body.status !== "manual_review_required") {
|
|
412
|
+
return {
|
|
413
|
+
success: false,
|
|
414
|
+
status: "pending",
|
|
415
|
+
idempotencyKey,
|
|
416
|
+
orderReference,
|
|
417
|
+
receiptUrl,
|
|
418
|
+
error: {
|
|
419
|
+
code: "PURCHASE_OUTCOME_AMBIGUOUS",
|
|
420
|
+
message: "The server failed to confirm the purchase outcome."
|
|
421
|
+
},
|
|
422
|
+
action: "Check the order with the same idempotency key. Do not create a new payment or purchase attempt."
|
|
423
|
+
};
|
|
424
|
+
}
|
|
411
425
|
if (response.status === 402) {
|
|
412
426
|
const order = normalizePurchaseOrder(
|
|
413
427
|
body.order,
|