@vonpay/checkout-cli 0.18.0 → 0.19.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 +461 -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)` : "";
|
|
@@ -970,16 +924,7 @@ function errorDetailLines(err) {
|
|
|
970
924
|
}
|
|
971
925
|
return lines;
|
|
972
926
|
}
|
|
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
|
-
}
|
|
927
|
+
var ERROR_JSON_CLEANING = { lines: "join", maxChars: ONE_LINE_MAX_CHARS, redact: scrubSecrets };
|
|
983
928
|
function errorJson(err) {
|
|
984
929
|
if (err instanceof VonPayError) {
|
|
985
930
|
const out = { error: err.message };
|
|
@@ -988,14 +933,14 @@ function errorJson(err) {
|
|
|
988
933
|
const value = source[field];
|
|
989
934
|
if (value !== void 0) out[field] = value;
|
|
990
935
|
}
|
|
991
|
-
return
|
|
936
|
+
return cleanJsonValue(out, ERROR_JSON_CLEANING);
|
|
992
937
|
}
|
|
993
938
|
const message = err instanceof Error ? err.message : String(err);
|
|
994
939
|
return { error: scrubSecrets(oneLine(message)), code: null };
|
|
995
940
|
}
|
|
996
941
|
function exitWithCliError(message, text) {
|
|
997
942
|
auditNote({ outcome: "refused_local", code: "local_refusal" }, true);
|
|
998
|
-
return emitCliError({ error:
|
|
943
|
+
return emitCliError({ error: cleanJsonValue(message, ERROR_JSON_CLEANING), code: null }, text);
|
|
999
944
|
}
|
|
1000
945
|
function handleError(err) {
|
|
1001
946
|
auditError(err);
|
|
@@ -1074,7 +1019,7 @@ function makeCheckoutClient() {
|
|
|
1074
1019
|
import { createInterface as createInterface2 } from "readline";
|
|
1075
1020
|
import chalk5 from "chalk";
|
|
1076
1021
|
import { Option as Option2 } from "commander";
|
|
1077
|
-
import { isLiveKey
|
|
1022
|
+
import { heldAsLive, isLiveKey } from "@vonpay/checkout-node";
|
|
1078
1023
|
var CONFIRM_WORD = "confirm";
|
|
1079
1024
|
var terminal = {
|
|
1080
1025
|
/**
|
|
@@ -1086,27 +1031,27 @@ var terminal = {
|
|
|
1086
1031
|
},
|
|
1087
1032
|
/** Ask one line on the terminal. End of input or Ctrl-C answers "" (cancel). */
|
|
1088
1033
|
ask(prompt) {
|
|
1089
|
-
return new Promise((
|
|
1034
|
+
return new Promise((resolve2) => {
|
|
1090
1035
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1091
1036
|
let answered = false;
|
|
1092
1037
|
rl.on("SIGINT", () => rl.close());
|
|
1093
1038
|
rl.on("close", () => {
|
|
1094
|
-
if (!answered)
|
|
1039
|
+
if (!answered) resolve2("");
|
|
1095
1040
|
});
|
|
1096
1041
|
rl.question(prompt, (answer) => {
|
|
1097
1042
|
answered = true;
|
|
1098
1043
|
rl.close();
|
|
1099
|
-
|
|
1044
|
+
resolve2(answer);
|
|
1100
1045
|
});
|
|
1101
1046
|
});
|
|
1102
1047
|
}
|
|
1103
1048
|
};
|
|
1104
|
-
function
|
|
1049
|
+
function keyFingerprint(key) {
|
|
1105
1050
|
const m = key.match(/^(vp_sk_live_)([A-Za-z0-9_-]{0,4})/);
|
|
1106
1051
|
return m ? `${m[1]}${m[2]}\u2026` : "a key that is not a sandbox key";
|
|
1107
1052
|
}
|
|
1108
1053
|
async function confirmLiveAction(key, summary, opts = {}) {
|
|
1109
|
-
if (!
|
|
1054
|
+
if (!heldAsLive(key)) return;
|
|
1110
1055
|
if (!terminal.isInteractive()) {
|
|
1111
1056
|
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
1057
|
auditNote({ outcome: "withheld", code: "no_terminal" });
|
|
@@ -1120,7 +1065,7 @@ async function confirmLiveAction(key, summary, opts = {}) {
|
|
|
1120
1065
|
);
|
|
1121
1066
|
}
|
|
1122
1067
|
const lines = [
|
|
1123
|
-
chalk5.bold.red(`You are about to ${summary.action} with a LIVE key (${
|
|
1068
|
+
chalk5.bold.red(`You are about to ${summary.action} with a LIVE key (${keyFingerprint(key)}).`),
|
|
1124
1069
|
// At least one space after the label, however long it is.
|
|
1125
1070
|
...(summary.details ?? []).map(([label, value]) => ` ${`${label}:`.padEnd(Math.max(17, label.length + 2))}${safeInline(value)}`),
|
|
1126
1071
|
chalk5.yellow(summary.consequence ?? "This moves real money and cannot be undone from here.")
|
|
@@ -1141,7 +1086,7 @@ function retiredConfirmLiveOption() {
|
|
|
1141
1086
|
).hideHelp();
|
|
1142
1087
|
}
|
|
1143
1088
|
function warnLiveSecretFlag(key, flag) {
|
|
1144
|
-
if (!
|
|
1089
|
+
if (!heldAsLive(key)) return;
|
|
1145
1090
|
console.warn(
|
|
1146
1091
|
chalk5.yellow(
|
|
1147
1092
|
`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 +1107,11 @@ function parseMinorUnits(raw, flag) {
|
|
|
1162
1107
|
}
|
|
1163
1108
|
return { ok: true, value };
|
|
1164
1109
|
}
|
|
1110
|
+
function positiveWholeNumber(raw, flag) {
|
|
1111
|
+
const parsed = parseMinorUnits(raw, flag);
|
|
1112
|
+
if (!parsed.ok || parsed.value < 1) throw new Error(`${flag} must be a whole number of at least 1; got ${JSON.stringify(raw)}.`);
|
|
1113
|
+
return parsed.value;
|
|
1114
|
+
}
|
|
1165
1115
|
function minorUnitsOrExit(raw, flag) {
|
|
1166
1116
|
const parsed = parseMinorUnits(raw, flag);
|
|
1167
1117
|
if (!parsed.ok) exitWithCliError(parsed.message, `Error: ${parsed.message}`);
|
|
@@ -1172,9 +1122,7 @@ function optionalMinorUnitsOrExit(raw, flag) {
|
|
|
1172
1122
|
}
|
|
1173
1123
|
|
|
1174
1124
|
// src/utils/mask-secrets.ts
|
|
1175
|
-
|
|
1176
|
-
return scrubSecrets(json);
|
|
1177
|
-
}
|
|
1125
|
+
var SHOW_SECRETS_CLEANING = { exactKeys: /* @__PURE__ */ new Set(["clientSecret"]) };
|
|
1178
1126
|
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
1127
|
function withClientSecret(session) {
|
|
1180
1128
|
if (session === null || typeof session !== "object" || Array.isArray(session)) return session;
|
|
@@ -1182,9 +1130,9 @@ function withClientSecret(session) {
|
|
|
1182
1130
|
return { ...session, clientSecret: session.clientSecret };
|
|
1183
1131
|
}
|
|
1184
1132
|
function sessionJson(session, showSecrets) {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
return
|
|
1133
|
+
const body = cleanJsonValue(withClientSecret(session), showSecrets === true ? SHOW_SECRETS_CLEANING : {});
|
|
1134
|
+
const json = JSON.stringify(body, null, 2);
|
|
1135
|
+
return json === void 0 ? String(json) : json;
|
|
1188
1136
|
}
|
|
1189
1137
|
|
|
1190
1138
|
// src/commands/sessions.ts
|
|
@@ -1284,7 +1232,7 @@ function sessionsCommand() {
|
|
|
1284
1232
|
const result = await client.sessions.validate(params);
|
|
1285
1233
|
spinner.stop();
|
|
1286
1234
|
if (opts.json) {
|
|
1287
|
-
console.log(
|
|
1235
|
+
console.log(jsonOutput(result));
|
|
1288
1236
|
} else if (result.valid) {
|
|
1289
1237
|
console.log(chalk6.green("\u2713 Valid request"));
|
|
1290
1238
|
if (result.warnings.length > 0) {
|
|
@@ -1342,6 +1290,7 @@ import { formatMinorUnits as formatMinorUnits3 } from "@vonpay/checkout-node";
|
|
|
1342
1290
|
// src/utils/idempotency.ts
|
|
1343
1291
|
import { randomUUID } from "crypto";
|
|
1344
1292
|
import chalk7 from "chalk";
|
|
1293
|
+
import { heldAsLive as heldAsLive2 } from "@vonpay/checkout-node";
|
|
1345
1294
|
function resolveIdempotencyKey(flag) {
|
|
1346
1295
|
const given = flag?.trim();
|
|
1347
1296
|
if (given) return { key: given, generated: false };
|
|
@@ -1349,7 +1298,7 @@ function resolveIdempotencyKey(flag) {
|
|
|
1349
1298
|
}
|
|
1350
1299
|
function idempotencyKeyFor(flag, what, apiKey) {
|
|
1351
1300
|
const { key, generated } = resolveIdempotencyKey(flag);
|
|
1352
|
-
if (generated && apiKey !== void 0 &&
|
|
1301
|
+
if (generated && apiKey !== void 0 && heldAsLive2(apiKey)) {
|
|
1353
1302
|
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
1303
|
auditNote({ outcome: "refused_local", code: "idempotency_key_required" });
|
|
1355
1304
|
exitWithCliError(message, chalk7.red(message));
|
|
@@ -1465,11 +1414,6 @@ function buildFilters(opts) {
|
|
|
1465
1414
|
}
|
|
1466
1415
|
return filters;
|
|
1467
1416
|
}
|
|
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
1417
|
var PAYMENT_LIST_JSON_FIELDS = [
|
|
1474
1418
|
"id",
|
|
1475
1419
|
"status",
|
|
@@ -1492,17 +1436,20 @@ function listItemJson(pi, includeMetadata) {
|
|
|
1492
1436
|
const src = pi;
|
|
1493
1437
|
return Object.fromEntries(PAYMENT_LIST_JSON_FIELDS.filter((k) => src[k] !== void 0).map((k) => [k, src[k]]));
|
|
1494
1438
|
}
|
|
1495
|
-
var
|
|
1439
|
+
var ALL_DEFAULT_MAX_ITEMS = 1e4;
|
|
1440
|
+
function stoppedAtDefaultCap() {
|
|
1441
|
+
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.`;
|
|
1442
|
+
}
|
|
1496
1443
|
function buildListParams(opts) {
|
|
1497
1444
|
const params = buildFilters(opts);
|
|
1498
1445
|
if (opts.status !== void 0) {
|
|
1499
1446
|
params.status = parseListFlag(opts.status, "--status", { lowercase: true });
|
|
1500
1447
|
}
|
|
1501
|
-
if (opts.limit !== void 0) params.limit =
|
|
1448
|
+
if (opts.limit !== void 0) params.limit = positiveWholeNumber(opts.limit, "--limit");
|
|
1502
1449
|
if (opts.startingAfter !== void 0) params.startingAfter = opts.startingAfter;
|
|
1503
1450
|
if (opts.maxItems !== void 0 && !opts.all) throw new Error("--max-items is used with --all.");
|
|
1504
1451
|
if (opts.includeMetadata && !opts.json) throw new Error("--include-metadata is used with --json.");
|
|
1505
|
-
const maxItems = opts.maxItems !== void 0 ?
|
|
1452
|
+
const maxItems = opts.maxItems !== void 0 ? positiveWholeNumber(opts.maxItems, "--max-items") : opts.all ? ALL_DEFAULT_MAX_ITEMS : void 0;
|
|
1506
1453
|
return { params, ...maxItems !== void 0 ? { maxItems } : {} };
|
|
1507
1454
|
}
|
|
1508
1455
|
function buildSummaryParams(opts) {
|
|
@@ -1587,7 +1534,10 @@ function addPaymentReportCommands(intents) {
|
|
|
1587
1534
|
).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
1535
|
"--starting-after <cursor>",
|
|
1589
1536
|
"The next-page cursor printed by a previous run, unchanged, with exactly the same filters and window (it lasts 24 hours)"
|
|
1590
|
-
).option(
|
|
1537
|
+
).option(
|
|
1538
|
+
"--all",
|
|
1539
|
+
`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)`
|
|
1540
|
+
).option("--max-items <n>", `With --all: stop after this many payments (default ${ALL_DEFAULT_MAX_ITEMS})`).option(
|
|
1591
1541
|
"--json",
|
|
1592
1542
|
"Output JSON: each payment's status, outcome, amount, card, store and decline fields \u2014 not your metadata (with --all: one JSON array)"
|
|
1593
1543
|
).option("--include-metadata", "With --json: print every field of each payment, your metadata included").action(async (opts) => {
|
|
@@ -1609,12 +1559,8 @@ function addPaymentReportCommands(intents) {
|
|
|
1609
1559
|
const items = [];
|
|
1610
1560
|
for await (const pi of pager) items.push(listItemJson(pi, opts.includeMetadata));
|
|
1611
1561
|
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
|
-
}
|
|
1562
|
+
console.log(jsonOutput(items));
|
|
1563
|
+
if (opts.maxItems === void 0 && items.length >= ALL_DEFAULT_MAX_ITEMS) console.error(stoppedAtDefaultCap());
|
|
1618
1564
|
return;
|
|
1619
1565
|
}
|
|
1620
1566
|
let count = 0;
|
|
@@ -1626,12 +1572,13 @@ function addPaymentReportCommands(intents) {
|
|
|
1626
1572
|
spinner.stop();
|
|
1627
1573
|
if (count === 0) console.log(" No payments match.");
|
|
1628
1574
|
console.log(` ${count} payment(s).`);
|
|
1575
|
+
if (opts.maxItems === void 0 && count >= ALL_DEFAULT_MAX_ITEMS) console.error(stoppedAtDefaultCap());
|
|
1629
1576
|
return;
|
|
1630
1577
|
}
|
|
1631
1578
|
const page = await client.paymentIntents.list(built.params);
|
|
1632
1579
|
spinner.stop();
|
|
1633
1580
|
if (opts.json) {
|
|
1634
|
-
console.log(
|
|
1581
|
+
console.log(jsonOutput({ ...page, data: page.data.map((pi) => listItemJson(pi, opts.includeMetadata)) }));
|
|
1635
1582
|
return;
|
|
1636
1583
|
}
|
|
1637
1584
|
for (const line of paymentListLines(page)) console.log(line);
|
|
@@ -1660,7 +1607,7 @@ function addPaymentReportCommands(intents) {
|
|
|
1660
1607
|
const summary = await client.paymentIntents.summary(params);
|
|
1661
1608
|
spinner.stop();
|
|
1662
1609
|
if (opts.json) {
|
|
1663
|
-
console.log(
|
|
1610
|
+
console.log(jsonOutput(summary));
|
|
1664
1611
|
return;
|
|
1665
1612
|
}
|
|
1666
1613
|
for (const line of summaryLines(summary)) console.log(line);
|
|
@@ -1849,7 +1796,7 @@ function paymentIntentsCommand() {
|
|
|
1849
1796
|
});
|
|
1850
1797
|
spinner.stop();
|
|
1851
1798
|
if (opts.json) {
|
|
1852
|
-
console.log(
|
|
1799
|
+
console.log(jsonOutput(intent));
|
|
1853
1800
|
} else {
|
|
1854
1801
|
console.log(chalk9.green("\u2713 Payment intent created"));
|
|
1855
1802
|
printPaymentIntent(intent);
|
|
@@ -1868,7 +1815,7 @@ function paymentIntentsCommand() {
|
|
|
1868
1815
|
const intent = await client.paymentIntents.retrieve(id);
|
|
1869
1816
|
spinner.stop();
|
|
1870
1817
|
if (opts.json) {
|
|
1871
|
-
console.log(
|
|
1818
|
+
console.log(jsonOutput(intent));
|
|
1872
1819
|
} else {
|
|
1873
1820
|
printPaymentIntent(intent);
|
|
1874
1821
|
}
|
|
@@ -1918,7 +1865,7 @@ function paymentIntentsCommand() {
|
|
|
1918
1865
|
);
|
|
1919
1866
|
spinner.stop();
|
|
1920
1867
|
if (opts.json) {
|
|
1921
|
-
console.log(
|
|
1868
|
+
console.log(jsonOutput(intent));
|
|
1922
1869
|
} else {
|
|
1923
1870
|
const captureLabel = amountToCapture !== void 0 ? ` (partial: ${formatMinorUnits3(amountToCapture, intent.currency)})` : "";
|
|
1924
1871
|
console.log(chalk9.green(`\u2713 Payment intent captured${captureLabel}`));
|
|
@@ -1957,7 +1904,7 @@ function paymentIntentsCommand() {
|
|
|
1957
1904
|
const intent = await client.paymentIntents.void(id, { idempotencyKey });
|
|
1958
1905
|
spinner.stop();
|
|
1959
1906
|
if (opts.json) {
|
|
1960
|
-
console.log(
|
|
1907
|
+
console.log(jsonOutput(intent));
|
|
1961
1908
|
} else {
|
|
1962
1909
|
console.log(chalk9.green("\u2713 Payment intent voided"));
|
|
1963
1910
|
printPaymentIntent(intent);
|
|
@@ -1975,7 +1922,7 @@ function paymentIntentsCommand() {
|
|
|
1975
1922
|
// src/commands/refunds.ts
|
|
1976
1923
|
import { Command as Command6 } from "commander";
|
|
1977
1924
|
import chalk10 from "chalk";
|
|
1978
|
-
import { formatMinorUnits as formatMinorUnits4, VonPayError as VonPayError2 } from "@vonpay/checkout-node";
|
|
1925
|
+
import { formatMinorUnits as formatMinorUnits4, heldAsLive as heldAsLive3, VonPayError as VonPayError2 } from "@vonpay/checkout-node";
|
|
1979
1926
|
function present(v) {
|
|
1980
1927
|
return v !== void 0 && v.trim() !== "";
|
|
1981
1928
|
}
|
|
@@ -2137,11 +2084,11 @@ function refundsCommand() {
|
|
|
2137
2084
|
handleError(err);
|
|
2138
2085
|
}
|
|
2139
2086
|
const target = resolveRefundTarget(opts);
|
|
2140
|
-
const sessionProblem = refundSessionProblem(target, opts.sessionId,
|
|
2087
|
+
const sessionProblem = refundSessionProblem(target, opts.sessionId, heldAsLive3(apiKey));
|
|
2141
2088
|
if (sessionProblem) exitWithCliError(sessionProblem, chalk10.red(sessionProblem));
|
|
2142
2089
|
refuseUnsafeLiveDestination(apiKey);
|
|
2143
2090
|
const idempotencyKey = idempotencyKeyFor(opts.idempotencyKey, "refund", apiKey);
|
|
2144
|
-
const lookedUp =
|
|
2091
|
+
const lookedUp = heldAsLive3(apiKey) && terminal.isInteractive() ? await refundLookupDetails(makeCheckoutClient(), target, opts.sessionId?.trim(), params.amount, params.currency) : [];
|
|
2145
2092
|
await confirmLiveAction(
|
|
2146
2093
|
apiKey,
|
|
2147
2094
|
{
|
|
@@ -2166,7 +2113,7 @@ function refundsCommand() {
|
|
|
2166
2113
|
const refund = await client.refunds.create(params, { idempotencyKey });
|
|
2167
2114
|
spinner.stop();
|
|
2168
2115
|
if (opts.json) {
|
|
2169
|
-
console.log(
|
|
2116
|
+
console.log(jsonOutput(refund));
|
|
2170
2117
|
} else {
|
|
2171
2118
|
console.log(chalk10.green("\u2713 Refund created"));
|
|
2172
2119
|
printRefund(refund);
|
|
@@ -2178,7 +2125,7 @@ function refundsCommand() {
|
|
|
2178
2125
|
});
|
|
2179
2126
|
refunds.command("list").description(
|
|
2180
2127
|
"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
|
|
2128
|
+
).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
2129
|
async (opts) => {
|
|
2183
2130
|
let target;
|
|
2184
2131
|
try {
|
|
@@ -2189,12 +2136,12 @@ function refundsCommand() {
|
|
|
2189
2136
|
}
|
|
2190
2137
|
let limit;
|
|
2191
2138
|
if (opts.limit !== void 0) {
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2139
|
+
try {
|
|
2140
|
+
limit = positiveWholeNumber(opts.limit, "--limit");
|
|
2141
|
+
} catch (err) {
|
|
2142
|
+
const message = err.message;
|
|
2195
2143
|
exitWithCliError(message, chalk10.red(`Error: ${message}`));
|
|
2196
2144
|
}
|
|
2197
|
-
limit = parsed.value;
|
|
2198
2145
|
}
|
|
2199
2146
|
const client = makeCheckoutClient();
|
|
2200
2147
|
const spinner = startSpinner("Listing refunds...");
|
|
@@ -2205,7 +2152,7 @@ function refundsCommand() {
|
|
|
2205
2152
|
});
|
|
2206
2153
|
spinner.stop();
|
|
2207
2154
|
if (opts.json) {
|
|
2208
|
-
console.log(
|
|
2155
|
+
console.log(jsonOutput(list));
|
|
2209
2156
|
return;
|
|
2210
2157
|
}
|
|
2211
2158
|
for (const line of refundListLines(list)) console.log(line);
|
|
@@ -2219,10 +2166,10 @@ function refundsCommand() {
|
|
|
2219
2166
|
}
|
|
2220
2167
|
function refundListLines(list) {
|
|
2221
2168
|
const currency = list.data[0]?.currency;
|
|
2222
|
-
const
|
|
2169
|
+
const money2 = (n) => currency ? formatMinorUnits4(n, currency) : `${n} in minor units`;
|
|
2223
2170
|
const lines = [
|
|
2224
|
-
` Refunded so far: ${
|
|
2225
|
-
` Still refundable: ${
|
|
2171
|
+
` Refunded so far: ${money2(list.amountRefunded)} (succeeded + still in progress)`,
|
|
2172
|
+
` Still refundable: ${money2(list.remainingRefundable)}${list.refundable ? "" : " \u2014 this payment cannot be refunded now"}`
|
|
2226
2173
|
];
|
|
2227
2174
|
if (list.data.length === 0) {
|
|
2228
2175
|
lines.push(" No refunds on this payment.");
|
|
@@ -2292,7 +2239,7 @@ function tokensCommand() {
|
|
|
2292
2239
|
);
|
|
2293
2240
|
spinner.stop();
|
|
2294
2241
|
if (opts.json) {
|
|
2295
|
-
console.log(
|
|
2242
|
+
console.log(jsonOutput(token));
|
|
2296
2243
|
} else {
|
|
2297
2244
|
console.log(chalk11.green("\u2713 Token created"));
|
|
2298
2245
|
printToken(token);
|
|
@@ -2311,35 +2258,166 @@ function formatTokenCard(card) {
|
|
|
2311
2258
|
return { card: cardLine, expires };
|
|
2312
2259
|
}
|
|
2313
2260
|
|
|
2314
|
-
// src/commands/
|
|
2261
|
+
// src/commands/subscription-products.ts
|
|
2315
2262
|
import { Command as Command8 } from "commander";
|
|
2316
2263
|
import chalk12 from "chalk";
|
|
2264
|
+
import { formatMinorUnits as formatMinorUnits5 } from "@vonpay/checkout-node";
|
|
2265
|
+
var STATUSES = ["active", "archived"];
|
|
2266
|
+
function buildListParams2(opts) {
|
|
2267
|
+
const params = {};
|
|
2268
|
+
if (opts.productRef !== void 0) params.productRef = opts.productRef;
|
|
2269
|
+
if (opts.status !== void 0) {
|
|
2270
|
+
const status = opts.status.trim().toLowerCase();
|
|
2271
|
+
if (!STATUSES.includes(status)) {
|
|
2272
|
+
throw new Error(`--status must be active or archived; got ${JSON.stringify(opts.status)}.`);
|
|
2273
|
+
}
|
|
2274
|
+
params.status = status;
|
|
2275
|
+
}
|
|
2276
|
+
if (opts.limit !== void 0) params.limit = positiveWholeNumber(opts.limit, "--limit");
|
|
2277
|
+
if (opts.startingAfter !== void 0) params.startingAfter = opts.startingAfter;
|
|
2278
|
+
return params;
|
|
2279
|
+
}
|
|
2280
|
+
function buildTermsParams(opts) {
|
|
2281
|
+
if (opts.amount === void 0) return void 0;
|
|
2282
|
+
const parsed = parseMinorUnits(opts.amount, "--amount");
|
|
2283
|
+
if (!parsed.ok) throw new Error(parsed.message);
|
|
2284
|
+
if (parsed.value < 1) throw new Error(`--amount must be at least 1 (minor units); got ${JSON.stringify(opts.amount)}.`);
|
|
2285
|
+
return { amount: parsed.value };
|
|
2286
|
+
}
|
|
2287
|
+
var LEGAL_TEXT_MAX_CHARS = 500;
|
|
2288
|
+
var CUT_MARK = "\u2026 [CUT: not the full text; use --json to read all of it]";
|
|
2289
|
+
function legalText(raw) {
|
|
2290
|
+
const clean = safeInline(raw, Number.MAX_SAFE_INTEGER);
|
|
2291
|
+
return clean.length > LEGAL_TEXT_MAX_CHARS ? { text: `${clean.slice(0, LEGAL_TEXT_MAX_CHARS)}${CUT_MARK}`, cut: true } : { text: clean, cut: false };
|
|
2292
|
+
}
|
|
2293
|
+
function money(amount, currency) {
|
|
2294
|
+
if (typeof amount !== "number") return "?";
|
|
2295
|
+
return currency ? safeInline(formatMinorUnits5(amount, currency)) : `${amount} in minor units`;
|
|
2296
|
+
}
|
|
2297
|
+
function every(count, interval) {
|
|
2298
|
+
if (!interval) return "";
|
|
2299
|
+
const unit = safeInline(interval);
|
|
2300
|
+
return typeof count === "number" && count !== 1 ? `every ${count} ${unit}s` : `every ${unit}`;
|
|
2301
|
+
}
|
|
2302
|
+
function statusTone2(status) {
|
|
2303
|
+
return status === "active" ? chalk12.green(status) : chalk12.yellow(status);
|
|
2304
|
+
}
|
|
2305
|
+
function productLines(p) {
|
|
2306
|
+
const head = ` ${safeInline(p.id)} ${statusTone2(safeInline(p.status))} ${safeInline(p.name, 120)} (ref ${safeInline(p.productRef, 128)})`;
|
|
2307
|
+
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)`;
|
|
2308
|
+
const lines = [head, ` ${schedule}`];
|
|
2309
|
+
const terms = p.terms;
|
|
2310
|
+
if (terms && typeof terms.disclosure === "string") lines.push(` Disclosure: ${legalText(terms.disclosure).text}`);
|
|
2311
|
+
return lines;
|
|
2312
|
+
}
|
|
2313
|
+
function productListLines(page) {
|
|
2314
|
+
const lines = [];
|
|
2315
|
+
if (page.data.length === 0) lines.push(" No subscription products match.");
|
|
2316
|
+
for (const p of page.data) lines.push(...productLines(p));
|
|
2317
|
+
if (page.hasMore && page.nextCursor) {
|
|
2318
|
+
lines.push(` More: re-run with the same filters and --starting-after ${safeInline(page.nextCursor)}`);
|
|
2319
|
+
}
|
|
2320
|
+
return lines;
|
|
2321
|
+
}
|
|
2322
|
+
function termsLines(t) {
|
|
2323
|
+
const disclosure = legalText(t.disclosure);
|
|
2324
|
+
const phrase = legalText(t.pricePhrase);
|
|
2325
|
+
return [
|
|
2326
|
+
` Product: ${safeInline(t.product)} (ref ${safeInline(t.productRef, 128)})`,
|
|
2327
|
+
` Disclosure: ${disclosure.text}`,
|
|
2328
|
+
` Price phrase: ${phrase.text}`,
|
|
2329
|
+
` First payment: ${money(t.firstAmount, t.currency)}`,
|
|
2330
|
+
` Renewal: ${money(t.amount, t.currency)} ${every(t.intervalCount, t.interval)}`,
|
|
2331
|
+
` Terms version: ${safeInline(t.termsVersion)}`,
|
|
2332
|
+
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."
|
|
2333
|
+
];
|
|
2334
|
+
}
|
|
2335
|
+
function exitOnBadFlag2(err) {
|
|
2336
|
+
const message = err.message;
|
|
2337
|
+
exitWithCliError(message, chalk12.red(`Error: ${message}`));
|
|
2338
|
+
}
|
|
2339
|
+
function subscriptionProductsCommand() {
|
|
2340
|
+
const products = new Command8("subscription-products").description(
|
|
2341
|
+
"Read your subscription products and the renewal terms a sign-up states (read-only; products are created and changed in the dashboard)"
|
|
2342
|
+
);
|
|
2343
|
+
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) => {
|
|
2344
|
+
let params;
|
|
2345
|
+
try {
|
|
2346
|
+
params = buildListParams2(opts);
|
|
2347
|
+
} catch (err) {
|
|
2348
|
+
exitOnBadFlag2(err);
|
|
2349
|
+
}
|
|
2350
|
+
const client = makeCheckoutClient();
|
|
2351
|
+
const spinner = startSpinner("Listing subscription products...");
|
|
2352
|
+
try {
|
|
2353
|
+
const page = await client.subscriptionProducts.list(params);
|
|
2354
|
+
spinner.stop();
|
|
2355
|
+
if (opts.json) {
|
|
2356
|
+
console.log(jsonOutput(page));
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
for (const line of productListLines(page)) console.log(line);
|
|
2360
|
+
} catch (err) {
|
|
2361
|
+
spinner.stop();
|
|
2362
|
+
handleError(err);
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
products.command("terms <product>").description(
|
|
2366
|
+
"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"
|
|
2367
|
+
).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) => {
|
|
2368
|
+
let params;
|
|
2369
|
+
try {
|
|
2370
|
+
params = buildTermsParams(opts);
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
exitOnBadFlag2(err);
|
|
2373
|
+
}
|
|
2374
|
+
const client = makeCheckoutClient();
|
|
2375
|
+
const spinner = startSpinner("Reading subscription product terms...");
|
|
2376
|
+
try {
|
|
2377
|
+
const terms = await client.subscriptionProducts.terms(product, params);
|
|
2378
|
+
spinner.stop();
|
|
2379
|
+
if (opts.json) {
|
|
2380
|
+
console.log(jsonOutput(terms));
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
for (const line of termsLines(terms)) console.log(line);
|
|
2384
|
+
} catch (err) {
|
|
2385
|
+
spinner.stop();
|
|
2386
|
+
handleError(err);
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
return products;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/commands/capabilities.ts
|
|
2393
|
+
import { Command as Command9 } from "commander";
|
|
2394
|
+
import chalk13 from "chalk";
|
|
2317
2395
|
function fmtBool(v) {
|
|
2318
|
-
return v ?
|
|
2396
|
+
return v ? chalk13.green("yes") : chalk13.dim("no");
|
|
2319
2397
|
}
|
|
2320
2398
|
function fmtVoidPolicy(v) {
|
|
2321
2399
|
switch (v) {
|
|
2322
2400
|
case "supported":
|
|
2323
|
-
return
|
|
2401
|
+
return chalk13.green(v);
|
|
2324
2402
|
case "rerouted_to_refund":
|
|
2325
|
-
return
|
|
2403
|
+
return chalk13.yellow(v);
|
|
2326
2404
|
case "not_supported":
|
|
2327
|
-
return
|
|
2405
|
+
return chalk13.dim(v);
|
|
2328
2406
|
default:
|
|
2329
|
-
return
|
|
2407
|
+
return chalk13.dim(String(v));
|
|
2330
2408
|
}
|
|
2331
2409
|
}
|
|
2332
2410
|
function fmtDisputeReporting(v) {
|
|
2333
|
-
return v === "tracked" ?
|
|
2411
|
+
return v === "tracked" ? chalk13.green(v) : chalk13.yellow(v);
|
|
2334
2412
|
}
|
|
2335
2413
|
function fmtSubscriptionBilling(v) {
|
|
2336
|
-
if (v === true) return
|
|
2337
|
-
if (v === false) return
|
|
2338
|
-
return
|
|
2414
|
+
if (v === true) return chalk13.yellow("yes");
|
|
2415
|
+
if (v === false) return chalk13.green("no");
|
|
2416
|
+
return chalk13.yellow("unknown");
|
|
2339
2417
|
}
|
|
2340
2418
|
function printCapabilities(caps) {
|
|
2341
2419
|
const ops = caps.supportedOperations;
|
|
2342
|
-
console.log(
|
|
2420
|
+
console.log(chalk13.bold("Supported operations"));
|
|
2343
2421
|
console.log(` auth/capture separation: ${fmtBool(ops.authCaptureSeparation)}`);
|
|
2344
2422
|
console.log(` partial capture: ${fmtBool(ops.partialCapture)}`);
|
|
2345
2423
|
console.log(` partial refund: ${fmtBool(ops.partialRefund)}`);
|
|
@@ -2354,17 +2432,17 @@ function printCapabilities(caps) {
|
|
|
2354
2432
|
console.log();
|
|
2355
2433
|
if (ops.disputeReporting !== "tracked") {
|
|
2356
2434
|
console.log(
|
|
2357
|
-
|
|
2435
|
+
chalk13.yellow(
|
|
2358
2436
|
" \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
2437
|
)
|
|
2360
2438
|
);
|
|
2361
2439
|
console.log();
|
|
2362
2440
|
}
|
|
2363
|
-
console.log(
|
|
2441
|
+
console.log(chalk13.bold("Subscription billing"));
|
|
2364
2442
|
console.log(` Von Pay renews for you: ${fmtSubscriptionBilling(caps.subscriptionBilling?.enabled)}`);
|
|
2365
2443
|
if (caps.subscriptionBilling?.enabled !== false) {
|
|
2366
2444
|
console.log(
|
|
2367
|
-
|
|
2445
|
+
chalk13.yellow(
|
|
2368
2446
|
` \u26A0 Do not run renewals from your own system while this is not "no":
|
|
2369
2447
|
Von Pay may be charging these customers' renewals itself, and both
|
|
2370
2448
|
would charge them. Check again at the moment you renew.`
|
|
@@ -2372,25 +2450,25 @@ function printCapabilities(caps) {
|
|
|
2372
2450
|
);
|
|
2373
2451
|
}
|
|
2374
2452
|
console.log();
|
|
2375
|
-
console.log(
|
|
2453
|
+
console.log(chalk13.bold("Settlement currencies"));
|
|
2376
2454
|
console.log(
|
|
2377
|
-
` ${caps.settlementCurrencies.length ? caps.settlementCurrencies.join(", ") :
|
|
2455
|
+
` ${caps.settlementCurrencies.length ? caps.settlementCurrencies.join(", ") : chalk13.dim("(none reported)")}`
|
|
2378
2456
|
);
|
|
2379
2457
|
console.log();
|
|
2380
|
-
console.log(
|
|
2458
|
+
console.log(chalk13.bold("Rate limits"));
|
|
2381
2459
|
console.log(
|
|
2382
2460
|
` payment intents / minute: ${caps.rateLimits.paymentIntentsPerMinute}`
|
|
2383
2461
|
);
|
|
2384
2462
|
}
|
|
2385
2463
|
function capabilitiesCommand() {
|
|
2386
|
-
return new
|
|
2464
|
+
return new Command9("capabilities").description("Show what this merchant's binder supports").option("--json", "Output raw JSON").action(async (opts) => {
|
|
2387
2465
|
const client = makeCheckoutClient();
|
|
2388
2466
|
const spinner = startSpinner("Fetching capabilities...");
|
|
2389
2467
|
try {
|
|
2390
2468
|
const caps = await client.capabilities.get();
|
|
2391
2469
|
spinner.stop();
|
|
2392
2470
|
if (opts.json) {
|
|
2393
|
-
console.log(
|
|
2471
|
+
console.log(jsonOutput(caps));
|
|
2394
2472
|
} else {
|
|
2395
2473
|
printCapabilities(caps);
|
|
2396
2474
|
}
|
|
@@ -2402,9 +2480,9 @@ function capabilitiesCommand() {
|
|
|
2402
2480
|
}
|
|
2403
2481
|
|
|
2404
2482
|
// src/commands/trigger.ts
|
|
2405
|
-
import { Command as
|
|
2483
|
+
import { Command as Command10 } from "commander";
|
|
2406
2484
|
import { createHmac as createHmac2, randomUUID as randomUUID2 } from "crypto";
|
|
2407
|
-
import
|
|
2485
|
+
import chalk14 from "chalk";
|
|
2408
2486
|
import ora2 from "ora";
|
|
2409
2487
|
|
|
2410
2488
|
// src/utils/signing-secret.ts
|
|
@@ -2622,7 +2700,7 @@ function warnIfUnsubscribable(event, write = (m) => console.warn(m)) {
|
|
|
2622
2700
|
if (!UNSUBSCRIBABLE_EVENTS.includes(event)) return false;
|
|
2623
2701
|
const suggestion = event === "session.succeeded" ? "charge.succeeded" : "charge.failed";
|
|
2624
2702
|
write(
|
|
2625
|
-
|
|
2703
|
+
chalk14.yellow(
|
|
2626
2704
|
`\u26A0 ${event} is emitted internally but is NOT subscribable.
|
|
2627
2705
|
The subscription API accepts the key, stores nothing and returns success, so a
|
|
2628
2706
|
real endpoint receives this event NEVER \u2014 a handler tested only against it is
|
|
@@ -2817,7 +2895,7 @@ function buildPayload(event, opts) {
|
|
|
2817
2895
|
};
|
|
2818
2896
|
}
|
|
2819
2897
|
function triggerCommand() {
|
|
2820
|
-
return new
|
|
2898
|
+
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
2899
|
"--url-tunnel",
|
|
2822
2900
|
"Allow a non-loopback --url (ngrok / Cloudflare Tunnel). Without it, --url must be this machine"
|
|
2823
2901
|
).option(
|
|
@@ -2826,7 +2904,7 @@ function triggerCommand() {
|
|
|
2826
2904
|
).addOption(retiredConfirmLiveOption()).action(async (event, opts) => {
|
|
2827
2905
|
if (!SUPPORTED_EVENTS.includes(event)) {
|
|
2828
2906
|
console.error(
|
|
2829
|
-
|
|
2907
|
+
chalk14.red(
|
|
2830
2908
|
`Unknown event: ${event}
|
|
2831
2909
|
Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
2832
2910
|
)
|
|
@@ -2840,10 +2918,10 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2840
2918
|
tunnelFlag: "--url-tunnel"
|
|
2841
2919
|
});
|
|
2842
2920
|
if (!destination.ok) {
|
|
2843
|
-
console.error(
|
|
2921
|
+
console.error(chalk14.red(destination.reason));
|
|
2844
2922
|
process.exit(1);
|
|
2845
2923
|
}
|
|
2846
|
-
if (destination.warning) console.warn(
|
|
2924
|
+
if (destination.warning) console.warn(chalk14.yellow(destination.warning));
|
|
2847
2925
|
const amount = optionalMinorUnitsOrExit(
|
|
2848
2926
|
opts.amount === void 0 ? void 0 : String(opts.amount),
|
|
2849
2927
|
"--amount"
|
|
@@ -2851,7 +2929,7 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2851
2929
|
const apiKey = requireApiKey();
|
|
2852
2930
|
const signing = resolveSigningSecret(opts.signingSecret, process.env, apiKey);
|
|
2853
2931
|
if ("error" in signing) {
|
|
2854
|
-
console.error(
|
|
2932
|
+
console.error(chalk14.red(signing.error));
|
|
2855
2933
|
process.exit(1);
|
|
2856
2934
|
}
|
|
2857
2935
|
await confirmLiveAction(
|
|
@@ -2868,7 +2946,7 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2868
2946
|
);
|
|
2869
2947
|
const payload = buildPayload(event, { ...opts, amount });
|
|
2870
2948
|
const body = JSON.stringify(payload);
|
|
2871
|
-
console.log(
|
|
2949
|
+
console.log(chalk14.dim(signingSecretNotice(signing)));
|
|
2872
2950
|
const t = Math.floor(Date.now() / 1e3);
|
|
2873
2951
|
const v1 = createHmac2("sha256", signing.secret).update(`${t}.${body}`).digest("hex");
|
|
2874
2952
|
const signatureHeader = `t=${t},v1=${v1}`;
|
|
@@ -2890,45 +2968,45 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2890
2968
|
spinner.stop();
|
|
2891
2969
|
if (isRedirect(response)) {
|
|
2892
2970
|
console.error(
|
|
2893
|
-
|
|
2971
|
+
chalk14.red(
|
|
2894
2972
|
`\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
2973
|
)
|
|
2896
2974
|
);
|
|
2897
2975
|
process.exit(1);
|
|
2898
2976
|
}
|
|
2899
2977
|
if (response.ok) {
|
|
2900
|
-
console.log(
|
|
2978
|
+
console.log(chalk14.green(`\u2713 ${event} delivered \u2192 ${response.status}`));
|
|
2901
2979
|
} else {
|
|
2902
2980
|
console.log(
|
|
2903
|
-
|
|
2981
|
+
chalk14.yellow(
|
|
2904
2982
|
`\u26A0 ${event} delivered but got ${response.status} response`
|
|
2905
2983
|
)
|
|
2906
2984
|
);
|
|
2907
2985
|
}
|
|
2908
|
-
console.log(
|
|
2986
|
+
console.log(chalk14.dim(` Event: ${payload.id} (${payload.type})`));
|
|
2909
2987
|
console.log(
|
|
2910
|
-
|
|
2988
|
+
chalk14.dim(
|
|
2911
2989
|
` Amount: ${payload.data.amount} ${payload.data.currency}`
|
|
2912
2990
|
)
|
|
2913
2991
|
);
|
|
2914
2992
|
console.log(
|
|
2915
|
-
|
|
2993
|
+
chalk14.dim(
|
|
2916
2994
|
`
|
|
2917
2995
|
Verify with: vonpay.webhooks.constructEvent(rawBody, signatureHeader, secret)`
|
|
2918
2996
|
)
|
|
2919
2997
|
);
|
|
2920
2998
|
console.log(
|
|
2921
|
-
|
|
2999
|
+
chalk14.dim(
|
|
2922
3000
|
` ('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
3001
|
)
|
|
2924
3002
|
);
|
|
2925
3003
|
} catch (err) {
|
|
2926
3004
|
spinner.stop();
|
|
2927
3005
|
if (err instanceof Error && err.name === "TimeoutError") {
|
|
2928
|
-
console.error(
|
|
3006
|
+
console.error(chalk14.red(`Timeout: ${opts.url} did not respond within 10s`));
|
|
2929
3007
|
} else {
|
|
2930
3008
|
console.error(
|
|
2931
|
-
|
|
3009
|
+
chalk14.red(
|
|
2932
3010
|
`Failed to deliver: ${err instanceof Error ? err.message : err}`
|
|
2933
3011
|
)
|
|
2934
3012
|
);
|
|
@@ -2939,7 +3017,7 @@ Supported: ${SUPPORTED_EVENTS.join(", ")}`
|
|
|
2939
3017
|
}
|
|
2940
3018
|
|
|
2941
3019
|
// src/commands/listen.ts
|
|
2942
|
-
import { Command as
|
|
3020
|
+
import { Command as Command11 } from "commander";
|
|
2943
3021
|
import { VonPayCheckout as VonPayCheckout2 } from "@vonpay/checkout-node";
|
|
2944
3022
|
|
|
2945
3023
|
// ../sse-parser/dist/index.js
|
|
@@ -3205,14 +3283,14 @@ var SseRedirectError = class extends Error {
|
|
|
3205
3283
|
status;
|
|
3206
3284
|
};
|
|
3207
3285
|
function sleep(ms, signal) {
|
|
3208
|
-
return new Promise((
|
|
3286
|
+
return new Promise((resolve2) => {
|
|
3209
3287
|
const timer = setTimeout(() => {
|
|
3210
3288
|
signal.removeEventListener("abort", onAbort);
|
|
3211
|
-
|
|
3289
|
+
resolve2();
|
|
3212
3290
|
}, ms);
|
|
3213
3291
|
const onAbort = () => {
|
|
3214
3292
|
clearTimeout(timer);
|
|
3215
|
-
|
|
3293
|
+
resolve2();
|
|
3216
3294
|
};
|
|
3217
3295
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
3218
3296
|
});
|
|
@@ -3231,7 +3309,7 @@ function noop() {
|
|
|
3231
3309
|
}
|
|
3232
3310
|
|
|
3233
3311
|
// src/utils/listen-output.ts
|
|
3234
|
-
import
|
|
3312
|
+
import chalk15 from "chalk";
|
|
3235
3313
|
function parseAttemptPayload(data) {
|
|
3236
3314
|
let raw;
|
|
3237
3315
|
try {
|
|
@@ -3248,21 +3326,21 @@ function parseAttemptPayload(data) {
|
|
|
3248
3326
|
}
|
|
3249
3327
|
function formatAttemptHuman(payload, forward) {
|
|
3250
3328
|
const ts = formatTimestamp(payload.timestamp);
|
|
3251
|
-
const eventType =
|
|
3252
|
-
const eventId =
|
|
3253
|
-
const attempt =
|
|
3254
|
-
const arrow =
|
|
3329
|
+
const eventType = chalk15.cyan(safeInline(payload.event_type, 64).padEnd(24));
|
|
3330
|
+
const eventId = chalk15.gray(safeInline(payload.event_id, 64).padEnd(28));
|
|
3331
|
+
const attempt = chalk15.gray(`attempt ${payload.attempt}`);
|
|
3332
|
+
const arrow = chalk15.gray("\u2192");
|
|
3255
3333
|
const upstream = formatUpstream(payload);
|
|
3256
|
-
const latency =
|
|
3257
|
-
const liveBadge = payload.livemode ?
|
|
3334
|
+
const latency = chalk15.gray(`${payload.latency_ms}ms`);
|
|
3335
|
+
const liveBadge = payload.livemode ? chalk15.red(" [LIVE]") : "";
|
|
3258
3336
|
let line = `[${ts}] ${eventType} ${eventId} ${attempt} ${arrow} ${upstream} ${latency}${liveBadge}`;
|
|
3259
3337
|
if (forward && forward.attempted) {
|
|
3260
|
-
const fwdArrow =
|
|
3338
|
+
const fwdArrow = chalk15.gray("\u2192 local");
|
|
3261
3339
|
if (forward.error) {
|
|
3262
|
-
line += ` ${fwdArrow} ${
|
|
3340
|
+
line += ` ${fwdArrow} ${chalk15.red(safeInline(forward.error))}`;
|
|
3263
3341
|
} else if (forward.status !== null) {
|
|
3264
3342
|
const fwdStatus = formatStatus(forward.status);
|
|
3265
|
-
const fwdLatency =
|
|
3343
|
+
const fwdLatency = chalk15.gray(`${forward.latencyMs}ms`);
|
|
3266
3344
|
line += ` ${fwdArrow} ${fwdStatus} ${fwdLatency}`;
|
|
3267
3345
|
}
|
|
3268
3346
|
}
|
|
@@ -3288,24 +3366,27 @@ function formatAttemptJson(payload, forward) {
|
|
|
3288
3366
|
error: forward.error === null ? null : safeInline(forward.error)
|
|
3289
3367
|
};
|
|
3290
3368
|
}
|
|
3291
|
-
return JSON.stringify(body);
|
|
3369
|
+
return JSON.stringify(cleanJsonValue(body));
|
|
3292
3370
|
}
|
|
3371
|
+
var LIFECYCLE_EXACT_KEYS = /* @__PURE__ */ new Set(["signing_secret"]);
|
|
3293
3372
|
function formatLifecycleHuman(message, kind) {
|
|
3294
|
-
const tag = kind === "info" ?
|
|
3373
|
+
const tag = kind === "info" ? chalk15.cyan("[vonpay]") : kind === "warn" ? chalk15.yellow("[vonpay]") : chalk15.red("[vonpay]");
|
|
3295
3374
|
return `${tag} ${message}`;
|
|
3296
3375
|
}
|
|
3297
3376
|
function formatLifecycleJson(kind, extra = {}) {
|
|
3298
|
-
return JSON.stringify(
|
|
3377
|
+
return JSON.stringify(
|
|
3378
|
+
cleanJsonValue({ type: `cli.${kind}`, ...extra, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, { exactKeys: LIFECYCLE_EXACT_KEYS })
|
|
3379
|
+
);
|
|
3299
3380
|
}
|
|
3300
3381
|
function formatStatus(status) {
|
|
3301
|
-
if (status >= 200 && status < 300) return
|
|
3302
|
-
if (status >= 400 && status < 500) return
|
|
3303
|
-
if (status >= 500) return
|
|
3304
|
-
return
|
|
3382
|
+
if (status >= 200 && status < 300) return chalk15.green(String(status));
|
|
3383
|
+
if (status >= 400 && status < 500) return chalk15.yellow(String(status));
|
|
3384
|
+
if (status >= 500) return chalk15.red(String(status));
|
|
3385
|
+
return chalk15.gray(String(status));
|
|
3305
3386
|
}
|
|
3306
3387
|
function formatUpstream(payload) {
|
|
3307
3388
|
if (payload.response_status === null) {
|
|
3308
|
-
return
|
|
3389
|
+
return chalk15.red("timeout");
|
|
3309
3390
|
}
|
|
3310
3391
|
return formatStatus(payload.response_status);
|
|
3311
3392
|
}
|
|
@@ -3320,7 +3401,7 @@ function formatTimestamp(iso) {
|
|
|
3320
3401
|
|
|
3321
3402
|
// src/commands/listen.ts
|
|
3322
3403
|
function listenCommand() {
|
|
3323
|
-
return new
|
|
3404
|
+
return new Command11("listen").description(
|
|
3324
3405
|
"Watch webhook delivery attempts in real time. Optionally re-forward each event to a local URL for development."
|
|
3325
3406
|
).option(
|
|
3326
3407
|
"--forward-to <url>",
|
|
@@ -3568,20 +3649,20 @@ function lifecycleSeverity(kind) {
|
|
|
3568
3649
|
}
|
|
3569
3650
|
|
|
3570
3651
|
// src/commands/health.ts
|
|
3571
|
-
import { Command as
|
|
3572
|
-
import
|
|
3652
|
+
import { Command as Command12 } from "commander";
|
|
3653
|
+
import chalk16 from "chalk";
|
|
3573
3654
|
import { VonPayError as VonPayError3 } from "@vonpay/checkout-node";
|
|
3574
3655
|
function healthCommand() {
|
|
3575
|
-
return new
|
|
3656
|
+
return new Command12("health").description("Check checkout API health").option("--json", "Output raw JSON").action(async (opts) => {
|
|
3576
3657
|
const client = makeCheckoutClient();
|
|
3577
3658
|
const spinner = startSpinner("Checking API health...");
|
|
3578
3659
|
try {
|
|
3579
3660
|
const health = await client.health();
|
|
3580
3661
|
spinner.stop();
|
|
3581
3662
|
if (opts.json) {
|
|
3582
|
-
console.log(
|
|
3663
|
+
console.log(jsonOutput(health));
|
|
3583
3664
|
} else {
|
|
3584
|
-
const statusColor = health.status === "ok" ?
|
|
3665
|
+
const statusColor = health.status === "ok" ? chalk16.green : health.status === "degraded" ? chalk16.yellow : chalk16.red;
|
|
3585
3666
|
console.log(` Status: ${statusColor(safeInline(String(health.status)))}`);
|
|
3586
3667
|
console.log(` Latency: ${safeInline(String(health.latencyMs))}ms`);
|
|
3587
3668
|
if (health.version) {
|
|
@@ -3593,11 +3674,11 @@ function healthCommand() {
|
|
|
3593
3674
|
auditError(err);
|
|
3594
3675
|
if (jsonErrorsEnabled()) handleError(err);
|
|
3595
3676
|
if (err instanceof VonPayError3) {
|
|
3596
|
-
console.error(
|
|
3597
|
-
console.error(
|
|
3677
|
+
console.error(chalk16.red(`API error: ${scrubSecrets(safeInline(err.message, 2e3))}`));
|
|
3678
|
+
console.error(chalk16.dim(` Code: ${safeInline(String(err.code))}`));
|
|
3598
3679
|
} else {
|
|
3599
3680
|
console.error(
|
|
3600
|
-
|
|
3681
|
+
chalk16.red(
|
|
3601
3682
|
`Cannot reach API: ${scrubSecrets(safeInline(err instanceof Error ? err.message : String(err), 2e3))}`
|
|
3602
3683
|
)
|
|
3603
3684
|
);
|
|
@@ -3608,10 +3689,10 @@ function healthCommand() {
|
|
|
3608
3689
|
}
|
|
3609
3690
|
|
|
3610
3691
|
// src/commands/doctor.ts
|
|
3611
|
-
import { Command as
|
|
3612
|
-
import
|
|
3692
|
+
import { Command as Command13 } from "commander";
|
|
3693
|
+
import chalk17 from "chalk";
|
|
3613
3694
|
import ora3 from "ora";
|
|
3614
|
-
import { homedir as
|
|
3695
|
+
import { homedir as homedir2 } from "os";
|
|
3615
3696
|
import { existsSync as existsSync3 } from "fs";
|
|
3616
3697
|
import {
|
|
3617
3698
|
VonPayCheckout as VonPayCheckout3,
|
|
@@ -3627,7 +3708,7 @@ var RELEVANT_ENV_VARS = [
|
|
|
3627
3708
|
"NEXT_PUBLIC_BASE_URL"
|
|
3628
3709
|
];
|
|
3629
3710
|
function redactHome(p) {
|
|
3630
|
-
const home =
|
|
3711
|
+
const home = homedir2();
|
|
3631
3712
|
if (home && p.startsWith(home)) {
|
|
3632
3713
|
return "~" + p.slice(home.length);
|
|
3633
3714
|
}
|
|
@@ -3639,7 +3720,8 @@ function maskPrefix(key) {
|
|
|
3639
3720
|
if (!m) return void 0;
|
|
3640
3721
|
return `${m[1]}${m[3]}\u2026`;
|
|
3641
3722
|
}
|
|
3642
|
-
|
|
3723
|
+
var DOCTOR_JSON_CLEANING = { exactKeys: /* @__PURE__ */ new Set(["apiKeyPrefix"]) };
|
|
3724
|
+
function keyMode2(key) {
|
|
3643
3725
|
if (!key) return void 0;
|
|
3644
3726
|
return secretKeyMode(key) ?? "not_a_secret_key";
|
|
3645
3727
|
}
|
|
@@ -3665,10 +3747,10 @@ async function gather() {
|
|
|
3665
3747
|
present: existsSync3(
|
|
3666
3748
|
`${process.env.HOME ?? process.env.USERPROFILE ?? ""}/.vonpay/config.json`
|
|
3667
3749
|
),
|
|
3668
|
-
keyType: config.checkout?.apiKey ?
|
|
3750
|
+
keyType: config.checkout?.apiKey ? keyMode2(config.checkout?.apiKey) : void 0
|
|
3669
3751
|
},
|
|
3670
3752
|
apiKeyPrefix: maskPrefix(apiKey),
|
|
3671
|
-
apiKeyMode:
|
|
3753
|
+
apiKeyMode: keyMode2(apiKey),
|
|
3672
3754
|
health: { baseUrl, reachable: false },
|
|
3673
3755
|
hints: []
|
|
3674
3756
|
};
|
|
@@ -3793,47 +3875,47 @@ async function gather() {
|
|
|
3793
3875
|
}
|
|
3794
3876
|
function renderHuman(r) {
|
|
3795
3877
|
const lines = [];
|
|
3796
|
-
lines.push(
|
|
3797
|
-
lines.push(
|
|
3878
|
+
lines.push(chalk17.bold.blue("vonpay doctor \u2014 diagnostic report"));
|
|
3879
|
+
lines.push(chalk17.dim(`generated ${r.generatedAt}`));
|
|
3798
3880
|
lines.push("");
|
|
3799
|
-
lines.push(
|
|
3881
|
+
lines.push(chalk17.bold("Runtime"));
|
|
3800
3882
|
lines.push(` CLI: ${r.cli.version}`);
|
|
3801
3883
|
lines.push(` Node: ${r.runtime.node}`);
|
|
3802
3884
|
lines.push(` Platform: ${r.runtime.platform} (${r.runtime.arch})`);
|
|
3803
3885
|
lines.push(` CWD: ${r.runtime.cwd}`);
|
|
3804
3886
|
lines.push("");
|
|
3805
|
-
lines.push(
|
|
3887
|
+
lines.push(chalk17.bold("API key"));
|
|
3806
3888
|
if (r.apiKeyPrefix) {
|
|
3807
3889
|
lines.push(` Prefix: ${r.apiKeyPrefix}`);
|
|
3808
3890
|
lines.push(` Mode: ${r.apiKeyMode}`);
|
|
3809
3891
|
} else {
|
|
3810
|
-
lines.push(
|
|
3892
|
+
lines.push(chalk17.dim(" (no key found in env or ~/.vonpay/config.json)"));
|
|
3811
3893
|
}
|
|
3812
3894
|
lines.push("");
|
|
3813
|
-
lines.push(
|
|
3895
|
+
lines.push(chalk17.bold("Env vars present (names only)"));
|
|
3814
3896
|
if (r.envVarsPresent.length === 0) {
|
|
3815
|
-
lines.push(
|
|
3897
|
+
lines.push(chalk17.dim(" (none of the expected vonpay env vars are set)"));
|
|
3816
3898
|
} else {
|
|
3817
3899
|
for (const n of r.envVarsPresent) lines.push(` \u2713 ${n}`);
|
|
3818
3900
|
}
|
|
3819
3901
|
lines.push("");
|
|
3820
|
-
lines.push(
|
|
3902
|
+
lines.push(chalk17.bold("Health probe"));
|
|
3821
3903
|
if (r.health.reachable) {
|
|
3822
|
-
const statusColor = r.health.status === "ok" ?
|
|
3823
|
-
lines.push(` Reachable: ${
|
|
3904
|
+
const statusColor = r.health.status === "ok" ? chalk17.green : r.health.status === "degraded" ? chalk17.yellow : chalk17.red;
|
|
3905
|
+
lines.push(` Reachable: ${chalk17.green("yes")}`);
|
|
3824
3906
|
lines.push(` Status: ${statusColor(r.health.status ?? "?")}`);
|
|
3825
3907
|
lines.push(` Latency: ${r.health.latencyMs}ms`);
|
|
3826
3908
|
} else {
|
|
3827
|
-
lines.push(` Reachable: ${
|
|
3909
|
+
lines.push(` Reachable: ${chalk17.red("no")}`);
|
|
3828
3910
|
lines.push(` Error: ${r.health.error}`);
|
|
3829
3911
|
}
|
|
3830
3912
|
lines.push("");
|
|
3831
3913
|
if (r.validateProbe) {
|
|
3832
|
-
lines.push(
|
|
3914
|
+
lines.push(chalk17.bold("sessions.validate probe ($14.99 USD dry-run)"));
|
|
3833
3915
|
if (r.validateProbe.ok) {
|
|
3834
|
-
lines.push(` Result: ${
|
|
3916
|
+
lines.push(` Result: ${chalk17.green("valid")}`);
|
|
3835
3917
|
} else {
|
|
3836
|
-
lines.push(` Result: ${
|
|
3918
|
+
lines.push(` Result: ${chalk17.red("invalid")}`);
|
|
3837
3919
|
if (r.validateProbe.error) {
|
|
3838
3920
|
lines.push(` Code: ${r.validateProbe.error.code}`);
|
|
3839
3921
|
lines.push(` Action: ${r.validateProbe.error.nextAction}`);
|
|
@@ -3846,7 +3928,7 @@ function renderHuman(r) {
|
|
|
3846
3928
|
lines.push("");
|
|
3847
3929
|
}
|
|
3848
3930
|
if (r.hints.length > 0) {
|
|
3849
|
-
lines.push(
|
|
3931
|
+
lines.push(chalk17.bold("Hints"));
|
|
3850
3932
|
for (const h of r.hints) lines.push(` \u2022 ${h}`);
|
|
3851
3933
|
lines.push("");
|
|
3852
3934
|
}
|
|
@@ -3923,7 +4005,7 @@ function renderForLlm(r) {
|
|
|
3923
4005
|
return out.join("\n");
|
|
3924
4006
|
}
|
|
3925
4007
|
function doctorCommand() {
|
|
3926
|
-
return new
|
|
4008
|
+
return new Command13("doctor").description(
|
|
3927
4009
|
"Diagnose your Von Payments environment \u2014 runtime, env vars, live API probe; outputs human / JSON / LLM-readable formats"
|
|
3928
4010
|
).option("--json", "Output JSON (machine-parseable)").option(
|
|
3929
4011
|
"--for-llm",
|
|
@@ -3938,7 +4020,7 @@ function doctorCommand() {
|
|
|
3938
4020
|
const report = await gather();
|
|
3939
4021
|
spinner.stop();
|
|
3940
4022
|
if (opts.json) {
|
|
3941
|
-
console.log(
|
|
4023
|
+
console.log(jsonOutput(report, DOCTOR_JSON_CLEANING));
|
|
3942
4024
|
} else if (opts.forLlm) {
|
|
3943
4025
|
console.log(renderForLlm(report));
|
|
3944
4026
|
} else {
|
|
@@ -3960,6 +4042,7 @@ function registerCheckout(program) {
|
|
|
3960
4042
|
checkout.addCommand(paymentIntentsCommand());
|
|
3961
4043
|
checkout.addCommand(refundsCommand());
|
|
3962
4044
|
checkout.addCommand(tokensCommand());
|
|
4045
|
+
checkout.addCommand(subscriptionProductsCommand());
|
|
3963
4046
|
checkout.addCommand(capabilitiesCommand());
|
|
3964
4047
|
checkout.addCommand(triggerCommand());
|
|
3965
4048
|
checkout.addCommand(listenCommand());
|
|
@@ -3974,7 +4057,7 @@ function commandPath(cmd) {
|
|
|
3974
4057
|
return names.join(" ");
|
|
3975
4058
|
}
|
|
3976
4059
|
function buildProgram() {
|
|
3977
|
-
const program = new
|
|
4060
|
+
const program = new Command14();
|
|
3978
4061
|
program.name("vonpay").description("Von Payments developer tools").version(CLI_VERSION).option(
|
|
3979
4062
|
ALLOW_CUSTOM_BASE_URL_FLAG,
|
|
3980
4063
|
"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)"
|