@vonpay/checkout-cli 0.18.0 → 0.20.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 +4 -2
- package/dist/index.js +462 -378
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
2
19
|
|
|
3
20
|
// src/program.ts
|
|
4
|
-
import { Command as
|
|
21
|
+
import { Command as Command14 } from "commander";
|
|
5
22
|
|
|
6
23
|
// src/commands/login.ts
|
|
7
24
|
import { Command, Option } from "commander";
|
|
@@ -10,7 +27,7 @@ import chalk from "chalk";
|
|
|
10
27
|
|
|
11
28
|
// src/config.ts
|
|
12
29
|
import { chmodSync as chmodSync2, existsSync, mkdirSync as mkdirSync2, readFileSync, rmSync, writeFileSync } from "fs";
|
|
13
|
-
import { homedir
|
|
30
|
+
import { homedir } from "os";
|
|
14
31
|
import { join as join2 } from "path";
|
|
15
32
|
|
|
16
33
|
// src/utils/json-errors.ts
|
|
@@ -31,218 +48,93 @@ function emitCliError(json, text) {
|
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
// src/utils/key-mode.ts
|
|
34
|
-
import {
|
|
35
|
-
var TEST_SECRET_KEY_PREFIX = "vp_sk_test_";
|
|
36
|
-
function requiresLiveConfirmation(key) {
|
|
37
|
-
if (typeof key !== "string") return true;
|
|
38
|
-
if (key.startsWith(TEST_SECRET_KEY_PREFIX)) return false;
|
|
39
|
-
if (key.startsWith("vp_pk_")) return false;
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
51
|
+
import { keyMode } from "@vonpay/checkout-node";
|
|
42
52
|
function secretKeyMode(key) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (key.startsWith(TEST_SECRET_KEY_PREFIX)) return "test";
|
|
46
|
-
return null;
|
|
53
|
+
const mode = keyMode(key);
|
|
54
|
+
return mode === "test" || mode === "live" ? mode : null;
|
|
47
55
|
}
|
|
48
56
|
var NOT_A_SECRET_KEY_MESSAGE = "This is not a Von Payments secret key: it must start with vp_sk_test_ (sandbox) or vp_sk_live_ (live). Publishable keys (vp_pk_\u2026) and keys from other services cannot be used with this CLI.";
|
|
49
57
|
|
|
50
58
|
// src/utils/audit-log.ts
|
|
59
|
+
var audit_log_exports = {};
|
|
60
|
+
__export(audit_log_exports, {
|
|
61
|
+
AUDIT_FIELDS: () => AUDIT_FIELDS,
|
|
62
|
+
COMMAND_KIND: () => COMMAND_KIND,
|
|
63
|
+
MONEY: () => MONEY,
|
|
64
|
+
UNKNOWN_COMMAND: () => UNKNOWN_COMMAND,
|
|
65
|
+
_resetAuditForTests: () => _resetAuditForTests,
|
|
66
|
+
_resetAuditWarningsForTests: () => _resetAuditWarningsForTests,
|
|
67
|
+
auditError: () => auditError,
|
|
68
|
+
auditFinish: () => auditFinish,
|
|
69
|
+
auditIntent: () => auditIntent,
|
|
70
|
+
auditLine: () => auditLine,
|
|
71
|
+
auditNote: () => auditNote,
|
|
72
|
+
auditSignalHandledByCommand: () => auditSignalHandledByCommand,
|
|
73
|
+
auditStart: () => auditStart,
|
|
74
|
+
auditUsageError: () => auditUsageError,
|
|
75
|
+
fullRecordLine: () => fullRecordLine,
|
|
76
|
+
isMoneyCommand: () => isMoneyCommand,
|
|
77
|
+
pruneAuditFiles: () => pruneAuditFiles,
|
|
78
|
+
writeAuditLine: () => writeAuditLine
|
|
79
|
+
});
|
|
80
|
+
__reExport(audit_log_exports, audit_log_star);
|
|
51
81
|
import {
|
|
52
82
|
chmodSync,
|
|
53
83
|
closeSync,
|
|
54
|
-
constants as FS,
|
|
55
84
|
fchmodSync,
|
|
56
85
|
fstatSync,
|
|
57
86
|
lstatSync,
|
|
58
87
|
mkdirSync,
|
|
59
88
|
openSync,
|
|
60
89
|
readdirSync,
|
|
90
|
+
readSync,
|
|
61
91
|
unlinkSync,
|
|
62
92
|
writeSync
|
|
63
93
|
} from "fs";
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"sent",
|
|
85
|
-
// an API command ran to the end (sent and answered); no person was asked
|
|
86
|
-
"local",
|
|
87
|
-
// a command that sends nothing to the API (login, logout, init, trigger) ran to the end
|
|
88
|
-
"approved",
|
|
89
|
-
// a person confirmed it at the terminal, and it ran to the end
|
|
90
|
-
"declined",
|
|
91
|
-
// the person did not type "confirm"
|
|
92
|
-
"budget_refused",
|
|
93
|
-
// (the MCP server's spending budget; the CLI has none)
|
|
94
|
-
"read_mode_blocked",
|
|
95
|
-
// (the MCP server's read-only mode; the CLI has none)
|
|
96
|
-
"withheld",
|
|
97
|
-
// a live action with no person at an interactive terminal to confirm it
|
|
98
|
-
"lookup_refused",
|
|
99
|
-
// the check before the confirmation failed (a refund's payment could not be looked up or did not match)
|
|
100
|
-
"refused_local",
|
|
101
|
-
// refused by the CLI before anything was sent (a flag, a key rule, an unsafe base URL, a command line it cannot parse, …)
|
|
102
|
-
"error"
|
|
103
|
-
// the API (or the network) answered with an error, the run was interrupted, or the CLI crashed (internal_error)
|
|
104
|
-
];
|
|
105
|
-
var LOCAL_ERROR_CODES = [
|
|
106
|
-
"auth_failed",
|
|
107
|
-
"budget",
|
|
108
|
-
"cancelled",
|
|
109
|
-
"confirm_live_required",
|
|
110
|
-
"currency_mismatch",
|
|
111
|
-
"declined",
|
|
112
|
-
"dismissed",
|
|
113
|
-
"excess_arguments",
|
|
114
|
-
"idempotency_key_required",
|
|
115
|
-
"internal_error",
|
|
116
|
-
"interrupted",
|
|
117
|
-
"invalid_argument",
|
|
118
|
-
"invalid_input",
|
|
119
|
-
"live_loopback_url",
|
|
120
|
-
"local_refusal",
|
|
121
|
-
"lookup_failed",
|
|
122
|
-
"missing_argument",
|
|
123
|
-
"network_error",
|
|
124
|
-
"no_approval_channel",
|
|
125
|
-
"no_terminal",
|
|
126
|
-
"not_ticked",
|
|
127
|
-
"prompt_failed",
|
|
128
|
-
"read_mode",
|
|
129
|
-
"unknown_command",
|
|
130
|
-
"unknown_option",
|
|
131
|
-
"unknown_tool",
|
|
132
|
-
"usage_error"
|
|
133
|
-
];
|
|
134
|
-
var LOCAL_CODES = new Set(LOCAL_ERROR_CODES);
|
|
135
|
-
var AUDIT_DIR_ENV = "VONPAY_AUDIT_DIR";
|
|
136
|
-
var AUDIT_SWITCH_ENV = "VONPAY_AUDIT_LOG";
|
|
137
|
-
var AUDIT_RETENTION_ENV = "VONPAY_AUDIT_RETENTION_DAYS";
|
|
138
|
-
var DEFAULT_RETENTION_DAYS = 30;
|
|
139
|
-
var MAX_FILE_BYTES = 50 * 1024 * 1024;
|
|
140
|
-
function auditText(s, max) {
|
|
141
|
-
const line = stripHiddenCharacters(s).replace(/[\p{Cc}\u{2028}\u{2029}]/gu, " ").trim();
|
|
142
|
-
return line.length > max ? line.slice(0, max) : line;
|
|
143
|
-
}
|
|
144
|
-
function keyFingerprint(key) {
|
|
145
|
-
if (key.length >= 24) return `${key.slice(0, 12)}\u2026${key.slice(-4)}`;
|
|
146
|
-
const head = Math.min(12, Math.floor(key.length / 3));
|
|
147
|
-
const tail = Math.min(4, Math.floor(key.length / 4));
|
|
148
|
-
return `${key.slice(0, head)}\u2026${key.slice(key.length - tail)}`;
|
|
149
|
-
}
|
|
150
|
-
var TARGET_ID = /^(vpi|vp_tx|vsub|vp_cs)_(live|test)_[A-Za-z0-9_-]{16}$/;
|
|
151
|
-
var REQUEST_ID = /^[A-Za-z0-9_-]{12}$/;
|
|
152
|
-
var CURRENCY = /^[A-Za-z]{3}$/;
|
|
153
|
-
var COMMAND = /^[a-z0-9-]+( [a-z0-9-]+){0,3}$/;
|
|
94
|
+
import { join } from "path";
|
|
95
|
+
import {
|
|
96
|
+
AUDIT_DIR_ENV,
|
|
97
|
+
AUDIT_SWITCH_ENV,
|
|
98
|
+
DEFAULT_AUDIT_LIMITS,
|
|
99
|
+
FULL_RECORD_SUFFIX,
|
|
100
|
+
auditAppendFlags,
|
|
101
|
+
auditErrorCodeOf,
|
|
102
|
+
auditFileExpired,
|
|
103
|
+
auditKeyFields,
|
|
104
|
+
auditLineFormat,
|
|
105
|
+
auditSettingsFromEnv,
|
|
106
|
+
auditSpaceWarnings,
|
|
107
|
+
checkAuditDirStats,
|
|
108
|
+
checkAuditFileStats,
|
|
109
|
+
isReservedLine,
|
|
110
|
+
spaceDecision,
|
|
111
|
+
stderrWarn
|
|
112
|
+
} from "@vonpay/checkout-node/internal/audit-log";
|
|
113
|
+
import * as audit_log_star from "@vonpay/checkout-node/internal/audit-log";
|
|
154
114
|
var UNKNOWN_COMMAND = "[unknown command]";
|
|
155
|
-
var
|
|
156
|
-
var
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
function auditLine(record) {
|
|
161
|
-
const out = {};
|
|
162
|
-
for (const field of AUDIT_FIELDS) {
|
|
163
|
-
const v = record[field];
|
|
164
|
-
if (v === void 0 || v === null) continue;
|
|
165
|
-
switch (field) {
|
|
166
|
-
case "amount":
|
|
167
|
-
if (typeof v === "number" && Number.isSafeInteger(v) && v >= 0) out[field] = v;
|
|
168
|
-
break;
|
|
169
|
-
case "currency":
|
|
170
|
-
if (typeof v === "string" && CURRENCY.test(v)) out[field] = v.toUpperCase();
|
|
171
|
-
break;
|
|
172
|
-
case "target":
|
|
173
|
-
if (typeof v === "string") out[field] = TARGET_ID.test(v) ? v : UNRECOGNISED_ID;
|
|
174
|
-
break;
|
|
175
|
-
case "error_code":
|
|
176
|
-
if (typeof v === "string") out[field] = knownErrorCode(v) ? v : "unrecognised_code";
|
|
177
|
-
break;
|
|
178
|
-
case "request_id":
|
|
179
|
-
if (typeof v === "string") out[field] = REQUEST_ID.test(v) ? v : UNRECOGNISED_REQUEST_ID;
|
|
180
|
-
break;
|
|
181
|
-
case "outcome":
|
|
182
|
-
if (typeof v === "string" && AUDIT_OUTCOMES.includes(v)) out[field] = v;
|
|
183
|
-
break;
|
|
184
|
-
case "key_mode":
|
|
185
|
-
if (v === "test" || v === "live") out[field] = v;
|
|
186
|
-
break;
|
|
187
|
-
case "command":
|
|
188
|
-
if (typeof v === "string") out[field] = COMMAND.test(v) ? v : UNKNOWN_COMMAND;
|
|
189
|
-
break;
|
|
190
|
-
default:
|
|
191
|
-
if (typeof v === "string") out[field] = auditText(v, field === "ts" ? 40 : 80);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return `${JSON.stringify(out)}
|
|
195
|
-
`;
|
|
196
|
-
}
|
|
197
|
-
function auditSettingsFromEnv(env = process.env, warn = stderrWarn) {
|
|
198
|
-
const off = ["off", "0", "false", "no"].includes((env[AUDIT_SWITCH_ENV] ?? "").trim().toLowerCase());
|
|
199
|
-
const raw = (env[AUDIT_RETENTION_ENV] ?? "").trim();
|
|
200
|
-
let retentionDays = DEFAULT_RETENTION_DAYS;
|
|
201
|
-
if (raw !== "") {
|
|
202
|
-
const n = Number(raw);
|
|
203
|
-
if (/^[0-9]{1,3}$/.test(raw) && n >= 1 && n <= 365) retentionDays = n;
|
|
204
|
-
else warn(`[vonpay] ${AUDIT_RETENTION_ENV} must be a whole number from 1 to 365; keeping audit files ${DEFAULT_RETENTION_DAYS} days.`);
|
|
205
|
-
}
|
|
206
|
-
const dir = env[AUDIT_DIR_ENV]?.trim() ? resolve(env[AUDIT_DIR_ENV].trim()) : join(homedir(), ".vonpay", "audit");
|
|
207
|
-
return { dir: off ? null : dir, retentionDays };
|
|
208
|
-
}
|
|
209
|
-
function stderrWarn(m) {
|
|
210
|
-
try {
|
|
211
|
-
process.stderr.write(`${m}
|
|
212
|
-
`);
|
|
213
|
-
} catch (err) {
|
|
214
|
-
void err;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function errorCodeOf(err) {
|
|
218
|
-
return err !== null && typeof err === "object" && typeof err.code === "string" ? err.code : "error";
|
|
219
|
-
}
|
|
220
|
-
var AuditPlaceRefused = class extends Error {
|
|
221
|
-
constructor(code) {
|
|
222
|
-
super(code);
|
|
223
|
-
this.code = code;
|
|
224
|
-
}
|
|
225
|
-
code;
|
|
226
|
-
};
|
|
227
|
-
function myUid() {
|
|
228
|
-
return typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
229
|
-
}
|
|
115
|
+
var FORMAT = auditLineFormat({ nameField: "command", namePattern: /^[a-z0-9-]+( [a-z0-9-]+){0,3}$/, unknownName: UNKNOWN_COMMAND });
|
|
116
|
+
var AUDIT_FIELDS = FORMAT.fields;
|
|
117
|
+
var auditLine = FORMAT.line;
|
|
118
|
+
var fullRecordLine = FORMAT.fullRecordLine;
|
|
230
119
|
function checkDir(dir, st) {
|
|
231
|
-
if (st
|
|
232
|
-
if (!st.isDirectory()) throw new AuditPlaceRefused("not_a_directory");
|
|
233
|
-
const uid = myUid();
|
|
234
|
-
if (uid !== void 0 && st.uid !== uid) throw new AuditPlaceRefused("directory_owned_by_another_user");
|
|
235
|
-
if (uid !== void 0 && (st.mode & 63) !== 0) chmodSync(dir, 448);
|
|
120
|
+
if (checkAuditDirStats(st)) chmodSync(dir, 448);
|
|
236
121
|
}
|
|
237
122
|
var FILE_NAME = /^cli-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
238
|
-
var appendFlags = () => FS.O_APPEND | FS.O_CREAT | FS.O_WRONLY | (FS.O_NOFOLLOW ?? 0);
|
|
239
123
|
var warned = false;
|
|
240
124
|
var warnedFull = false;
|
|
125
|
+
var warnedReserve = false;
|
|
126
|
+
var warnedTooLong = false;
|
|
127
|
+
function _resetAuditWarningsForTests() {
|
|
128
|
+
warned = false;
|
|
129
|
+
warnedFull = false;
|
|
130
|
+
warnedReserve = false;
|
|
131
|
+
warnedTooLong = false;
|
|
132
|
+
}
|
|
241
133
|
function warnOnce(dir, err, warn) {
|
|
242
134
|
if (warned) return;
|
|
243
135
|
warned = true;
|
|
244
136
|
warn(
|
|
245
|
-
`[vonpay] The local audit log cannot be written (${
|
|
137
|
+
`[vonpay] The local audit log cannot be written (${auditErrorCodeOf(err)}) in ${dir}; the command went on without it. Set ${AUDIT_DIR_ENV} to a writable directory of your own, or ${AUDIT_SWITCH_ENV}=off.`
|
|
246
138
|
);
|
|
247
139
|
}
|
|
248
140
|
function pruneAuditFiles(dir, retentionDays, now = /* @__PURE__ */ new Date(), warn = stderrWarn) {
|
|
@@ -251,17 +143,15 @@ function pruneAuditFiles(dir, retentionDays, now = /* @__PURE__ */ new Date(), w
|
|
|
251
143
|
checkDir(dir, lstatSync(dir));
|
|
252
144
|
names = readdirSync(dir);
|
|
253
145
|
} catch (err) {
|
|
254
|
-
if (
|
|
146
|
+
if (auditErrorCodeOf(err) !== "ENOENT") warnOnce(dir, err, warn);
|
|
255
147
|
return 0;
|
|
256
148
|
}
|
|
257
|
-
const today = Date.parse(`${now.toISOString().slice(0, 10)}T00:00:00Z`);
|
|
258
149
|
let removed = 0;
|
|
259
150
|
let skipped = 0;
|
|
260
151
|
for (const name of names) {
|
|
261
152
|
const m = FILE_NAME.exec(name);
|
|
262
153
|
if (!m) continue;
|
|
263
|
-
|
|
264
|
-
if (!Number.isFinite(day) || (today - day) / 864e5 <= retentionDays) continue;
|
|
154
|
+
if (!auditFileExpired(`${m[1]}-${m[2]}-${m[3]}`, now, retentionDays)) continue;
|
|
265
155
|
const path = join(dir, name);
|
|
266
156
|
try {
|
|
267
157
|
if (!lstatSync(path).isFile()) {
|
|
@@ -274,42 +164,60 @@ function pruneAuditFiles(dir, retentionDays, now = /* @__PURE__ */ new Date(), w
|
|
|
274
164
|
skipped++;
|
|
275
165
|
}
|
|
276
166
|
}
|
|
277
|
-
if (skipped > 0) warn(
|
|
167
|
+
if (skipped > 0) warn(auditSpaceWarnings.notPruned(skipped, dir));
|
|
278
168
|
return removed;
|
|
279
169
|
}
|
|
280
|
-
function
|
|
281
|
-
const
|
|
170
|
+
function endsWithFullRecord(fd, size) {
|
|
171
|
+
const n = Buffer.byteLength(FULL_RECORD_SUFFIX);
|
|
172
|
+
if (size < n) return false;
|
|
173
|
+
const buf = Buffer.alloc(n);
|
|
174
|
+
readSync(fd, buf, 0, n, size - n);
|
|
175
|
+
return buf.toString("utf8") === FULL_RECORD_SUFFIX;
|
|
176
|
+
}
|
|
177
|
+
function appendSecure(file, line, reserved, record, now, limits, dir, warn) {
|
|
178
|
+
const fd = openSync(file, auditAppendFlags(), 384);
|
|
282
179
|
try {
|
|
283
180
|
const st = fstatSync(fd);
|
|
284
|
-
if (
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
181
|
+
if (checkAuditFileStats(st)) fchmodSync(fd, 384);
|
|
182
|
+
const decision = spaceDecision(st.size, Buffer.byteLength(line), reserved, limits);
|
|
183
|
+
if (decision === "write") {
|
|
184
|
+
writeSync(fd, line);
|
|
185
|
+
} else if (decision === "too_long") {
|
|
186
|
+
if (!warnedTooLong) {
|
|
187
|
+
warnedTooLong = true;
|
|
188
|
+
warn(auditSpaceWarnings.tooLong(limits.maxLineBytes, dir));
|
|
189
|
+
}
|
|
190
|
+
} else if (decision === "drop") {
|
|
191
|
+
if (!warnedReserve) {
|
|
192
|
+
warnedReserve = true;
|
|
193
|
+
warn(auditSpaceWarnings.nearlyFull(dir));
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
const full = fullRecordLine(record.surface, record.version, now);
|
|
197
|
+
if (!endsWithFullRecord(fd, st.size) && st.size + Buffer.byteLength(full) <= limits.maxFileBytes) writeSync(fd, full);
|
|
289
198
|
if (!warnedFull) {
|
|
290
199
|
warnedFull = true;
|
|
291
|
-
warn(
|
|
200
|
+
warn(auditSpaceWarnings.full(dir));
|
|
292
201
|
}
|
|
293
|
-
return;
|
|
294
202
|
}
|
|
295
|
-
writeSync(fd, line);
|
|
296
203
|
} finally {
|
|
297
204
|
closeSync(fd);
|
|
298
205
|
}
|
|
299
206
|
}
|
|
300
|
-
function writeAuditLine(dir, record, now = /* @__PURE__ */ new Date(), warn = stderrWarn,
|
|
207
|
+
function writeAuditLine(dir, record, now = /* @__PURE__ */ new Date(), warn = stderrWarn, limits = DEFAULT_AUDIT_LIMITS) {
|
|
301
208
|
const file = join(dir, `cli-${now.toISOString().slice(0, 10)}.jsonl`);
|
|
302
209
|
try {
|
|
303
210
|
const line = auditLine(record);
|
|
211
|
+
const reserved = isReservedLine(record, typeof record.command === "string" && isMoneyCommand(record.command));
|
|
304
212
|
const attempt = () => {
|
|
305
213
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
306
214
|
checkDir(dir, lstatSync(dir));
|
|
307
|
-
appendSecure(file, line,
|
|
215
|
+
appendSecure(file, line, reserved, record, now, limits, dir, warn);
|
|
308
216
|
};
|
|
309
217
|
try {
|
|
310
218
|
attempt();
|
|
311
219
|
} catch (err) {
|
|
312
|
-
if (
|
|
220
|
+
if (auditErrorCodeOf(err) !== "ENOENT") throw err;
|
|
313
221
|
attempt();
|
|
314
222
|
}
|
|
315
223
|
return true;
|
|
@@ -335,6 +243,8 @@ var COMMAND_KIND = {
|
|
|
335
243
|
"checkout refunds create": "api",
|
|
336
244
|
"checkout refunds list": "api",
|
|
337
245
|
"checkout tokens create": "api",
|
|
246
|
+
"checkout subscription-products list": "api",
|
|
247
|
+
"checkout subscription-products terms": "api",
|
|
338
248
|
"checkout capabilities": "api",
|
|
339
249
|
"checkout listen": "api",
|
|
340
250
|
"checkout health": "api",
|
|
@@ -347,6 +257,9 @@ var MONEY = {
|
|
|
347
257
|
"checkout refunds create": (o) => ({ amount: wholeNumber(o.amount), currency: o.currency, target: o.paymentIntent ?? o.transaction }),
|
|
348
258
|
"checkout sessions create": (o) => ({ amount: wholeNumber(o.amount), currency: o.currency })
|
|
349
259
|
};
|
|
260
|
+
function isMoneyCommand(command) {
|
|
261
|
+
return Object.prototype.hasOwnProperty.call(MONEY, command);
|
|
262
|
+
}
|
|
350
263
|
function wholeNumber(v) {
|
|
351
264
|
return typeof v === "string" && /^[0-9]{1,15}$/.test(v.trim()) ? Number(v.trim()) : typeof v === "number" ? v : void 0;
|
|
352
265
|
}
|
|
@@ -354,9 +267,6 @@ var pending = null;
|
|
|
354
267
|
var hooked = false;
|
|
355
268
|
var SIGNAL_EXIT = { SIGINT: 130, SIGTERM: 143 };
|
|
356
269
|
var processHandlers = [];
|
|
357
|
-
function keyFields(apiKey) {
|
|
358
|
-
return apiKey ? { key_mode: apiKey.startsWith("vp_sk_test_") ? "test" : "live", key_fingerprint: keyFingerprint(apiKey) } : {};
|
|
359
|
-
}
|
|
360
270
|
var signalsHandledByCommand = false;
|
|
361
271
|
function auditSignalHandledByCommand() {
|
|
362
272
|
signalsHandledByCommand = true;
|
|
@@ -387,13 +297,13 @@ function auditStart(command, version, opts = {}, args = []) {
|
|
|
387
297
|
return;
|
|
388
298
|
}
|
|
389
299
|
pruneAuditFiles(settings.dir, settings.retentionDays);
|
|
390
|
-
let
|
|
300
|
+
let money2 = {};
|
|
391
301
|
try {
|
|
392
|
-
|
|
302
|
+
money2 = Object.prototype.hasOwnProperty.call(MONEY, command) ? MONEY[command](opts, args) : {};
|
|
393
303
|
} catch {
|
|
394
|
-
|
|
304
|
+
money2 = {};
|
|
395
305
|
}
|
|
396
|
-
pending = { command, money, settings, version, intentWritten: false, written: false };
|
|
306
|
+
pending = { command, money: money2, settings, version, intentWritten: false, written: false };
|
|
397
307
|
if (typeof opts.apiKey === "string") pending.apiKey = opts.apiKey;
|
|
398
308
|
hookProcess();
|
|
399
309
|
}
|
|
@@ -415,7 +325,7 @@ function auditIntent(now = /* @__PURE__ */ new Date()) {
|
|
|
415
325
|
const p = pending;
|
|
416
326
|
if (!p || p.intentWritten || p.settings.dir === null || !Object.prototype.hasOwnProperty.call(MONEY, p.command)) return;
|
|
417
327
|
p.intentWritten = true;
|
|
418
|
-
writeAuditLine(p.settings.dir, { command: p.command, ts: now.toISOString(), surface: "cli", version: p.version, ...
|
|
328
|
+
writeAuditLine(p.settings.dir, { command: p.command, ts: now.toISOString(), surface: "cli", version: p.version, ...auditKeyFields(p.apiKey), outcome: "pending", ...p.money }, now);
|
|
419
329
|
}
|
|
420
330
|
function auditFinish(exitCode, now = /* @__PURE__ */ new Date()) {
|
|
421
331
|
const p = pending;
|
|
@@ -438,7 +348,7 @@ function auditFinish(exitCode, now = /* @__PURE__ */ new Date()) {
|
|
|
438
348
|
ts: now.toISOString(),
|
|
439
349
|
surface: "cli",
|
|
440
350
|
version: p.version,
|
|
441
|
-
...
|
|
351
|
+
...auditKeyFields(p.apiKey),
|
|
442
352
|
outcome,
|
|
443
353
|
...code ? { error_code: code } : {},
|
|
444
354
|
...p.requestId ? { request_id: p.requestId } : {},
|
|
@@ -484,9 +394,15 @@ function auditUsageError(command, version, commanderCode, exitCode, now = /* @__
|
|
|
484
394
|
void err;
|
|
485
395
|
}
|
|
486
396
|
}
|
|
397
|
+
function _resetAuditForTests() {
|
|
398
|
+
pending = null;
|
|
399
|
+
for (const [event, h] of processHandlers.splice(0)) process.removeListener(event, h);
|
|
400
|
+
hooked = false;
|
|
401
|
+
signalsHandledByCommand = false;
|
|
402
|
+
}
|
|
487
403
|
|
|
488
404
|
// src/config.ts
|
|
489
|
-
var CONFIG_DIR = join2(
|
|
405
|
+
var CONFIG_DIR = join2(homedir(), ".vonpay");
|
|
490
406
|
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
491
407
|
function loadConfig() {
|
|
492
408
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
@@ -582,10 +498,10 @@ function evaluateKeyToSave(key) {
|
|
|
582
498
|
}
|
|
583
499
|
function readLine(prompt) {
|
|
584
500
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
585
|
-
return new Promise((
|
|
501
|
+
return new Promise((resolve2) => {
|
|
586
502
|
rl.question(prompt, (answer) => {
|
|
587
503
|
rl.close();
|
|
588
|
-
|
|
504
|
+
resolve2(answer.trim());
|
|
589
505
|
});
|
|
590
506
|
});
|
|
591
507
|
}
|
|
@@ -659,7 +575,7 @@ function logoutCommand() {
|
|
|
659
575
|
// src/commands/init.ts
|
|
660
576
|
import { Command as Command3 } from "commander";
|
|
661
577
|
import { appendFileSync, chmodSync as chmodSync3, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
662
|
-
import { resolve
|
|
578
|
+
import { resolve } from "path";
|
|
663
579
|
import chalk3 from "chalk";
|
|
664
580
|
var LIVE_KEY_NOT_WRITTEN_MESSAGE = "Refusing to write a live key (vp_sk_live_\u2026) to .env: live keys are never written to disk. Run `init` with a sandbox key (vp_sk_test_\u2026), and give your app its live key through your deployment's secret store or the environment when you go live.";
|
|
665
581
|
function ensureGitignoreBlocksEnv() {
|
|
@@ -710,7 +626,7 @@ function initCommand() {
|
|
|
710
626
|
process.exit(1);
|
|
711
627
|
}
|
|
712
628
|
const envFile = ".env";
|
|
713
|
-
const absPath =
|
|
629
|
+
const absPath = resolve(envFile);
|
|
714
630
|
if (existsSync2(envFile) && !opts.force) {
|
|
715
631
|
console.error(
|
|
716
632
|
chalk3.yellow(
|
|
@@ -846,13 +762,14 @@ function scrubSecrets(s) {
|
|
|
846
762
|
}
|
|
847
763
|
|
|
848
764
|
// src/utils/sanitize.ts
|
|
849
|
-
import { stripHiddenCharacters
|
|
765
|
+
import { stripHiddenCharacters } from "@vonpay/checkout-node";
|
|
850
766
|
var ANSI_PATTERN = /\x1B\[[0-9;?]*[A-Za-z]/g;
|
|
851
767
|
var C1_CSI_PATTERN = /\u{9B}[0-9;?]*[A-Za-z]/gu;
|
|
852
768
|
var CONTROL_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\u{80}-\u{9F}]/gu;
|
|
853
769
|
var LINE_SEPARATOR_PATTERN = /[\u{2028}\u{2029}]/gu;
|
|
770
|
+
var PLAIN_TEXT_PATTERN = /^[\x20-\x7e]*$/;
|
|
854
771
|
function stripAnsi(s) {
|
|
855
|
-
return
|
|
772
|
+
return stripHiddenCharacters(s.replace(ANSI_PATTERN, "").replace(C1_CSI_PATTERN, "").replace(CONTROL_PATTERN, ""));
|
|
856
773
|
}
|
|
857
774
|
function truncate(s, max) {
|
|
858
775
|
if (s.length <= max) return s;
|
|
@@ -864,13 +781,50 @@ function safeDisplay(s, max = 200) {
|
|
|
864
781
|
function safeInline(s, max = 200) {
|
|
865
782
|
return safeDisplay(s.replace(/[\r\n]+/g, " ").replace(LINE_SEPARATOR_PATTERN, " "), max);
|
|
866
783
|
}
|
|
784
|
+
var READ_SECRET_PATTERNS = SECRET_PATTERNS.map(
|
|
785
|
+
([re, replacement]) => [new RegExp(`(?<![A-Za-z0-9])(?:${re.source})`, re.flags), replacement]
|
|
786
|
+
);
|
|
787
|
+
function redactSecretShapes(s) {
|
|
788
|
+
let out = s;
|
|
789
|
+
for (const [re, replacement] of READ_SECRET_PATTERNS) out = out.replace(re, replacement);
|
|
790
|
+
return out;
|
|
791
|
+
}
|
|
792
|
+
function uniqueKey(cleaned, taken) {
|
|
793
|
+
let key = cleaned;
|
|
794
|
+
for (let n = 2; taken.has(key); n++) key = `${cleaned}#${n}`;
|
|
795
|
+
taken.add(key);
|
|
796
|
+
return key;
|
|
797
|
+
}
|
|
798
|
+
function cleanJsonValue(value, options = {}) {
|
|
799
|
+
const { lines = "keep", maxChars = Number.MAX_SAFE_INTEGER, redact = redactSecretShapes, exactKeys } = options;
|
|
800
|
+
const text = (s) => {
|
|
801
|
+
if (PLAIN_TEXT_PATTERN.test(s)) return truncate(s, maxChars);
|
|
802
|
+
return lines === "join" ? safeInline(s, maxChars) : truncate(stripAnsi(s.replace(LINE_SEPARATOR_PATTERN, " ")), maxChars);
|
|
803
|
+
};
|
|
804
|
+
const walk = (v, exact) => {
|
|
805
|
+
if (typeof v === "string") return exact ? text(v) : redact(text(v));
|
|
806
|
+
if (v === null || typeof v !== "object") return v;
|
|
807
|
+
const toJSON = v.toJSON;
|
|
808
|
+
if (typeof toJSON === "function") return walk(toJSON.call(v), exact);
|
|
809
|
+
if (Array.isArray(v)) return v.map((item) => walk(item, exact));
|
|
810
|
+
const taken = /* @__PURE__ */ new Set();
|
|
811
|
+
return Object.fromEntries(
|
|
812
|
+
Object.entries(v).map(([k, item]) => [uniqueKey(text(k), taken), walk(item, exact || exactKeys?.has(k) === true)])
|
|
813
|
+
);
|
|
814
|
+
};
|
|
815
|
+
return walk(value, false);
|
|
816
|
+
}
|
|
817
|
+
function jsonOutput(value, options) {
|
|
818
|
+
return JSON.stringify(cleanJsonValue(value, options), null, 2);
|
|
819
|
+
}
|
|
867
820
|
|
|
868
821
|
// src/utils/errors.ts
|
|
869
822
|
var CLI_READ_BACK = "From this CLI: `vonpay checkout payment-intents get <id>` (charge, capture, void), `vonpay checkout refunds list --payment-intent <id>` or `--transaction <id>` (refund), `sessions get <id>` (checkout session).";
|
|
870
823
|
var READ_BACK_FIRST = "Check the payment first: read it back (`vonpay checkout payment-intents get <id>` for a charge, capture or void; `vonpay checkout refunds list --payment-intent <id>` (or `--transaction <id>`) for a refund; `sessions get <id>` for a checkout session) and act only on what it shows.";
|
|
871
824
|
var UNKNOWN_OUTCOME_GUIDANCE = `${READ_BACK_FIRST} If you then retry, reuse the SAME --idempotency-key as the first attempt, never a new one: a new key is a new action and can move money twice.`;
|
|
872
825
|
var str = (v) => v === void 0 || v === null ? null : String(v);
|
|
873
|
-
var
|
|
826
|
+
var ONE_LINE_MAX_CHARS = 2e3;
|
|
827
|
+
var oneLine = (s) => safeInline(s, ONE_LINE_MAX_CHARS);
|
|
874
828
|
function paramFlagHint(param) {
|
|
875
829
|
const flag = param.startsWith("metadata") ? "--metadata" : `--${param.replace(/\[(gte|lt)\]$/, "-$1").replace(/_/g, "-")}`;
|
|
876
830
|
return /^--[a-z-]+$/.test(flag) ? ` (the ${flag} flag)` : "";
|
|
@@ -897,6 +851,7 @@ var FIELD_PRINTERS = {
|
|
|
897
851
|
currentStatus: (e) => e.currentStatus ? `Current status: ${str(e.currentStatus)}` : null,
|
|
898
852
|
rejectReason: (e) => e.rejectReason ? `Reject reason: ${str(e.rejectReason)}` : null,
|
|
899
853
|
paymentIntent: (e) => e.paymentIntent ? `Payment intent: ${str(e.paymentIntent)}` : null,
|
|
854
|
+
paymentIntentStatus: (e) => e.paymentIntentStatus ? `Payment intent status: ${str(e.paymentIntentStatus)}` : null,
|
|
900
855
|
// refund_target_is_duplicate: the SDK has already checked this is a
|
|
901
856
|
// transaction id (vp_tx_*), so it is safe to echo as a command.
|
|
902
857
|
refundInstead: (e) => e.refundInstead ? `Refund instead: vonpay checkout refunds create --transaction ${str(e.refundInstead)}` : null,
|
|
@@ -970,16 +925,7 @@ function errorDetailLines(err) {
|
|
|
970
925
|
}
|
|
971
926
|
return lines;
|
|
972
927
|
}
|
|
973
|
-
|
|
974
|
-
if (typeof value === "string") return scrubSecrets(oneLine(value));
|
|
975
|
-
if (Array.isArray(value)) return value.map(scrubDeep);
|
|
976
|
-
if (value !== null && typeof value === "object") {
|
|
977
|
-
const out = {};
|
|
978
|
-
for (const [k, v] of Object.entries(value)) out[k] = scrubDeep(v);
|
|
979
|
-
return out;
|
|
980
|
-
}
|
|
981
|
-
return value;
|
|
982
|
-
}
|
|
928
|
+
var ERROR_JSON_CLEANING = { lines: "join", maxChars: ONE_LINE_MAX_CHARS, redact: scrubSecrets };
|
|
983
929
|
function errorJson(err) {
|
|
984
930
|
if (err instanceof VonPayError) {
|
|
985
931
|
const out = { error: err.message };
|
|
@@ -988,14 +934,14 @@ function errorJson(err) {
|
|
|
988
934
|
const value = source[field];
|
|
989
935
|
if (value !== void 0) out[field] = value;
|
|
990
936
|
}
|
|
991
|
-
return
|
|
937
|
+
return cleanJsonValue(out, ERROR_JSON_CLEANING);
|
|
992
938
|
}
|
|
993
939
|
const message = err instanceof Error ? err.message : String(err);
|
|
994
940
|
return { error: scrubSecrets(oneLine(message)), code: null };
|
|
995
941
|
}
|
|
996
942
|
function exitWithCliError(message, text) {
|
|
997
943
|
auditNote({ outcome: "refused_local", code: "local_refusal" }, true);
|
|
998
|
-
return emitCliError({ error:
|
|
944
|
+
return emitCliError({ error: cleanJsonValue(message, ERROR_JSON_CLEANING), code: null }, text);
|
|
999
945
|
}
|
|
1000
946
|
function handleError(err) {
|
|
1001
947
|
auditError(err);
|
|
@@ -1074,7 +1020,7 @@ function makeCheckoutClient() {
|
|
|
1074
1020
|
import { createInterface as createInterface2 } from "readline";
|
|
1075
1021
|
import chalk5 from "chalk";
|
|
1076
1022
|
import { Option as Option2 } from "commander";
|
|
1077
|
-
import { isLiveKey
|
|
1023
|
+
import { heldAsLive, isLiveKey } from "@vonpay/checkout-node";
|
|
1078
1024
|
var CONFIRM_WORD = "confirm";
|
|
1079
1025
|
var terminal = {
|
|
1080
1026
|
/**
|
|
@@ -1086,27 +1032,27 @@ var terminal = {
|
|
|
1086
1032
|
},
|
|
1087
1033
|
/** Ask one line on the terminal. End of input or Ctrl-C answers "" (cancel). */
|
|
1088
1034
|
ask(prompt) {
|
|
1089
|
-
return new Promise((
|
|
1035
|
+
return new Promise((resolve2) => {
|
|
1090
1036
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1091
1037
|
let answered = false;
|
|
1092
1038
|
rl.on("SIGINT", () => rl.close());
|
|
1093
1039
|
rl.on("close", () => {
|
|
1094
|
-
if (!answered)
|
|
1040
|
+
if (!answered) resolve2("");
|
|
1095
1041
|
});
|
|
1096
1042
|
rl.question(prompt, (answer) => {
|
|
1097
1043
|
answered = true;
|
|
1098
1044
|
rl.close();
|
|
1099
|
-
|
|
1045
|
+
resolve2(answer);
|
|
1100
1046
|
});
|
|
1101
1047
|
});
|
|
1102
1048
|
}
|
|
1103
1049
|
};
|
|
1104
|
-
function
|
|
1050
|
+
function keyFingerprint(key) {
|
|
1105
1051
|
const m = key.match(/^(vp_sk_live_)([A-Za-z0-9_-]{0,4})/);
|
|
1106
1052
|
return m ? `${m[1]}${m[2]}\u2026` : "a key that is not a sandbox key";
|
|
1107
1053
|
}
|
|
1108
1054
|
async function confirmLiveAction(key, summary, opts = {}) {
|
|
1109
|
-
if (!
|
|
1055
|
+
if (!heldAsLive(key)) return;
|
|
1110
1056
|
if (!terminal.isInteractive()) {
|
|
1111
1057
|
const message = `Refusing to ${summary.action} with a live key: no person is at an interactive terminal to confirm it. A live-key action must be confirmed by a person typing at a terminal (stdin and stdout both a terminal); scripts, CI jobs and AI agents cannot confirm it, and --confirm-live is no longer enough. Use a sandbox key (vp_sk_test_\u2026) for automation, or run the command yourself in a terminal. Nothing was sent.`;
|
|
1112
1058
|
auditNote({ outcome: "withheld", code: "no_terminal" });
|
|
@@ -1120,7 +1066,7 @@ async function confirmLiveAction(key, summary, opts = {}) {
|
|
|
1120
1066
|
);
|
|
1121
1067
|
}
|
|
1122
1068
|
const lines = [
|
|
1123
|
-
chalk5.bold.red(`You are about to ${summary.action} with a LIVE key (${
|
|
1069
|
+
chalk5.bold.red(`You are about to ${summary.action} with a LIVE key (${keyFingerprint(key)}).`),
|
|
1124
1070
|
// At least one space after the label, however long it is.
|
|
1125
1071
|
...(summary.details ?? []).map(([label, value]) => ` ${`${label}:`.padEnd(Math.max(17, label.length + 2))}${safeInline(value)}`),
|
|
1126
1072
|
chalk5.yellow(summary.consequence ?? "This moves real money and cannot be undone from here.")
|
|
@@ -1141,7 +1087,7 @@ function retiredConfirmLiveOption() {
|
|
|
1141
1087
|
).hideHelp();
|
|
1142
1088
|
}
|
|
1143
1089
|
function warnLiveSecretFlag(key, flag) {
|
|
1144
|
-
if (!
|
|
1090
|
+
if (!heldAsLive(key)) return;
|
|
1145
1091
|
console.warn(
|
|
1146
1092
|
chalk5.yellow(
|
|
1147
1093
|
`Warning: passing ${flag} as a command argument with a live key exposes it to shell history and process-list (\`ps\`). Consider piping or env-injecting the value instead.`
|
|
@@ -1162,6 +1108,11 @@ function parseMinorUnits(raw, flag) {
|
|
|
1162
1108
|
}
|
|
1163
1109
|
return { ok: true, value };
|
|
1164
1110
|
}
|
|
1111
|
+
function positiveWholeNumber(raw, flag) {
|
|
1112
|
+
const parsed = parseMinorUnits(raw, flag);
|
|
1113
|
+
if (!parsed.ok || parsed.value < 1) throw new Error(`${flag} must be a whole number of at least 1; got ${JSON.stringify(raw)}.`);
|
|
1114
|
+
return parsed.value;
|
|
1115
|
+
}
|
|
1165
1116
|
function minorUnitsOrExit(raw, flag) {
|
|
1166
1117
|
const parsed = parseMinorUnits(raw, flag);
|
|
1167
1118
|
if (!parsed.ok) exitWithCliError(parsed.message, `Error: ${parsed.message}`);
|
|
@@ -1172,9 +1123,7 @@ function optionalMinorUnitsOrExit(raw, flag) {
|
|
|
1172
1123
|
}
|
|
1173
1124
|
|
|
1174
1125
|
// src/utils/mask-secrets.ts
|
|
1175
|
-
|
|
1176
|
-
return scrubSecrets(json);
|
|
1177
|
-
}
|
|
1126
|
+
var SHOW_SECRETS_CLEANING = { exactKeys: /* @__PURE__ */ new Set(["clientSecret"]) };
|
|
1178
1127
|
var SHOW_SECRETS_HELP = "With --json, print the session's client secret in full. \u26A0 The output then contains a secret: do not paste it or let it reach a log";
|
|
1179
1128
|
function withClientSecret(session) {
|
|
1180
1129
|
if (session === null || typeof session !== "object" || Array.isArray(session)) return session;
|
|
@@ -1182,9 +1131,9 @@ function withClientSecret(session) {
|
|
|
1182
1131
|
return { ...session, clientSecret: session.clientSecret };
|
|
1183
1132
|
}
|
|
1184
1133
|
function sessionJson(session, showSecrets) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
return
|
|
1134
|
+
const body = cleanJsonValue(withClientSecret(session), showSecrets === true ? SHOW_SECRETS_CLEANING : {});
|
|
1135
|
+
const json = JSON.stringify(body, null, 2);
|
|
1136
|
+
return json === void 0 ? String(json) : json;
|
|
1188
1137
|
}
|
|
1189
1138
|
|
|
1190
1139
|
// src/commands/sessions.ts
|
|
@@ -1284,7 +1233,7 @@ function sessionsCommand() {
|
|
|
1284
1233
|
const result = await client.sessions.validate(params);
|
|
1285
1234
|
spinner.stop();
|
|
1286
1235
|
if (opts.json) {
|
|
1287
|
-
console.log(
|
|
1236
|
+
console.log(jsonOutput(result));
|
|
1288
1237
|
} else if (result.valid) {
|
|
1289
1238
|
console.log(chalk6.green("\u2713 Valid request"));
|
|
1290
1239
|
if (result.warnings.length > 0) {
|
|
@@ -1342,6 +1291,7 @@ import { formatMinorUnits as formatMinorUnits3 } from "@vonpay/checkout-node";
|
|
|
1342
1291
|
// src/utils/idempotency.ts
|
|
1343
1292
|
import { randomUUID } from "crypto";
|
|
1344
1293
|
import chalk7 from "chalk";
|
|
1294
|
+
import { heldAsLive as heldAsLive2 } from "@vonpay/checkout-node";
|
|
1345
1295
|
function resolveIdempotencyKey(flag) {
|
|
1346
1296
|
const given = flag?.trim();
|
|
1347
1297
|
if (given) return { key: given, generated: false };
|
|
@@ -1349,7 +1299,7 @@ function resolveIdempotencyKey(flag) {
|
|
|
1349
1299
|
}
|
|
1350
1300
|
function idempotencyKeyFor(flag, what, apiKey) {
|
|
1351
1301
|
const { key, generated } = resolveIdempotencyKey(flag);
|
|
1352
|
-
if (generated && apiKey !== void 0 &&
|
|
1302
|
+
if (generated && apiKey !== void 0 && heldAsLive2(apiKey)) {
|
|
1353
1303
|
const message = `Refusing to send this ${what} with a live key without --idempotency-key. Choose one key for this ${what} (for example your order id plus the action, such as "order_1042:${what}") and pass the SAME key if you ever run the command again: a new key is a new action and can move money twice. Nothing was sent.`;
|
|
1354
1304
|
auditNote({ outcome: "refused_local", code: "idempotency_key_required" });
|
|
1355
1305
|
exitWithCliError(message, chalk7.red(message));
|
|
@@ -1465,11 +1415,6 @@ function buildFilters(opts) {
|
|
|
1465
1415
|
}
|
|
1466
1416
|
return filters;
|
|
1467
1417
|
}
|
|
1468
|
-
function wholeNumber2(raw, flag) {
|
|
1469
|
-
const parsed = parseMinorUnits(raw, flag);
|
|
1470
|
-
if (!parsed.ok || parsed.value < 1) throw new Error(`${flag} must be a whole number of at least 1; got ${JSON.stringify(raw)}.`);
|
|
1471
|
-
return parsed.value;
|
|
1472
|
-
}
|
|
1473
1418
|
var PAYMENT_LIST_JSON_FIELDS = [
|
|
1474
1419
|
"id",
|
|
1475
1420
|
"status",
|
|
@@ -1492,17 +1437,20 @@ function listItemJson(pi, includeMetadata) {
|
|
|
1492
1437
|
const src = pi;
|
|
1493
1438
|
return Object.fromEntries(PAYMENT_LIST_JSON_FIELDS.filter((k) => src[k] !== void 0).map((k) => [k, src[k]]));
|
|
1494
1439
|
}
|
|
1495
|
-
var
|
|
1440
|
+
var ALL_DEFAULT_MAX_ITEMS = 1e4;
|
|
1441
|
+
function stoppedAtDefaultCap() {
|
|
1442
|
+
return `Stopped at ${ALL_DEFAULT_MAX_ITEMS} payments (the --all default); there may be more. Pass --max-items to read more, or narrow the window.`;
|
|
1443
|
+
}
|
|
1496
1444
|
function buildListParams(opts) {
|
|
1497
1445
|
const params = buildFilters(opts);
|
|
1498
1446
|
if (opts.status !== void 0) {
|
|
1499
1447
|
params.status = parseListFlag(opts.status, "--status", { lowercase: true });
|
|
1500
1448
|
}
|
|
1501
|
-
if (opts.limit !== void 0) params.limit =
|
|
1449
|
+
if (opts.limit !== void 0) params.limit = positiveWholeNumber(opts.limit, "--limit");
|
|
1502
1450
|
if (opts.startingAfter !== void 0) params.startingAfter = opts.startingAfter;
|
|
1503
1451
|
if (opts.maxItems !== void 0 && !opts.all) throw new Error("--max-items is used with --all.");
|
|
1504
1452
|
if (opts.includeMetadata && !opts.json) throw new Error("--include-metadata is used with --json.");
|
|
1505
|
-
const maxItems = opts.maxItems !== void 0 ?
|
|
1453
|
+
const maxItems = opts.maxItems !== void 0 ? positiveWholeNumber(opts.maxItems, "--max-items") : opts.all ? ALL_DEFAULT_MAX_ITEMS : void 0;
|
|
1506
1454
|
return { params, ...maxItems !== void 0 ? { maxItems } : {} };
|
|
1507
1455
|
}
|
|
1508
1456
|
function buildSummaryParams(opts) {
|
|
@@ -1587,7 +1535,10 @@ function addPaymentReportCommands(intents) {
|
|
|
1587
1535
|
).option("--status <list>", "Only these statuses, comma-separated (e.g. failed,succeeded)").option("--limit <n>", "Page size, up to 100 per page (default 25; larger values are treated as 100)").option(
|
|
1588
1536
|
"--starting-after <cursor>",
|
|
1589
1537
|
"The next-page cursor printed by a previous run, unchanged, with exactly the same filters and window (it lasts 24 hours)"
|
|
1590
|
-
).option(
|
|
1538
|
+
).option(
|
|
1539
|
+
"--all",
|
|
1540
|
+
`Read every page, one request at a time, up to ${ALL_DEFAULT_MAX_ITEMS} payments unless --max-items says otherwise (text: printed as each payment arrives)`
|
|
1541
|
+
).option("--max-items <n>", `With --all: stop after this many payments (default ${ALL_DEFAULT_MAX_ITEMS})`).option(
|
|
1591
1542
|
"--json",
|
|
1592
1543
|
"Output JSON: each payment's status, outcome, amount, card, store and decline fields \u2014 not your metadata (with --all: one JSON array)"
|
|
1593
1544
|
).option("--include-metadata", "With --json: print every field of each payment, your metadata included").action(async (opts) => {
|
|
@@ -1609,12 +1560,8 @@ function addPaymentReportCommands(intents) {
|
|
|
1609
1560
|
const items = [];
|
|
1610
1561
|
for await (const pi of pager) items.push(listItemJson(pi, opts.includeMetadata));
|
|
1611
1562
|
spinner.stop();
|
|
1612
|
-
console.log(
|
|
1613
|
-
if (opts.maxItems === void 0 && items.length >=
|
|
1614
|
-
console.error(
|
|
1615
|
-
`Stopped at ${ALL_JSON_DEFAULT_MAX_ITEMS} payments (the --all --json default). Pass --max-items to read more, or narrow the window.`
|
|
1616
|
-
);
|
|
1617
|
-
}
|
|
1563
|
+
console.log(jsonOutput(items));
|
|
1564
|
+
if (opts.maxItems === void 0 && items.length >= ALL_DEFAULT_MAX_ITEMS) console.error(stoppedAtDefaultCap());
|
|
1618
1565
|
return;
|
|
1619
1566
|
}
|
|
1620
1567
|
let count = 0;
|
|
@@ -1626,12 +1573,13 @@ function addPaymentReportCommands(intents) {
|
|
|
1626
1573
|
spinner.stop();
|
|
1627
1574
|
if (count === 0) console.log(" No payments match.");
|
|
1628
1575
|
console.log(` ${count} payment(s).`);
|
|
1576
|
+
if (opts.maxItems === void 0 && count >= ALL_DEFAULT_MAX_ITEMS) console.error(stoppedAtDefaultCap());
|
|
1629
1577
|
return;
|
|
1630
1578
|
}
|
|
1631
1579
|
const page = await client.paymentIntents.list(built.params);
|
|
1632
1580
|
spinner.stop();
|
|
1633
1581
|
if (opts.json) {
|
|
1634
|
-
console.log(
|
|
1582
|
+
console.log(jsonOutput({ ...page, data: page.data.map((pi) => listItemJson(pi, opts.includeMetadata)) }));
|
|
1635
1583
|
return;
|
|
1636
1584
|
}
|
|
1637
1585
|
for (const line of paymentListLines(page)) console.log(line);
|
|
@@ -1660,7 +1608,7 @@ function addPaymentReportCommands(intents) {
|
|
|
1660
1608
|
const summary = await client.paymentIntents.summary(params);
|
|
1661
1609
|
spinner.stop();
|
|
1662
1610
|
if (opts.json) {
|
|
1663
|
-
console.log(
|
|
1611
|
+
console.log(jsonOutput(summary));
|
|
1664
1612
|
return;
|
|
1665
1613
|
}
|
|
1666
1614
|
for (const line of summaryLines(summary)) console.log(line);
|
|
@@ -1849,7 +1797,7 @@ function paymentIntentsCommand() {
|
|
|
1849
1797
|
});
|
|
1850
1798
|
spinner.stop();
|
|
1851
1799
|
if (opts.json) {
|
|
1852
|
-
console.log(
|
|
1800
|
+
console.log(jsonOutput(intent));
|
|
1853
1801
|
} else {
|
|
1854
1802
|
console.log(chalk9.green("\u2713 Payment intent created"));
|
|
1855
1803
|
printPaymentIntent(intent);
|
|
@@ -1868,7 +1816,7 @@ function paymentIntentsCommand() {
|
|
|
1868
1816
|
const intent = await client.paymentIntents.retrieve(id);
|
|
1869
1817
|
spinner.stop();
|
|
1870
1818
|
if (opts.json) {
|
|
1871
|
-
console.log(
|
|
1819
|
+
console.log(jsonOutput(intent));
|
|
1872
1820
|
} else {
|
|
1873
1821
|
printPaymentIntent(intent);
|
|
1874
1822
|
}
|
|
@@ -1918,7 +1866,7 @@ function paymentIntentsCommand() {
|
|
|
1918
1866
|
);
|
|
1919
1867
|
spinner.stop();
|
|
1920
1868
|
if (opts.json) {
|
|
1921
|
-
console.log(
|
|
1869
|
+
console.log(jsonOutput(intent));
|
|
1922
1870
|
} else {
|
|
1923
1871
|
const captureLabel = amountToCapture !== void 0 ? ` (partial: ${formatMinorUnits3(amountToCapture, intent.currency)})` : "";
|
|
1924
1872
|
console.log(chalk9.green(`\u2713 Payment intent captured${captureLabel}`));
|
|
@@ -1957,7 +1905,7 @@ function paymentIntentsCommand() {
|
|
|
1957
1905
|
const intent = await client.paymentIntents.void(id, { idempotencyKey });
|
|
1958
1906
|
spinner.stop();
|
|
1959
1907
|
if (opts.json) {
|
|
1960
|
-
console.log(
|
|
1908
|
+
console.log(jsonOutput(intent));
|
|
1961
1909
|
} else {
|
|
1962
1910
|
console.log(chalk9.green("\u2713 Payment intent voided"));
|
|
1963
1911
|
printPaymentIntent(intent);
|
|
@@ -1975,7 +1923,7 @@ function paymentIntentsCommand() {
|
|
|
1975
1923
|
// src/commands/refunds.ts
|
|
1976
1924
|
import { Command as Command6 } from "commander";
|
|
1977
1925
|
import chalk10 from "chalk";
|
|
1978
|
-
import { formatMinorUnits as formatMinorUnits4, VonPayError as VonPayError2 } from "@vonpay/checkout-node";
|
|
1926
|
+
import { formatMinorUnits as formatMinorUnits4, heldAsLive as heldAsLive3, VonPayError as VonPayError2 } from "@vonpay/checkout-node";
|
|
1979
1927
|
function present(v) {
|
|
1980
1928
|
return v !== void 0 && v.trim() !== "";
|
|
1981
1929
|
}
|
|
@@ -2137,11 +2085,11 @@ function refundsCommand() {
|
|
|
2137
2085
|
handleError(err);
|
|
2138
2086
|
}
|
|
2139
2087
|
const target = resolveRefundTarget(opts);
|
|
2140
|
-
const sessionProblem = refundSessionProblem(target, opts.sessionId,
|
|
2088
|
+
const sessionProblem = refundSessionProblem(target, opts.sessionId, heldAsLive3(apiKey));
|
|
2141
2089
|
if (sessionProblem) exitWithCliError(sessionProblem, chalk10.red(sessionProblem));
|
|
2142
2090
|
refuseUnsafeLiveDestination(apiKey);
|
|
2143
2091
|
const idempotencyKey = idempotencyKeyFor(opts.idempotencyKey, "refund", apiKey);
|
|
2144
|
-
const lookedUp =
|
|
2092
|
+
const lookedUp = heldAsLive3(apiKey) && terminal.isInteractive() ? await refundLookupDetails(makeCheckoutClient(), target, opts.sessionId?.trim(), params.amount, params.currency) : [];
|
|
2145
2093
|
await confirmLiveAction(
|
|
2146
2094
|
apiKey,
|
|
2147
2095
|
{
|
|
@@ -2166,7 +2114,7 @@ function refundsCommand() {
|
|
|
2166
2114
|
const refund = await client.refunds.create(params, { idempotencyKey });
|
|
2167
2115
|
spinner.stop();
|
|
2168
2116
|
if (opts.json) {
|
|
2169
|
-
console.log(
|
|
2117
|
+
console.log(jsonOutput(refund));
|
|
2170
2118
|
} else {
|
|
2171
2119
|
console.log(chalk10.green("\u2713 Refund created"));
|
|
2172
2120
|
printRefund(refund);
|
|
@@ -2178,7 +2126,7 @@ function refundsCommand() {
|
|
|
2178
2126
|
});
|
|
2179
2127
|
refunds.command("list").description(
|
|
2180
2128
|
"List the refunds of ONE payment and its refund totals \u2014 the way to check a refund whose outcome was unclear"
|
|
2181
|
-
).option("--payment-intent <id>", "The payment's payment intent ID (vpi_*). Use this OR --transaction").option("--transaction <id>", "The payment's transaction ID (vp_tx_*), instead of --payment-intent").option("--limit <n>", "How many refunds to show (default 25
|
|
2129
|
+
).option("--payment-intent <id>", "The payment's payment intent ID (vpi_*). Use this OR --transaction").option("--transaction <id>", "The payment's transaction ID (vp_tx_*), instead of --payment-intent").option("--limit <n>", "How many refunds to show (default 25; the API allows at most 100)").option("--starting-after <cursor>", "The next-page cursor printed by a previous run").option("--json", "Output raw JSON").action(
|
|
2182
2130
|
async (opts) => {
|
|
2183
2131
|
let target;
|
|
2184
2132
|
try {
|
|
@@ -2189,12 +2137,12 @@ function refundsCommand() {
|
|
|
2189
2137
|
}
|
|
2190
2138
|
let limit;
|
|
2191
2139
|
if (opts.limit !== void 0) {
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2140
|
+
try {
|
|
2141
|
+
limit = positiveWholeNumber(opts.limit, "--limit");
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
const message = err.message;
|
|
2195
2144
|
exitWithCliError(message, chalk10.red(`Error: ${message}`));
|
|
2196
2145
|
}
|
|
2197
|
-
limit = parsed.value;
|
|
2198
2146
|
}
|
|
2199
2147
|
const client = makeCheckoutClient();
|
|
2200
2148
|
const spinner = startSpinner("Listing refunds...");
|
|
@@ -2205,7 +2153,7 @@ function refundsCommand() {
|
|
|
2205
2153
|
});
|
|
2206
2154
|
spinner.stop();
|
|
2207
2155
|
if (opts.json) {
|
|
2208
|
-
console.log(
|
|
2156
|
+
console.log(jsonOutput(list));
|
|
2209
2157
|
return;
|
|
2210
2158
|
}
|
|
2211
2159
|
for (const line of refundListLines(list)) console.log(line);
|
|
@@ -2219,10 +2167,10 @@ function refundsCommand() {
|
|
|
2219
2167
|
}
|
|
2220
2168
|
function refundListLines(list) {
|
|
2221
2169
|
const currency = list.data[0]?.currency;
|
|
2222
|
-
const
|
|
2170
|
+
const money2 = (n) => currency ? formatMinorUnits4(n, currency) : `${n} in minor units`;
|
|
2223
2171
|
const lines = [
|
|
2224
|
-
` Refunded so far: ${
|
|
2225
|
-
` Still refundable: ${
|
|
2172
|
+
` Refunded so far: ${money2(list.amountRefunded)} (succeeded + still in progress)`,
|
|
2173
|
+
` Still refundable: ${money2(list.remainingRefundable)}${list.refundable ? "" : " \u2014 this payment cannot be refunded now"}`
|
|
2226
2174
|
];
|
|
2227
2175
|
if (list.data.length === 0) {
|
|
2228
2176
|
lines.push(" No refunds on this payment.");
|
|
@@ -2292,7 +2240,7 @@ function tokensCommand() {
|
|
|
2292
2240
|
);
|
|
2293
2241
|
spinner.stop();
|
|
2294
2242
|
if (opts.json) {
|
|
2295
|
-
console.log(
|
|
2243
|
+
console.log(jsonOutput(token));
|
|
2296
2244
|
} else {
|
|
2297
2245
|
console.log(chalk11.green("\u2713 Token created"));
|
|
2298
2246
|
printToken(token);
|
|
@@ -2311,35 +2259,166 @@ function formatTokenCard(card) {
|
|
|
2311
2259
|
return { card: cardLine, expires };
|
|
2312
2260
|
}
|
|
2313
2261
|
|
|
2314
|
-
// src/commands/
|
|
2262
|
+
// src/commands/subscription-products.ts
|
|
2315
2263
|
import { Command as Command8 } from "commander";
|
|
2316
2264
|
import chalk12 from "chalk";
|
|
2265
|
+
import { formatMinorUnits as formatMinorUnits5 } from "@vonpay/checkout-node";
|
|
2266
|
+
var STATUSES = ["active", "archived"];
|
|
2267
|
+
function buildListParams2(opts) {
|
|
2268
|
+
const params = {};
|
|
2269
|
+
if (opts.productRef !== void 0) params.productRef = opts.productRef;
|
|
2270
|
+
if (opts.status !== void 0) {
|
|
2271
|
+
const status = opts.status.trim().toLowerCase();
|
|
2272
|
+
if (!STATUSES.includes(status)) {
|
|
2273
|
+
throw new Error(`--status must be active or archived; got ${JSON.stringify(opts.status)}.`);
|
|
2274
|
+
}
|
|
2275
|
+
params.status = status;
|
|
2276
|
+
}
|
|
2277
|
+
if (opts.limit !== void 0) params.limit = positiveWholeNumber(opts.limit, "--limit");
|
|
2278
|
+
if (opts.startingAfter !== void 0) params.startingAfter = opts.startingAfter;
|
|
2279
|
+
return params;
|
|
2280
|
+
}
|
|
2281
|
+
function buildTermsParams(opts) {
|
|
2282
|
+
if (opts.amount === void 0) return void 0;
|
|
2283
|
+
const parsed = parseMinorUnits(opts.amount, "--amount");
|
|
2284
|
+
if (!parsed.ok) throw new Error(parsed.message);
|
|
2285
|
+
if (parsed.value < 1) throw new Error(`--amount must be at least 1 (minor units); got ${JSON.stringify(opts.amount)}.`);
|
|
2286
|
+
return { amount: parsed.value };
|
|
2287
|
+
}
|
|
2288
|
+
var LEGAL_TEXT_MAX_CHARS = 500;
|
|
2289
|
+
var CUT_MARK = "\u2026 [CUT: not the full text; use --json to read all of it]";
|
|
2290
|
+
function legalText(raw) {
|
|
2291
|
+
const clean = safeInline(raw, Number.MAX_SAFE_INTEGER);
|
|
2292
|
+
return clean.length > LEGAL_TEXT_MAX_CHARS ? { text: `${clean.slice(0, LEGAL_TEXT_MAX_CHARS)}${CUT_MARK}`, cut: true } : { text: clean, cut: false };
|
|
2293
|
+
}
|
|
2294
|
+
function money(amount, currency) {
|
|
2295
|
+
if (typeof amount !== "number") return "?";
|
|
2296
|
+
return currency ? safeInline(formatMinorUnits5(amount, currency)) : `${amount} in minor units`;
|
|
2297
|
+
}
|
|
2298
|
+
function every(count, interval) {
|
|
2299
|
+
if (!interval) return "";
|
|
2300
|
+
const unit = safeInline(interval);
|
|
2301
|
+
return typeof count === "number" && count !== 1 ? `every ${count} ${unit}s` : `every ${unit}`;
|
|
2302
|
+
}
|
|
2303
|
+
function statusTone2(status) {
|
|
2304
|
+
return status === "active" ? chalk12.green(status) : chalk12.yellow(status);
|
|
2305
|
+
}
|
|
2306
|
+
function productLines(p) {
|
|
2307
|
+
const head = ` ${safeInline(p.id)} ${statusTone2(safeInline(p.status))} ${safeInline(p.name, 120)} (ref ${safeInline(p.productRef, 128)})`;
|
|
2308
|
+
const schedule = p.interval ? `first payment ${money(p.firstPrice, p.currency)}, then ${money(p.renewalPrice, p.currency)} ${every(p.intervalCount, p.interval)}` : `${money(p.renewalPrice, p.currency)} \u2014 no renewal schedule yet (it cannot start a subscription)`;
|
|
2309
|
+
const lines = [head, ` ${schedule}`];
|
|
2310
|
+
const terms = p.terms;
|
|
2311
|
+
if (terms && typeof terms.disclosure === "string") lines.push(` Disclosure: ${legalText(terms.disclosure).text}`);
|
|
2312
|
+
return lines;
|
|
2313
|
+
}
|
|
2314
|
+
function productListLines(page) {
|
|
2315
|
+
const lines = [];
|
|
2316
|
+
if (page.data.length === 0) lines.push(" No subscription products match.");
|
|
2317
|
+
for (const p of page.data) lines.push(...productLines(p));
|
|
2318
|
+
if (page.hasMore && page.nextCursor) {
|
|
2319
|
+
lines.push(` More: re-run with the same filters and --starting-after ${safeInline(page.nextCursor)}`);
|
|
2320
|
+
}
|
|
2321
|
+
return lines;
|
|
2322
|
+
}
|
|
2323
|
+
function termsLines(t) {
|
|
2324
|
+
const disclosure = legalText(t.disclosure);
|
|
2325
|
+
const phrase = legalText(t.pricePhrase);
|
|
2326
|
+
return [
|
|
2327
|
+
` Product: ${safeInline(t.product)} (ref ${safeInline(t.productRef, 128)})`,
|
|
2328
|
+
` Disclosure: ${disclosure.text}`,
|
|
2329
|
+
` Price phrase: ${phrase.text}`,
|
|
2330
|
+
` First payment: ${money(t.firstAmount, t.currency)}`,
|
|
2331
|
+
` Renewal: ${money(t.amount, t.currency)} ${every(t.intervalCount, t.interval)}`,
|
|
2332
|
+
` Terms version: ${safeInline(t.termsVersion)}`,
|
|
2333
|
+
disclosure.cut || phrase.cut ? " The text above was cut. Read all of it with --json before you show it next to the order button." : " Show the disclosure, or your own words containing the price phrase, with your business name and how to cancel."
|
|
2334
|
+
];
|
|
2335
|
+
}
|
|
2336
|
+
function exitOnBadFlag2(err) {
|
|
2337
|
+
const message = err.message;
|
|
2338
|
+
exitWithCliError(message, chalk12.red(`Error: ${message}`));
|
|
2339
|
+
}
|
|
2340
|
+
function subscriptionProductsCommand() {
|
|
2341
|
+
const products = new Command8("subscription-products").description(
|
|
2342
|
+
"Read your subscription products and the renewal terms a sign-up states (read-only; products are created and changed in the dashboard)"
|
|
2343
|
+
);
|
|
2344
|
+
products.command("list").description("List your subscription products, newest first, each with the terms a sign-up on it states").option("--product-ref <ref>", "Only the product with this reference (your store's product number), matched exactly").option("--status <status>", "Only products with this status: active or archived").option("--limit <n>", "Page size, 1 to 100 (default 10)").option("--starting-after <id>", "The id of the last product on the previous page (printed by a previous run)").option("--json", "Output raw JSON").action(async (opts) => {
|
|
2345
|
+
let params;
|
|
2346
|
+
try {
|
|
2347
|
+
params = buildListParams2(opts);
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
exitOnBadFlag2(err);
|
|
2350
|
+
}
|
|
2351
|
+
const client = makeCheckoutClient();
|
|
2352
|
+
const spinner = startSpinner("Listing subscription products...");
|
|
2353
|
+
try {
|
|
2354
|
+
const page = await client.subscriptionProducts.list(params);
|
|
2355
|
+
spinner.stop();
|
|
2356
|
+
if (opts.json) {
|
|
2357
|
+
console.log(jsonOutput(page));
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
for (const line of productListLines(page)) console.log(line);
|
|
2361
|
+
} catch (err) {
|
|
2362
|
+
spinner.stop();
|
|
2363
|
+
handleError(err);
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
products.command("terms <product>").description(
|
|
2367
|
+
"Read one subscription product's renewal terms, to show next to the order button before the sign-up charge. <product> is the product's id (vsprod_*) or its reference"
|
|
2368
|
+
).option("--amount <minor>", "An intro first payment in minor units (e.g. 100 = 1.00 USD), at or below the product's price").option("--json", "Output raw JSON").action(async (product, opts) => {
|
|
2369
|
+
let params;
|
|
2370
|
+
try {
|
|
2371
|
+
params = buildTermsParams(opts);
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
exitOnBadFlag2(err);
|
|
2374
|
+
}
|
|
2375
|
+
const client = makeCheckoutClient();
|
|
2376
|
+
const spinner = startSpinner("Reading subscription product terms...");
|
|
2377
|
+
try {
|
|
2378
|
+
const terms = await client.subscriptionProducts.terms(product, params);
|
|
2379
|
+
spinner.stop();
|
|
2380
|
+
if (opts.json) {
|
|
2381
|
+
console.log(jsonOutput(terms));
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
for (const line of termsLines(terms)) console.log(line);
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
spinner.stop();
|
|
2387
|
+
handleError(err);
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2390
|
+
return products;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
// src/commands/capabilities.ts
|
|
2394
|
+
import { Command as Command9 } from "commander";
|
|
2395
|
+
import chalk13 from "chalk";
|
|
2317
2396
|
function fmtBool(v) {
|
|
2318
|
-
return v ?
|
|
2397
|
+
return v ? chalk13.green("yes") : chalk13.dim("no");
|
|
2319
2398
|
}
|
|
2320
2399
|
function fmtVoidPolicy(v) {
|
|
2321
2400
|
switch (v) {
|
|
2322
2401
|
case "supported":
|
|
2323
|
-
return
|
|
2402
|
+
return chalk13.green(v);
|
|
2324
2403
|
case "rerouted_to_refund":
|
|
2325
|
-
return
|
|
2404
|
+
return chalk13.yellow(v);
|
|
2326
2405
|
case "not_supported":
|
|
2327
|
-
return
|
|
2406
|
+
return chalk13.dim(v);
|
|
2328
2407
|
default:
|
|
2329
|
-
return
|
|
2408
|
+
return chalk13.dim(String(v));
|
|
2330
2409
|
}
|
|
2331
2410
|
}
|
|
2332
2411
|
function fmtDisputeReporting(v) {
|
|
2333
|
-
return v === "tracked" ?
|
|
2412
|
+
return v === "tracked" ? chalk13.green(v) : chalk13.yellow(v);
|
|
2334
2413
|
}
|
|
2335
2414
|
function fmtSubscriptionBilling(v) {
|
|
2336
|
-
if (v === true) return
|
|
2337
|
-
if (v === false) return
|
|
2338
|
-
return
|
|
2415
|
+
if (v === true) return chalk13.yellow("yes");
|
|
2416
|
+
if (v === false) return chalk13.green("no");
|
|
2417
|
+
return chalk13.yellow("unknown");
|
|
2339
2418
|
}
|
|
2340
2419
|
function printCapabilities(caps) {
|
|
2341
2420
|
const ops = caps.supportedOperations;
|
|
2342
|
-
console.log(
|
|
2421
|
+
console.log(chalk13.bold("Supported operations"));
|
|
2343
2422
|
console.log(` auth/capture separation: ${fmtBool(ops.authCaptureSeparation)}`);
|
|
2344
2423
|
console.log(` partial capture: ${fmtBool(ops.partialCapture)}`);
|
|
2345
2424
|
console.log(` partial refund: ${fmtBool(ops.partialRefund)}`);
|
|
@@ -2354,17 +2433,17 @@ function printCapabilities(caps) {
|
|
|
2354
2433
|
console.log();
|
|
2355
2434
|
if (ops.disputeReporting !== "tracked") {
|
|
2356
2435
|
console.log(
|
|
2357
|
-
|
|
2436
|
+
chalk13.yellow(
|
|
2358
2437
|
" \u26A0 Chargebacks on this connection do NOT reach Von Payments.\n A `succeeded` payment is not evidence it was never disputed.\n Monitor and answer disputes in your processor's own dashboard \u2014\n they carry a deadline we cannot see or report."
|
|
2359
2438
|
)
|
|
2360
2439
|
);
|
|
2361
2440
|
console.log();
|
|
2362
2441
|
}
|
|
2363
|
-
console.log(
|
|
2442
|
+
console.log(chalk13.bold("Subscription billing"));
|
|
2364
2443
|
console.log(` Von Pay renews for you: ${fmtSubscriptionBilling(caps.subscriptionBilling?.enabled)}`);
|
|
2365
2444
|
if (caps.subscriptionBilling?.enabled !== false) {
|
|
2366
2445
|
console.log(
|
|
2367
|
-
|
|
2446
|
+
chalk13.yellow(
|
|
2368
2447
|
` \u26A0 Do not run renewals from your own system while this is not "no":
|
|
2369
2448
|
Von Pay may be charging these customers' renewals itself, and both
|
|
2370
2449
|
would charge them. Check again at the moment you renew.`
|
|
@@ -2372,25 +2451,25 @@ function printCapabilities(caps) {
|
|
|
2372
2451
|
);
|
|
2373
2452
|
}
|
|
2374
2453
|
console.log();
|
|
2375
|
-
console.log(
|
|
2454
|
+
console.log(chalk13.bold("Settlement currencies"));
|
|
2376
2455
|
console.log(
|
|
2377
|
-
` ${caps.settlementCurrencies.length ? caps.settlementCurrencies.join(", ") :
|
|
2456
|
+
` ${caps.settlementCurrencies.length ? caps.settlementCurrencies.join(", ") : chalk13.dim("(none reported)")}`
|
|
2378
2457
|
);
|
|
2379
2458
|
console.log();
|
|
2380
|
-
console.log(
|
|
2459
|
+
console.log(chalk13.bold("Rate limits"));
|
|
2381
2460
|
console.log(
|
|
2382
2461
|
` payment intents / minute: ${caps.rateLimits.paymentIntentsPerMinute}`
|
|
2383
2462
|
);
|
|
2384
2463
|
}
|
|
2385
2464
|
function capabilitiesCommand() {
|
|
2386
|
-
return new
|
|
2465
|
+
return new Command9("capabilities").description("Show what this merchant's binder supports").option("--json", "Output raw JSON").action(async (opts) => {
|
|
2387
2466
|
const client = makeCheckoutClient();
|
|
2388
2467
|
const spinner = startSpinner("Fetching capabilities...");
|
|
2389
2468
|
try {
|
|
2390
2469
|
const caps = await client.capabilities.get();
|
|
2391
2470
|
spinner.stop();
|
|
2392
2471
|
if (opts.json) {
|
|
2393
|
-
console.log(
|
|
2472
|
+
console.log(jsonOutput(caps));
|
|
2394
2473
|
} else {
|
|
2395
2474
|
printCapabilities(caps);
|
|
2396
2475
|
}
|
|
@@ -2402,9 +2481,9 @@ function capabilitiesCommand() {
|
|
|
2402
2481
|
}
|
|
2403
2482
|
|
|
2404
2483
|
// src/commands/trigger.ts
|
|
2405
|
-
import { Command as
|
|
2484
|
+
import { Command as Command10 } from "commander";
|
|
2406
2485
|
import { createHmac as createHmac2, randomUUID as randomUUID2 } from "crypto";
|
|
2407
|
-
import
|
|
2486
|
+
import chalk14 from "chalk";
|
|
2408
2487
|
import ora2 from "ora";
|
|
2409
2488
|
|
|
2410
2489
|
// src/utils/signing-secret.ts
|
|
@@ -2622,7 +2701,7 @@ function warnIfUnsubscribable(event, write = (m) => console.warn(m)) {
|
|
|
2622
2701
|
if (!UNSUBSCRIBABLE_EVENTS.includes(event)) return false;
|
|
2623
2702
|
const suggestion = event === "session.succeeded" ? "charge.succeeded" : "charge.failed";
|
|
2624
2703
|
write(
|
|
2625
|
-
|
|
2704
|
+
chalk14.yellow(
|
|
2626
2705
|
`\u26A0 ${event} is emitted internally but is NOT subscribable.
|
|
2627
2706
|
The subscription API accepts the key, stores nothing and returns success, so a
|
|
2628
2707
|
real endpoint receives this event NEVER \u2014 a handler tested only against it is
|
|
@@ -2817,7 +2896,7 @@ function buildPayload(event, opts) {
|
|
|
2817
2896
|
};
|
|
2818
2897
|
}
|
|
2819
2898
|
function triggerCommand() {
|
|
2820
|
-
return new
|
|
2899
|
+
return new Command10("trigger").description("Send a test webhook event to a local URL").argument("<event>", `Event type (${SUPPORTED_EVENTS.join(", ")})`).requiredOption("--url <url>", "Local webhook URL (e.g. http://localhost:3000/webhooks)").option("--session-id <id>", "Custom session ID").option("--amount <cents>", "Amount in minor units, a whole number").option("--currency <code>", "Currency code").option(
|
|
2821
2900
|
"--url-tunnel",
|
|
2822
2901
|
"Allow a non-loopback --url (ngrok / Cloudflare Tunnel). Without it, --url must be this machine"
|
|
2823
2902
|
).option(
|
|
@@ -2826,7 +2905,7 @@ function triggerCommand() {
|
|
|
2826
2905
|
).addOption(retiredConfirmLiveOption()).action(async (event, opts) => {
|
|
2827
2906
|
if (!SUPPORTED_EVENTS.includes(event)) {
|
|
2828
2907
|
console.error(
|
|
2829
|
-
|
|
2908
|
+
chalk14.red(
|
|
2830
2909
|
`Unknown event: ${event}
|
|
2831
2910
|
Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
2832
2911
|
)
|
|
@@ -2840,10 +2919,10 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2840
2919
|
tunnelFlag: "--url-tunnel"
|
|
2841
2920
|
});
|
|
2842
2921
|
if (!destination.ok) {
|
|
2843
|
-
console.error(
|
|
2922
|
+
console.error(chalk14.red(destination.reason));
|
|
2844
2923
|
process.exit(1);
|
|
2845
2924
|
}
|
|
2846
|
-
if (destination.warning) console.warn(
|
|
2925
|
+
if (destination.warning) console.warn(chalk14.yellow(destination.warning));
|
|
2847
2926
|
const amount = optionalMinorUnitsOrExit(
|
|
2848
2927
|
opts.amount === void 0 ? void 0 : String(opts.amount),
|
|
2849
2928
|
"--amount"
|
|
@@ -2851,7 +2930,7 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2851
2930
|
const apiKey = requireApiKey();
|
|
2852
2931
|
const signing = resolveSigningSecret(opts.signingSecret, process.env, apiKey);
|
|
2853
2932
|
if ("error" in signing) {
|
|
2854
|
-
console.error(
|
|
2933
|
+
console.error(chalk14.red(signing.error));
|
|
2855
2934
|
process.exit(1);
|
|
2856
2935
|
}
|
|
2857
2936
|
await confirmLiveAction(
|
|
@@ -2868,7 +2947,7 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2868
2947
|
);
|
|
2869
2948
|
const payload = buildPayload(event, { ...opts, amount });
|
|
2870
2949
|
const body = JSON.stringify(payload);
|
|
2871
|
-
console.log(
|
|
2950
|
+
console.log(chalk14.dim(signingSecretNotice(signing)));
|
|
2872
2951
|
const t = Math.floor(Date.now() / 1e3);
|
|
2873
2952
|
const v1 = createHmac2("sha256", signing.secret).update(`${t}.${body}`).digest("hex");
|
|
2874
2953
|
const signatureHeader = `t=${t},v1=${v1}`;
|
|
@@ -2890,45 +2969,45 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2890
2969
|
spinner.stop();
|
|
2891
2970
|
if (isRedirect(response)) {
|
|
2892
2971
|
console.error(
|
|
2893
|
-
|
|
2972
|
+
chalk14.red(
|
|
2894
2973
|
`\u2717 ${event} not delivered: ${safeDisplay(String(opts.url))} answered with a redirect (HTTP ${response.status}). It was not followed, and the signed event was not sent anywhere else. Point --url at the endpoint itself.`
|
|
2895
2974
|
)
|
|
2896
2975
|
);
|
|
2897
2976
|
process.exit(1);
|
|
2898
2977
|
}
|
|
2899
2978
|
if (response.ok) {
|
|
2900
|
-
console.log(
|
|
2979
|
+
console.log(chalk14.green(`\u2713 ${event} delivered \u2192 ${response.status}`));
|
|
2901
2980
|
} else {
|
|
2902
2981
|
console.log(
|
|
2903
|
-
|
|
2982
|
+
chalk14.yellow(
|
|
2904
2983
|
`\u26A0 ${event} delivered but got ${response.status} response`
|
|
2905
2984
|
)
|
|
2906
2985
|
);
|
|
2907
2986
|
}
|
|
2908
|
-
console.log(
|
|
2987
|
+
console.log(chalk14.dim(` Event: ${payload.id} (${payload.type})`));
|
|
2909
2988
|
console.log(
|
|
2910
|
-
|
|
2989
|
+
chalk14.dim(
|
|
2911
2990
|
` Amount: ${payload.data.amount} ${payload.data.currency}`
|
|
2912
2991
|
)
|
|
2913
2992
|
);
|
|
2914
2993
|
console.log(
|
|
2915
|
-
|
|
2994
|
+
chalk14.dim(
|
|
2916
2995
|
`
|
|
2917
2996
|
Verify with: vonpay.webhooks.constructEvent(rawBody, signatureHeader, secret)`
|
|
2918
2997
|
)
|
|
2919
2998
|
);
|
|
2920
2999
|
console.log(
|
|
2921
|
-
|
|
3000
|
+
chalk14.dim(
|
|
2922
3001
|
` ('secret' is this run's signing secret, printed above \u2014 production webhooks are signed with your endpoint's whsec_\u2026 secret, and never with your API key)`
|
|
2923
3002
|
)
|
|
2924
3003
|
);
|
|
2925
3004
|
} catch (err) {
|
|
2926
3005
|
spinner.stop();
|
|
2927
3006
|
if (err instanceof Error && err.name === "TimeoutError") {
|
|
2928
|
-
console.error(
|
|
3007
|
+
console.error(chalk14.red(`Timeout: ${opts.url} did not respond within 10s`));
|
|
2929
3008
|
} else {
|
|
2930
3009
|
console.error(
|
|
2931
|
-
|
|
3010
|
+
chalk14.red(
|
|
2932
3011
|
`Failed to deliver: ${err instanceof Error ? err.message : err}`
|
|
2933
3012
|
)
|
|
2934
3013
|
);
|
|
@@ -2939,7 +3018,7 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2939
3018
|
}
|
|
2940
3019
|
|
|
2941
3020
|
// src/commands/listen.ts
|
|
2942
|
-
import { Command as
|
|
3021
|
+
import { Command as Command11 } from "commander";
|
|
2943
3022
|
import { VonPayCheckout as VonPayCheckout2 } from "@vonpay/checkout-node";
|
|
2944
3023
|
|
|
2945
3024
|
// ../sse-parser/dist/index.js
|
|
@@ -3205,14 +3284,14 @@ var SseRedirectError = class extends Error {
|
|
|
3205
3284
|
status;
|
|
3206
3285
|
};
|
|
3207
3286
|
function sleep(ms, signal) {
|
|
3208
|
-
return new Promise((
|
|
3287
|
+
return new Promise((resolve2) => {
|
|
3209
3288
|
const timer = setTimeout(() => {
|
|
3210
3289
|
signal.removeEventListener("abort", onAbort);
|
|
3211
|
-
|
|
3290
|
+
resolve2();
|
|
3212
3291
|
}, ms);
|
|
3213
3292
|
const onAbort = () => {
|
|
3214
3293
|
clearTimeout(timer);
|
|
3215
|
-
|
|
3294
|
+
resolve2();
|
|
3216
3295
|
};
|
|
3217
3296
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
3218
3297
|
});
|
|
@@ -3231,7 +3310,7 @@ function noop() {
|
|
|
3231
3310
|
}
|
|
3232
3311
|
|
|
3233
3312
|
// src/utils/listen-output.ts
|
|
3234
|
-
import
|
|
3313
|
+
import chalk15 from "chalk";
|
|
3235
3314
|
function parseAttemptPayload(data) {
|
|
3236
3315
|
let raw;
|
|
3237
3316
|
try {
|
|
@@ -3248,21 +3327,21 @@ function parseAttemptPayload(data) {
|
|
|
3248
3327
|
}
|
|
3249
3328
|
function formatAttemptHuman(payload, forward) {
|
|
3250
3329
|
const ts = formatTimestamp(payload.timestamp);
|
|
3251
|
-
const eventType =
|
|
3252
|
-
const eventId =
|
|
3253
|
-
const attempt =
|
|
3254
|
-
const arrow =
|
|
3330
|
+
const eventType = chalk15.cyan(safeInline(payload.event_type, 64).padEnd(24));
|
|
3331
|
+
const eventId = chalk15.gray(safeInline(payload.event_id, 64).padEnd(28));
|
|
3332
|
+
const attempt = chalk15.gray(`attempt ${payload.attempt}`);
|
|
3333
|
+
const arrow = chalk15.gray("\u2192");
|
|
3255
3334
|
const upstream = formatUpstream(payload);
|
|
3256
|
-
const latency =
|
|
3257
|
-
const liveBadge = payload.livemode ?
|
|
3335
|
+
const latency = chalk15.gray(`${payload.latency_ms}ms`);
|
|
3336
|
+
const liveBadge = payload.livemode ? chalk15.red(" [LIVE]") : "";
|
|
3258
3337
|
let line = `[${ts}] ${eventType} ${eventId} ${attempt} ${arrow} ${upstream} ${latency}${liveBadge}`;
|
|
3259
3338
|
if (forward && forward.attempted) {
|
|
3260
|
-
const fwdArrow =
|
|
3339
|
+
const fwdArrow = chalk15.gray("\u2192 local");
|
|
3261
3340
|
if (forward.error) {
|
|
3262
|
-
line += ` ${fwdArrow} ${
|
|
3341
|
+
line += ` ${fwdArrow} ${chalk15.red(safeInline(forward.error))}`;
|
|
3263
3342
|
} else if (forward.status !== null) {
|
|
3264
3343
|
const fwdStatus = formatStatus(forward.status);
|
|
3265
|
-
const fwdLatency =
|
|
3344
|
+
const fwdLatency = chalk15.gray(`${forward.latencyMs}ms`);
|
|
3266
3345
|
line += ` ${fwdArrow} ${fwdStatus} ${fwdLatency}`;
|
|
3267
3346
|
}
|
|
3268
3347
|
}
|
|
@@ -3288,24 +3367,27 @@ function formatAttemptJson(payload, forward) {
|
|
|
3288
3367
|
error: forward.error === null ? null : safeInline(forward.error)
|
|
3289
3368
|
};
|
|
3290
3369
|
}
|
|
3291
|
-
return JSON.stringify(body);
|
|
3370
|
+
return JSON.stringify(cleanJsonValue(body));
|
|
3292
3371
|
}
|
|
3372
|
+
var LIFECYCLE_EXACT_KEYS = /* @__PURE__ */ new Set(["signing_secret"]);
|
|
3293
3373
|
function formatLifecycleHuman(message, kind) {
|
|
3294
|
-
const tag = kind === "info" ?
|
|
3374
|
+
const tag = kind === "info" ? chalk15.cyan("[vonpay]") : kind === "warn" ? chalk15.yellow("[vonpay]") : chalk15.red("[vonpay]");
|
|
3295
3375
|
return `${tag} ${message}`;
|
|
3296
3376
|
}
|
|
3297
3377
|
function formatLifecycleJson(kind, extra = {}) {
|
|
3298
|
-
return JSON.stringify(
|
|
3378
|
+
return JSON.stringify(
|
|
3379
|
+
cleanJsonValue({ type: `cli.${kind}`, ...extra, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, { exactKeys: LIFECYCLE_EXACT_KEYS })
|
|
3380
|
+
);
|
|
3299
3381
|
}
|
|
3300
3382
|
function formatStatus(status) {
|
|
3301
|
-
if (status >= 200 && status < 300) return
|
|
3302
|
-
if (status >= 400 && status < 500) return
|
|
3303
|
-
if (status >= 500) return
|
|
3304
|
-
return
|
|
3383
|
+
if (status >= 200 && status < 300) return chalk15.green(String(status));
|
|
3384
|
+
if (status >= 400 && status < 500) return chalk15.yellow(String(status));
|
|
3385
|
+
if (status >= 500) return chalk15.red(String(status));
|
|
3386
|
+
return chalk15.gray(String(status));
|
|
3305
3387
|
}
|
|
3306
3388
|
function formatUpstream(payload) {
|
|
3307
3389
|
if (payload.response_status === null) {
|
|
3308
|
-
return
|
|
3390
|
+
return chalk15.red("timeout");
|
|
3309
3391
|
}
|
|
3310
3392
|
return formatStatus(payload.response_status);
|
|
3311
3393
|
}
|
|
@@ -3320,7 +3402,7 @@ function formatTimestamp(iso) {
|
|
|
3320
3402
|
|
|
3321
3403
|
// src/commands/listen.ts
|
|
3322
3404
|
function listenCommand() {
|
|
3323
|
-
return new
|
|
3405
|
+
return new Command11("listen").description(
|
|
3324
3406
|
"Watch webhook delivery attempts in real time. Optionally re-forward each event to a local URL for development."
|
|
3325
3407
|
).option(
|
|
3326
3408
|
"--forward-to <url>",
|
|
@@ -3568,20 +3650,20 @@ function lifecycleSeverity(kind) {
|
|
|
3568
3650
|
}
|
|
3569
3651
|
|
|
3570
3652
|
// src/commands/health.ts
|
|
3571
|
-
import { Command as
|
|
3572
|
-
import
|
|
3653
|
+
import { Command as Command12 } from "commander";
|
|
3654
|
+
import chalk16 from "chalk";
|
|
3573
3655
|
import { VonPayError as VonPayError3 } from "@vonpay/checkout-node";
|
|
3574
3656
|
function healthCommand() {
|
|
3575
|
-
return new
|
|
3657
|
+
return new Command12("health").description("Check checkout API health").option("--json", "Output raw JSON").action(async (opts) => {
|
|
3576
3658
|
const client = makeCheckoutClient();
|
|
3577
3659
|
const spinner = startSpinner("Checking API health...");
|
|
3578
3660
|
try {
|
|
3579
3661
|
const health = await client.health();
|
|
3580
3662
|
spinner.stop();
|
|
3581
3663
|
if (opts.json) {
|
|
3582
|
-
console.log(
|
|
3664
|
+
console.log(jsonOutput(health));
|
|
3583
3665
|
} else {
|
|
3584
|
-
const statusColor = health.status === "ok" ?
|
|
3666
|
+
const statusColor = health.status === "ok" ? chalk16.green : health.status === "degraded" ? chalk16.yellow : chalk16.red;
|
|
3585
3667
|
console.log(` Status: ${statusColor(safeInline(String(health.status)))}`);
|
|
3586
3668
|
console.log(` Latency: ${safeInline(String(health.latencyMs))}ms`);
|
|
3587
3669
|
if (health.version) {
|
|
@@ -3593,11 +3675,11 @@ function healthCommand() {
|
|
|
3593
3675
|
auditError(err);
|
|
3594
3676
|
if (jsonErrorsEnabled()) handleError(err);
|
|
3595
3677
|
if (err instanceof VonPayError3) {
|
|
3596
|
-
console.error(
|
|
3597
|
-
console.error(
|
|
3678
|
+
console.error(chalk16.red(`API error: ${scrubSecrets(safeInline(err.message, 2e3))}`));
|
|
3679
|
+
console.error(chalk16.dim(` Code: ${safeInline(String(err.code))}`));
|
|
3598
3680
|
} else {
|
|
3599
3681
|
console.error(
|
|
3600
|
-
|
|
3682
|
+
chalk16.red(
|
|
3601
3683
|
`Cannot reach API: ${scrubSecrets(safeInline(err instanceof Error ? err.message : String(err), 2e3))}`
|
|
3602
3684
|
)
|
|
3603
3685
|
);
|
|
@@ -3608,10 +3690,10 @@ function healthCommand() {
|
|
|
3608
3690
|
}
|
|
3609
3691
|
|
|
3610
3692
|
// src/commands/doctor.ts
|
|
3611
|
-
import { Command as
|
|
3612
|
-
import
|
|
3693
|
+
import { Command as Command13 } from "commander";
|
|
3694
|
+
import chalk17 from "chalk";
|
|
3613
3695
|
import ora3 from "ora";
|
|
3614
|
-
import { homedir as
|
|
3696
|
+
import { homedir as homedir2 } from "os";
|
|
3615
3697
|
import { existsSync as existsSync3 } from "fs";
|
|
3616
3698
|
import {
|
|
3617
3699
|
VonPayCheckout as VonPayCheckout3,
|
|
@@ -3627,7 +3709,7 @@ var RELEVANT_ENV_VARS = [
|
|
|
3627
3709
|
"NEXT_PUBLIC_BASE_URL"
|
|
3628
3710
|
];
|
|
3629
3711
|
function redactHome(p) {
|
|
3630
|
-
const home =
|
|
3712
|
+
const home = homedir2();
|
|
3631
3713
|
if (home && p.startsWith(home)) {
|
|
3632
3714
|
return "~" + p.slice(home.length);
|
|
3633
3715
|
}
|
|
@@ -3639,7 +3721,8 @@ function maskPrefix(key) {
|
|
|
3639
3721
|
if (!m) return void 0;
|
|
3640
3722
|
return `${m[1]}${m[3]}\u2026`;
|
|
3641
3723
|
}
|
|
3642
|
-
|
|
3724
|
+
var DOCTOR_JSON_CLEANING = { exactKeys: /* @__PURE__ */ new Set(["apiKeyPrefix"]) };
|
|
3725
|
+
function keyMode2(key) {
|
|
3643
3726
|
if (!key) return void 0;
|
|
3644
3727
|
return secretKeyMode(key) ?? "not_a_secret_key";
|
|
3645
3728
|
}
|
|
@@ -3665,10 +3748,10 @@ async function gather() {
|
|
|
3665
3748
|
present: existsSync3(
|
|
3666
3749
|
`${process.env.HOME ?? process.env.USERPROFILE ?? ""}/.vonpay/config.json`
|
|
3667
3750
|
),
|
|
3668
|
-
keyType: config.checkout?.apiKey ?
|
|
3751
|
+
keyType: config.checkout?.apiKey ? keyMode2(config.checkout?.apiKey) : void 0
|
|
3669
3752
|
},
|
|
3670
3753
|
apiKeyPrefix: maskPrefix(apiKey),
|
|
3671
|
-
apiKeyMode:
|
|
3754
|
+
apiKeyMode: keyMode2(apiKey),
|
|
3672
3755
|
health: { baseUrl, reachable: false },
|
|
3673
3756
|
hints: []
|
|
3674
3757
|
};
|
|
@@ -3793,47 +3876,47 @@ async function gather() {
|
|
|
3793
3876
|
}
|
|
3794
3877
|
function renderHuman(r) {
|
|
3795
3878
|
const lines = [];
|
|
3796
|
-
lines.push(
|
|
3797
|
-
lines.push(
|
|
3879
|
+
lines.push(chalk17.bold.blue("vonpay doctor \u2014 diagnostic report"));
|
|
3880
|
+
lines.push(chalk17.dim(`generated ${r.generatedAt}`));
|
|
3798
3881
|
lines.push("");
|
|
3799
|
-
lines.push(
|
|
3882
|
+
lines.push(chalk17.bold("Runtime"));
|
|
3800
3883
|
lines.push(` CLI: ${r.cli.version}`);
|
|
3801
3884
|
lines.push(` Node: ${r.runtime.node}`);
|
|
3802
3885
|
lines.push(` Platform: ${r.runtime.platform} (${r.runtime.arch})`);
|
|
3803
3886
|
lines.push(` CWD: ${r.runtime.cwd}`);
|
|
3804
3887
|
lines.push("");
|
|
3805
|
-
lines.push(
|
|
3888
|
+
lines.push(chalk17.bold("API key"));
|
|
3806
3889
|
if (r.apiKeyPrefix) {
|
|
3807
3890
|
lines.push(` Prefix: ${r.apiKeyPrefix}`);
|
|
3808
3891
|
lines.push(` Mode: ${r.apiKeyMode}`);
|
|
3809
3892
|
} else {
|
|
3810
|
-
lines.push(
|
|
3893
|
+
lines.push(chalk17.dim(" (no key found in env or ~/.vonpay/config.json)"));
|
|
3811
3894
|
}
|
|
3812
3895
|
lines.push("");
|
|
3813
|
-
lines.push(
|
|
3896
|
+
lines.push(chalk17.bold("Env vars present (names only)"));
|
|
3814
3897
|
if (r.envVarsPresent.length === 0) {
|
|
3815
|
-
lines.push(
|
|
3898
|
+
lines.push(chalk17.dim(" (none of the expected vonpay env vars are set)"));
|
|
3816
3899
|
} else {
|
|
3817
3900
|
for (const n of r.envVarsPresent) lines.push(` \u2713 ${n}`);
|
|
3818
3901
|
}
|
|
3819
3902
|
lines.push("");
|
|
3820
|
-
lines.push(
|
|
3903
|
+
lines.push(chalk17.bold("Health probe"));
|
|
3821
3904
|
if (r.health.reachable) {
|
|
3822
|
-
const statusColor = r.health.status === "ok" ?
|
|
3823
|
-
lines.push(` Reachable: ${
|
|
3905
|
+
const statusColor = r.health.status === "ok" ? chalk17.green : r.health.status === "degraded" ? chalk17.yellow : chalk17.red;
|
|
3906
|
+
lines.push(` Reachable: ${chalk17.green("yes")}`);
|
|
3824
3907
|
lines.push(` Status: ${statusColor(r.health.status ?? "?")}`);
|
|
3825
3908
|
lines.push(` Latency: ${r.health.latencyMs}ms`);
|
|
3826
3909
|
} else {
|
|
3827
|
-
lines.push(` Reachable: ${
|
|
3910
|
+
lines.push(` Reachable: ${chalk17.red("no")}`);
|
|
3828
3911
|
lines.push(` Error: ${r.health.error}`);
|
|
3829
3912
|
}
|
|
3830
3913
|
lines.push("");
|
|
3831
3914
|
if (r.validateProbe) {
|
|
3832
|
-
lines.push(
|
|
3915
|
+
lines.push(chalk17.bold("sessions.validate probe ($14.99 USD dry-run)"));
|
|
3833
3916
|
if (r.validateProbe.ok) {
|
|
3834
|
-
lines.push(` Result: ${
|
|
3917
|
+
lines.push(` Result: ${chalk17.green("valid")}`);
|
|
3835
3918
|
} else {
|
|
3836
|
-
lines.push(` Result: ${
|
|
3919
|
+
lines.push(` Result: ${chalk17.red("invalid")}`);
|
|
3837
3920
|
if (r.validateProbe.error) {
|
|
3838
3921
|
lines.push(` Code: ${r.validateProbe.error.code}`);
|
|
3839
3922
|
lines.push(` Action: ${r.validateProbe.error.nextAction}`);
|
|
@@ -3846,7 +3929,7 @@ function renderHuman(r) {
|
|
|
3846
3929
|
lines.push("");
|
|
3847
3930
|
}
|
|
3848
3931
|
if (r.hints.length > 0) {
|
|
3849
|
-
lines.push(
|
|
3932
|
+
lines.push(chalk17.bold("Hints"));
|
|
3850
3933
|
for (const h of r.hints) lines.push(` \u2022 ${h}`);
|
|
3851
3934
|
lines.push("");
|
|
3852
3935
|
}
|
|
@@ -3923,7 +4006,7 @@ function renderForLlm(r) {
|
|
|
3923
4006
|
return out.join("\n");
|
|
3924
4007
|
}
|
|
3925
4008
|
function doctorCommand() {
|
|
3926
|
-
return new
|
|
4009
|
+
return new Command13("doctor").description(
|
|
3927
4010
|
"Diagnose your Von Payments environment \u2014 runtime, env vars, live API probe; outputs human / JSON / LLM-readable formats"
|
|
3928
4011
|
).option("--json", "Output JSON (machine-parseable)").option(
|
|
3929
4012
|
"--for-llm",
|
|
@@ -3938,7 +4021,7 @@ function doctorCommand() {
|
|
|
3938
4021
|
const report = await gather();
|
|
3939
4022
|
spinner.stop();
|
|
3940
4023
|
if (opts.json) {
|
|
3941
|
-
console.log(
|
|
4024
|
+
console.log(jsonOutput(report, DOCTOR_JSON_CLEANING));
|
|
3942
4025
|
} else if (opts.forLlm) {
|
|
3943
4026
|
console.log(renderForLlm(report));
|
|
3944
4027
|
} else {
|
|
@@ -3960,6 +4043,7 @@ function registerCheckout(program) {
|
|
|
3960
4043
|
checkout.addCommand(paymentIntentsCommand());
|
|
3961
4044
|
checkout.addCommand(refundsCommand());
|
|
3962
4045
|
checkout.addCommand(tokensCommand());
|
|
4046
|
+
checkout.addCommand(subscriptionProductsCommand());
|
|
3963
4047
|
checkout.addCommand(capabilitiesCommand());
|
|
3964
4048
|
checkout.addCommand(triggerCommand());
|
|
3965
4049
|
checkout.addCommand(listenCommand());
|
|
@@ -3974,7 +4058,7 @@ function commandPath(cmd) {
|
|
|
3974
4058
|
return names.join(" ");
|
|
3975
4059
|
}
|
|
3976
4060
|
function buildProgram() {
|
|
3977
|
-
const program = new
|
|
4061
|
+
const program = new Command14();
|
|
3978
4062
|
program.name("vonpay").description("Von Payments developer tools").version(CLI_VERSION).option(
|
|
3979
4063
|
ALLOW_CUSTOM_BASE_URL_FLAG,
|
|
3980
4064
|
"With VONPAY_ALLOW_CUSTOM_BASE_URL=1: send a live key to a base URL that is not a Von Payments host (a proxy you run)"
|