@vonpay/checkout-cli 0.3.2 → 0.4.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 +55 -10
- package/dist/client-factory.d.ts +10 -0
- package/dist/client-factory.d.ts.map +1 -0
- package/dist/client-factory.js +26 -0
- package/dist/client-factory.js.map +1 -0
- package/dist/commands/capabilities.d.ts +11 -0
- package/dist/commands/capabilities.d.ts.map +1 -0
- package/dist/commands/capabilities.js +66 -0
- package/dist/commands/capabilities.js.map +1 -0
- package/dist/commands/checkout.d.ts.map +1 -1
- package/dist/commands/checkout.js +10 -0
- package/dist/commands/checkout.js.map +1 -1
- package/dist/commands/doctor.d.ts +56 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +15 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/health.js +3 -3
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/listen.d.ts +19 -0
- package/dist/commands/listen.d.ts.map +1 -0
- package/dist/commands/listen.js +236 -0
- package/dist/commands/listen.js.map +1 -0
- package/dist/commands/payment-intents.d.ts +21 -0
- package/dist/commands/payment-intents.d.ts.map +1 -0
- package/dist/commands/payment-intents.js +195 -0
- package/dist/commands/payment-intents.js.map +1 -0
- package/dist/commands/refunds.d.ts +3 -0
- package/dist/commands/refunds.d.ts.map +1 -0
- package/dist/commands/refunds.js +78 -0
- package/dist/commands/refunds.js.map +1 -0
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +3 -18
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/tokens.d.ts +3 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +77 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/trigger.d.ts +20 -0
- package/dist/commands/trigger.d.ts.map +1 -1
- package/dist/commands/trigger.js +17 -5
- package/dist/commands/trigger.js.map +1 -1
- package/dist/utils/errors.d.ts +7 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +24 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/forward-to.d.ts +34 -0
- package/dist/utils/forward-to.d.ts.map +1 -0
- package/dist/utils/forward-to.js +88 -0
- package/dist/utils/forward-to.js.map +1 -0
- package/dist/utils/listen-forward.d.ts +43 -0
- package/dist/utils/listen-forward.d.ts.map +1 -0
- package/dist/utils/listen-forward.js +106 -0
- package/dist/utils/listen-forward.js.map +1 -0
- package/dist/utils/listen-output.d.ts +52 -0
- package/dist/utils/listen-output.d.ts.map +1 -0
- package/dist/utils/listen-output.js +85 -0
- package/dist/utils/listen-output.js.map +1 -0
- package/dist/utils/live-key.d.ts +16 -0
- package/dist/utils/live-key.d.ts.map +1 -0
- package/dist/utils/live-key.js +29 -0
- package/dist/utils/live-key.js.map +1 -0
- package/dist/utils/metadata.d.ts +2 -0
- package/dist/utils/metadata.d.ts.map +1 -0
- package/dist/utils/metadata.js +31 -0
- package/dist/utils/metadata.js.map +1 -0
- package/dist/utils/sanitize.d.ts +4 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +18 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/sse-consumer.d.ts +81 -0
- package/dist/utils/sse-consumer.d.ts.map +1 -0
- package/dist/utils/sse-consumer.js +172 -0
- package/dist/utils/sse-consumer.js.map +1 -0
- package/dist/utils/sse-parser.d.ts +63 -0
- package/dist/utils/sse-parser.d.ts.map +1 -0
- package/dist/utils/sse-parser.js +149 -0
- package/dist/utils/sse-parser.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/commands/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC,wBAAgB,aAAa,IAAI,OAAO,CAsFvC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { requireApiKey } from "../config.js";
|
|
5
|
+
import { makeCheckoutClient } from "../client-factory.js";
|
|
6
|
+
import { parseMetadata } from "../utils/metadata.js";
|
|
7
|
+
import { handleError } from "../utils/errors.js";
|
|
8
|
+
import { isLiveKey, warnLiveSecretFlag } from "../utils/live-key.js";
|
|
9
|
+
function printToken(token) {
|
|
10
|
+
const statusColor = token.status === "active"
|
|
11
|
+
? chalk.green
|
|
12
|
+
: token.status === "expired"
|
|
13
|
+
? chalk.yellow
|
|
14
|
+
: chalk.red;
|
|
15
|
+
console.log(` ID: ${token.id}`);
|
|
16
|
+
console.log(` Status: ${statusColor(token.status)}`);
|
|
17
|
+
console.log(` Card: ${token.card.brand} •••• ${token.card.last4}`);
|
|
18
|
+
console.log(` Expires: ${String(token.card.expMonth).padStart(2, "0")}/${token.card.expYear}`);
|
|
19
|
+
}
|
|
20
|
+
export function tokensCommand() {
|
|
21
|
+
const tokens = new Command("tokens").description("Create payment-method tokens for saved-card / MIT flows");
|
|
22
|
+
tokens
|
|
23
|
+
.command("create")
|
|
24
|
+
.description("Create a payment-method token")
|
|
25
|
+
.option("--buyer-id <id>", "Buyer ID to attach the token to")
|
|
26
|
+
.option("--provider-reference <ref>", "Provider tokenization handle (required for live keys with iframe-vault providers)")
|
|
27
|
+
.option("--metadata <pair...>", "Metadata key=value (repeat flag for multiple pairs)")
|
|
28
|
+
.option("--idempotency-key <key>", "Idempotency key for safe retries")
|
|
29
|
+
.option("--json", "Output raw JSON")
|
|
30
|
+
.action(async (opts) => {
|
|
31
|
+
const apiKey = requireApiKey();
|
|
32
|
+
// Sandbox keys auto-mint a mock card when no provider reference is
|
|
33
|
+
// supplied. Live keys with iframe-vault providers require a
|
|
34
|
+
// browser-minted vault token; the server returns 422 otherwise.
|
|
35
|
+
if (!opts.buyerId && !opts.providerReference) {
|
|
36
|
+
if (isLiveKey(apiKey)) {
|
|
37
|
+
console.warn(chalk.yellow("Warning: no --buyer-id or --provider-reference supplied. Live keys with iframe-vault providers will fail with a 422 validation_error."));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log(chalk.dim("No --provider-reference supplied; sandbox will auto-mint a mock card token."));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (opts.providerReference !== undefined) {
|
|
44
|
+
warnLiveSecretFlag(apiKey, "--provider-reference");
|
|
45
|
+
}
|
|
46
|
+
const client = makeCheckoutClient();
|
|
47
|
+
const spinner = ora("Creating token...").start();
|
|
48
|
+
try {
|
|
49
|
+
const metadata = parseMetadata(opts.metadata);
|
|
50
|
+
const params = {};
|
|
51
|
+
if (opts.buyerId !== undefined)
|
|
52
|
+
params.buyerId = opts.buyerId;
|
|
53
|
+
if (opts.providerReference !== undefined) {
|
|
54
|
+
params.providerReference = opts.providerReference;
|
|
55
|
+
}
|
|
56
|
+
if (metadata !== undefined)
|
|
57
|
+
params.metadata = metadata;
|
|
58
|
+
const token = await client.tokens.create(params, opts.idempotencyKey
|
|
59
|
+
? { idempotencyKey: opts.idempotencyKey }
|
|
60
|
+
: undefined);
|
|
61
|
+
spinner.stop();
|
|
62
|
+
if (opts.json) {
|
|
63
|
+
console.log(JSON.stringify(token, null, 2));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(chalk.green("✓ Token created"));
|
|
67
|
+
printToken(token);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
spinner.stop();
|
|
72
|
+
handleError(err);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return tokens;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/commands/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,23 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
export declare const SUPPORTED_EVENTS: readonly ["session.succeeded", "session.failed", "payment_intent.succeeded", "payment_intent.failed", "payment_intent.cancelled", "refund.created"];
|
|
3
|
+
type SupportedEvent = (typeof SUPPORTED_EVENTS)[number];
|
|
4
|
+
interface TriggerPayload {
|
|
5
|
+
event: SupportedEvent;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
merchantId: string;
|
|
8
|
+
amount: number;
|
|
9
|
+
currency: string;
|
|
10
|
+
status: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
transactionId?: string;
|
|
13
|
+
refundId?: string;
|
|
14
|
+
paymentIntentId?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildPayload(event: SupportedEvent, opts: {
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
amount?: number;
|
|
19
|
+
currency?: string;
|
|
20
|
+
}): TriggerPayload;
|
|
2
21
|
export declare function triggerCommand(): Command;
|
|
22
|
+
export {};
|
|
3
23
|
//# sourceMappingURL=trigger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trigger.d.ts","sourceRoot":"","sources":["../../src/commands/trigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"trigger.d.ts","sourceRoot":"","sources":["../../src/commands/trigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,gBAAgB,qJAOnB,CAAC;AACX,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAWxD,UAAU,cAAc;IACtB,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,cAAc,CA4BhB;AAED,wBAAgB,cAAc,IAAI,OAAO,CA0ExC"}
|
package/dist/commands/trigger.js
CHANGED
|
@@ -3,21 +3,30 @@ import { createHmac, randomUUID } from "node:crypto";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import ora from "ora";
|
|
5
5
|
import { requireApiKey } from "../config.js";
|
|
6
|
-
const SUPPORTED_EVENTS = [
|
|
6
|
+
export const SUPPORTED_EVENTS = [
|
|
7
7
|
"session.succeeded",
|
|
8
8
|
"session.failed",
|
|
9
|
-
"
|
|
9
|
+
"payment_intent.succeeded",
|
|
10
|
+
"payment_intent.failed",
|
|
11
|
+
"payment_intent.cancelled",
|
|
10
12
|
"refund.created",
|
|
11
13
|
];
|
|
12
14
|
const STATUS_BY_EVENT = {
|
|
13
15
|
"session.succeeded": "succeeded",
|
|
14
16
|
"session.failed": "failed",
|
|
15
|
-
"
|
|
17
|
+
"payment_intent.succeeded": "succeeded",
|
|
18
|
+
"payment_intent.failed": "failed",
|
|
19
|
+
"payment_intent.cancelled": "cancelled",
|
|
16
20
|
"refund.created": "created",
|
|
17
21
|
};
|
|
18
|
-
function buildPayload(event, opts) {
|
|
19
|
-
const
|
|
22
|
+
export function buildPayload(event, opts) {
|
|
23
|
+
const isPaymentIntent = event.startsWith("payment_intent.");
|
|
20
24
|
const isRefund = event === "refund.created";
|
|
25
|
+
// A transaction row is written for any terminal success — a settled
|
|
26
|
+
// session, a captured payment intent, or a refund.
|
|
27
|
+
const hasTransaction = event === "session.succeeded" ||
|
|
28
|
+
event === "payment_intent.succeeded" ||
|
|
29
|
+
isRefund;
|
|
21
30
|
const payload = {
|
|
22
31
|
event,
|
|
23
32
|
sessionId: opts.sessionId ?? `session_test_${randomUUID().slice(0, 8)}`,
|
|
@@ -33,6 +42,9 @@ function buildPayload(event, opts) {
|
|
|
33
42
|
if (isRefund) {
|
|
34
43
|
payload.refundId = `re_test_${randomUUID().slice(0, 8)}`;
|
|
35
44
|
}
|
|
45
|
+
if (isPaymentIntent) {
|
|
46
|
+
payload.paymentIntentId = `vpi_test_${randomUUID().slice(0, 8)}`;
|
|
47
|
+
}
|
|
36
48
|
return payload;
|
|
37
49
|
}
|
|
38
50
|
export function triggerCommand() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trigger.js","sourceRoot":"","sources":["../../src/commands/trigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,gBAAgB,GAAG;
|
|
1
|
+
{"version":3,"file":"trigger.js","sourceRoot":"","sources":["../../src/commands/trigger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAKA,iBAAS,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAa/C;AAED,eAAO,MAAM,WAAW;;CAEvB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { VonPayError } from "@vonpay/checkout-node";
|
|
3
|
+
const SECRET_KEY_PATTERN = /vp_sk_(test|live)_[A-Za-z0-9_-]+/g;
|
|
4
|
+
function scrubSecrets(s) {
|
|
5
|
+
return s.replace(SECRET_KEY_PATTERN, "vp_sk_***");
|
|
6
|
+
}
|
|
7
|
+
export function handleError(err) {
|
|
8
|
+
if (err instanceof VonPayError) {
|
|
9
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
10
|
+
console.error(chalk.dim(` Code: ${err.code}`));
|
|
11
|
+
console.error(chalk.dim(` Fix: ${err.fix}`));
|
|
12
|
+
if (err.requestId) {
|
|
13
|
+
console.error(chalk.dim(` Request ID: ${err.requestId}`));
|
|
14
|
+
}
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
18
|
+
console.error(chalk.red(`Error: ${scrubSecrets(message)}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
export const __testing__ = {
|
|
22
|
+
scrubSecrets,
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,MAAM,kBAAkB,GAAG,mCAAmC,CAAC;AAE/D,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAChD,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/C,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,YAAY;CACb,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL validation for `vonpay checkout listen --forward-to`.
|
|
3
|
+
*
|
|
4
|
+
* Default posture: loopback-only. Refuses any URL whose hostname is not
|
|
5
|
+
* one of the IPv4/IPv6 loopback aliases. This protects against the
|
|
6
|
+
* "I typed the prod URL by mistake" class of incident, where a dev
|
|
7
|
+
* starts forwarding production webhook attempts at their staging
|
|
8
|
+
* endpoint or a public colleague's URL.
|
|
9
|
+
*
|
|
10
|
+
* The opt-in --forward-to-tunnel flag allows non-loopback hosts for
|
|
11
|
+
* ngrok / Cloudflare Tunnel workflows. When set, only `https://` is
|
|
12
|
+
* additionally rejected for non-loopback — http on a public host is
|
|
13
|
+
* a stronger anti-pattern than tunneled https.
|
|
14
|
+
*
|
|
15
|
+
* Scheme: http and https accepted on loopback; non-loopback requires
|
|
16
|
+
* the tunnel flag.
|
|
17
|
+
*/
|
|
18
|
+
export type ForwardToVerdict = {
|
|
19
|
+
ok: true;
|
|
20
|
+
url: URL;
|
|
21
|
+
isLoopback: boolean;
|
|
22
|
+
warning?: string;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
reason: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Pure validator. No I/O. Caller decides what to do with the verdict
|
|
29
|
+
* (the CLI surfaces `reason` to stderr and exits non-zero).
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateForwardTo(raw: string, opts: {
|
|
32
|
+
allowTunnel: boolean;
|
|
33
|
+
}): ForwardToVerdict;
|
|
34
|
+
//# sourceMappingURL=forward-to.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward-to.d.ts","sourceRoot":"","sources":["../../src/utils/forward-to.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAmBH,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,GAC7B,gBAAgB,CA6ClB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL validation for `vonpay checkout listen --forward-to`.
|
|
3
|
+
*
|
|
4
|
+
* Default posture: loopback-only. Refuses any URL whose hostname is not
|
|
5
|
+
* one of the IPv4/IPv6 loopback aliases. This protects against the
|
|
6
|
+
* "I typed the prod URL by mistake" class of incident, where a dev
|
|
7
|
+
* starts forwarding production webhook attempts at their staging
|
|
8
|
+
* endpoint or a public colleague's URL.
|
|
9
|
+
*
|
|
10
|
+
* The opt-in --forward-to-tunnel flag allows non-loopback hosts for
|
|
11
|
+
* ngrok / Cloudflare Tunnel workflows. When set, only `https://` is
|
|
12
|
+
* additionally rejected for non-loopback — http on a public host is
|
|
13
|
+
* a stronger anti-pattern than tunneled https.
|
|
14
|
+
*
|
|
15
|
+
* Scheme: http and https accepted on loopback; non-loopback requires
|
|
16
|
+
* the tunnel flag.
|
|
17
|
+
*/
|
|
18
|
+
const LOOPBACK_HOSTS = new Set([
|
|
19
|
+
"localhost",
|
|
20
|
+
"127.0.0.1",
|
|
21
|
+
"0.0.0.0", // kernel-wildcard often routes to loopback on Linux / macOS dev hosts
|
|
22
|
+
"::1",
|
|
23
|
+
// URL.hostname surrounds IPv6 with brackets, but the bracket-stripping
|
|
24
|
+
// happens before this set is consulted (see normalizeHost). Both forms
|
|
25
|
+
// listed for defensiveness.
|
|
26
|
+
"[::1]",
|
|
27
|
+
// IPv6-mapped IPv4 loopback. Node's URL parser normalizes
|
|
28
|
+
// `[::ffff:127.0.0.1]` to either form depending on version — both
|
|
29
|
+
// are functionally loopback so we list every variant we have
|
|
30
|
+
// observed in the wild.
|
|
31
|
+
"::ffff:127.0.0.1",
|
|
32
|
+
"::ffff:7f00:1",
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Pure validator. No I/O. Caller decides what to do with the verdict
|
|
36
|
+
* (the CLI surfaces `reason` to stderr and exits non-zero).
|
|
37
|
+
*/
|
|
38
|
+
export function validateForwardTo(raw, opts) {
|
|
39
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
40
|
+
return { ok: false, reason: "--forward-to URL is required" };
|
|
41
|
+
}
|
|
42
|
+
let url;
|
|
43
|
+
try {
|
|
44
|
+
url = new URL(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: `--forward-to is not a parseable URL: ${truncate(raw)}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: `--forward-to scheme must be http or https; got ${url.protocol}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const host = normalizeHost(url.hostname);
|
|
59
|
+
const isLoopback = LOOPBACK_HOSTS.has(host);
|
|
60
|
+
if (!isLoopback && !opts.allowTunnel) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: `--forward-to refuses non-loopback hosts by default (got ${url.hostname}). ` +
|
|
64
|
+
`Re-run with --forward-to-tunnel if forwarding through ngrok / Cloudflare Tunnel / etc.`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// `0.0.0.0` is treated as loopback for dev convenience, but it's a
|
|
68
|
+
// bind-wildcard, not a guaranteed destination — in some container /
|
|
69
|
+
// network configs an outbound connection to 0.0.0.0 routes off-host.
|
|
70
|
+
// Accept it (non-breaking) but warn so a dev who meant "localhost"
|
|
71
|
+
// isn't silently forwarding off-loopback.
|
|
72
|
+
const warning = host === "0.0.0.0"
|
|
73
|
+
? "forwarding to 0.0.0.0 — that's a bind-wildcard, not a guaranteed loopback destination (it can route off-host in some container/network setups). Prefer 127.0.0.1 or localhost if you meant your local machine."
|
|
74
|
+
: undefined;
|
|
75
|
+
return { ok: true, url, isLoopback, warning };
|
|
76
|
+
}
|
|
77
|
+
function normalizeHost(hostname) {
|
|
78
|
+
// URL.hostname returns IPv6 addresses unwrapped (no brackets), but
|
|
79
|
+
// dual-coverage doesn't hurt.
|
|
80
|
+
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
|
81
|
+
return hostname.slice(1, -1);
|
|
82
|
+
}
|
|
83
|
+
return hostname.toLowerCase();
|
|
84
|
+
}
|
|
85
|
+
function truncate(s) {
|
|
86
|
+
return s.length <= 120 ? s : `${s.slice(0, 117)}...`;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=forward-to.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward-to.js","sourceRoot":"","sources":["../../src/utils/forward-to.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,WAAW;IACX,WAAW;IACX,SAAS,EAAE,sEAAsE;IACjF,KAAK;IACL,uEAAuE;IACvE,uEAAuE;IACvE,4BAA4B;IAC5B,OAAO;IACP,0DAA0D;IAC1D,kEAAkE;IAClE,6DAA6D;IAC7D,wBAAwB;IACxB,kBAAkB;IAClB,eAAe;CAChB,CAAC,CAAC;AAMH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAW,EACX,IAA8B;IAE9B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC;IAC/D,CAAC;IAED,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,wCAAwC,QAAQ,CAAC,GAAG,CAAC,EAAE;SAChE,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1D,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,kDAAkD,GAAG,CAAC,QAAQ,EAAE;SACzE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5C,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EACJ,2DAA2D,GAAG,CAAC,QAAQ,KAAK;gBAC5E,wFAAwF;SAC3F,CAAC;IACJ,CAAC;IAED,mEAAmE;IACnE,oEAAoE;IACpE,qEAAqE;IACrE,mEAAmE;IACnE,0CAA0C;IAC1C,MAAM,OAAO,GACX,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC,gNAAgN;QAClN,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AAChD,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,mEAAmE;IACnE,8BAA8B;IAC9B,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,QAAQ,CAAC,WAAW,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACzB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ForwardOutcome } from "./listen-output.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shadow-forward a webhook event to a local URL.
|
|
4
|
+
*
|
|
5
|
+
* The CLI receives a delivery-attempt TRANSCRIPT over SSE — not the
|
|
6
|
+
* payload itself. To replay the same delivery against a local URL,
|
|
7
|
+
* the CLI:
|
|
8
|
+
*
|
|
9
|
+
* 1. Fetches the full event record via GET /v1/webhook_events/:id
|
|
10
|
+
* (the public webhook-event read endpoint)
|
|
11
|
+
* 2. Computes an HMAC-SHA256 signature over the body using the
|
|
12
|
+
* merchant's API key (same secret-equals-key convention the SDK's
|
|
13
|
+
* `webhooks.constructEvent` verifies)
|
|
14
|
+
* 3. POSTs to the local URL with `X-VonPay-Signature` +
|
|
15
|
+
* `X-VonPay-Timestamp` headers, matching the live delivery shape.
|
|
16
|
+
* The timestamp is stamped HERE, at forward time — see the
|
|
17
|
+
* `forwardPayload` body for the fresh-timestamp rationale.
|
|
18
|
+
*
|
|
19
|
+
* Bounded by a per-forward timeout (default 5s) and never retries —
|
|
20
|
+
* shadow-forward is best-effort; if the local URL is down, the
|
|
21
|
+
* transcript line shows the failure and the dev moves on.
|
|
22
|
+
*
|
|
23
|
+
* This re-delivers from the SSE delivery transcript, which differs from
|
|
24
|
+
* Stripe CLI's `stripe listen --forward-to` pre-empt model.
|
|
25
|
+
*/
|
|
26
|
+
interface ForwardOptions {
|
|
27
|
+
/** Local URL to POST the payload to. */
|
|
28
|
+
url: URL;
|
|
29
|
+
/** Merchant API key (vp_sk_*) — used as HMAC secret. */
|
|
30
|
+
signingSecret: string;
|
|
31
|
+
/** Per-forward timeout. Default 5_000. */
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
/** AbortSignal for graceful shutdown. */
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sign + POST a JSON payload to the local URL. Returns an outcome
|
|
38
|
+
* shape that the output formatters render — never throws (errors
|
|
39
|
+
* surface via `outcome.error`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function forwardPayload(payload: Record<string, unknown>, opts: ForwardOptions): Promise<ForwardOutcome>;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=listen-forward.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-forward.d.ts","sourceRoot":"","sources":["../../src/utils/listen-forward.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,UAAU,cAAc;IACtB,wCAAwC;IACxC,GAAG,EAAE,GAAG,CAAC;IACT,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,CAAC,CA8DzB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Sign + POST a JSON payload to the local URL. Returns an outcome
|
|
4
|
+
* shape that the output formatters render — never throws (errors
|
|
5
|
+
* surface via `outcome.error`).
|
|
6
|
+
*/
|
|
7
|
+
export async function forwardPayload(payload, opts) {
|
|
8
|
+
const body = JSON.stringify(payload);
|
|
9
|
+
// Stamp the moment we forward — NOT the server's original
|
|
10
|
+
// delivery-attempt time. The CLI is the *re-sender* for this local
|
|
11
|
+
// delivery: the dev's handler calls `webhooks.constructEvent`, which
|
|
12
|
+
// rejects any `X-VonPay-Timestamp` outside ±5 min of now. The server's
|
|
13
|
+
// original timestamp (carried on the SSE `webhook.attempt` frame) is
|
|
14
|
+
// routinely minutes old — retries, processing lag, a transcript the
|
|
15
|
+
// dev scrolled back through — so replaying it trips the freshness
|
|
16
|
+
// window and breaks local verification. A fresh timestamp here matches
|
|
17
|
+
// Stripe CLI's re-sign posture. The v1 signature covers the body only
|
|
18
|
+
// (constructEvent verifies the timestamp independently), so the fresh
|
|
19
|
+
// timestamp does NOT need to be folded into the HMAC.
|
|
20
|
+
const timestamp = new Date().toISOString();
|
|
21
|
+
const signature = createHmac("sha256", opts.signingSecret)
|
|
22
|
+
.update(body)
|
|
23
|
+
.digest("hex");
|
|
24
|
+
const timeoutMs = opts.timeoutMs ?? 5_000;
|
|
25
|
+
const localController = new AbortController();
|
|
26
|
+
const timeoutId = setTimeout(() => localController.abort(), timeoutMs);
|
|
27
|
+
const combinedSignal = anySignal([opts.signal, localController.signal]);
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(opts.url.toString(), {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"X-VonPay-Signature": signature,
|
|
35
|
+
"X-VonPay-Timestamp": timestamp,
|
|
36
|
+
"User-Agent": "vonpay-cli-listen",
|
|
37
|
+
},
|
|
38
|
+
body,
|
|
39
|
+
signal: combinedSignal,
|
|
40
|
+
});
|
|
41
|
+
// Drain response body so the connection can be reused — best practice
|
|
42
|
+
// for repeated POSTs to the same local endpoint.
|
|
43
|
+
try {
|
|
44
|
+
await res.arrayBuffer();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// ignore drain errors; not relevant to forward semantics
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
attempted: true,
|
|
51
|
+
status: res.status,
|
|
52
|
+
latencyMs: Date.now() - start,
|
|
53
|
+
error: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (opts.signal.aborted) {
|
|
58
|
+
return { attempted: true, status: null, latencyMs: null, error: "aborted" };
|
|
59
|
+
}
|
|
60
|
+
if (localController.signal.aborted) {
|
|
61
|
+
return { attempted: true, status: null, latencyMs: null, error: `timeout after ${timeoutMs}ms` };
|
|
62
|
+
}
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
return { attempted: true, status: null, latencyMs: null, error: message };
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Combine multiple AbortSignals into one. The returned signal is
|
|
72
|
+
* aborted when any input signal aborts. Polyfill for `AbortSignal.any`
|
|
73
|
+
* which is Node 20.5+ only — guard for older 20.x deployments.
|
|
74
|
+
*
|
|
75
|
+
* The polyfill tracks every listener it registers and removes them all
|
|
76
|
+
* once any input fires — without this, the long-lived parent
|
|
77
|
+
* `opts.signal` accumulates one orphaned listener per forward call,
|
|
78
|
+
* which on a busy listen stream is a measurable leak.
|
|
79
|
+
*/
|
|
80
|
+
function anySignal(signals) {
|
|
81
|
+
if (typeof AbortSignal.any === "function") {
|
|
82
|
+
return AbortSignal.any(signals);
|
|
83
|
+
}
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const tracked = [];
|
|
86
|
+
const onAbort = () => {
|
|
87
|
+
for (const sig of tracked) {
|
|
88
|
+
sig.removeEventListener("abort", onAbort);
|
|
89
|
+
}
|
|
90
|
+
controller.abort();
|
|
91
|
+
};
|
|
92
|
+
for (const signal of signals) {
|
|
93
|
+
if (signal.aborted) {
|
|
94
|
+
controller.abort();
|
|
95
|
+
// Remove already-added listeners — none yet, but defensive.
|
|
96
|
+
for (const sig of tracked) {
|
|
97
|
+
sig.removeEventListener("abort", onAbort);
|
|
98
|
+
}
|
|
99
|
+
return controller.signal;
|
|
100
|
+
}
|
|
101
|
+
signal.addEventListener("abort", onAbort);
|
|
102
|
+
tracked.push(signal);
|
|
103
|
+
}
|
|
104
|
+
return controller.signal;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=listen-forward.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-forward.js","sourceRoot":"","sources":["../../src/utils/listen-forward.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuCzC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAgC,EAChC,IAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAErC,0DAA0D;IAC1D,mEAAmE;IACnE,qEAAqE;IACrE,uEAAuE;IACvE,qEAAqE;IACrE,oEAAoE;IACpE,kEAAkE;IAClE,uEAAuE;IACvE,sEAAsE;IACtE,sEAAsE;IACtE,sDAAsD;IACtD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC;SACvD,MAAM,CAAC,IAAI,CAAC;SACZ,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IACvE,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAExE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,oBAAoB,EAAE,SAAS;gBAC/B,oBAAoB,EAAE,SAAS;gBAC/B,YAAY,EAAE,mBAAmB;aAClC;YACD,IAAI;YACJ,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QACH,sEAAsE;QACtE,iDAAiD;QACjD,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,yDAAyD;QAC3D,CAAC;QACD,OAAO;YACL,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;YAC7B,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC9E,CAAC;QACD,IAAI,eAAe,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACnC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,SAAS,IAAI,EAAE,CAAC;QACnG,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,OAAsB;IACvC,IAAI,OAAQ,WAA4C,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAC5E,OAAQ,WAAqE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,GAAG,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,UAAU,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC,CAAC;IACF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,4DAA4D;YAC5D,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,GAAG,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5C,CAAC;YACD,OAAO,UAAU,CAAC,MAAM,CAAC;QAC3B,CAAC;QACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatters for `vonpay checkout listen`.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - "human" (default) — colored transcript, one line per attempt.
|
|
6
|
+
* Optimized for the dev sitting at a terminal watching events
|
|
7
|
+
* flow in real time.
|
|
8
|
+
* - "json" — one JSON object per line on stdout. Optimized for
|
|
9
|
+
* piping to `jq`, `grep`, or log aggregators.
|
|
10
|
+
*
|
|
11
|
+
* Lifecycle messages (connecting, reconnecting, etc.) ALWAYS go to
|
|
12
|
+
* stderr in human mode so stdout stays parseable in `--json` mode
|
|
13
|
+
* even when interleaved with reconnects.
|
|
14
|
+
*/
|
|
15
|
+
export type OutputMode = "human" | "json";
|
|
16
|
+
/**
|
|
17
|
+
* Wire shape of the SSE `webhook.attempt` payload. Mirrors
|
|
18
|
+
* vonpay-checkout's WebhookListenAttempt (snake_case on the wire).
|
|
19
|
+
*
|
|
20
|
+
* Duplicated here rather than importing from @vonpay/checkout-node
|
|
21
|
+
* because the SDK does not currently export an SSE-event type — the
|
|
22
|
+
* listen surface is CLI-only. If the SDK adds a streaming consumer in
|
|
23
|
+
* a future minor, this type should move to the SDK and be re-imported.
|
|
24
|
+
*/
|
|
25
|
+
export interface WebhookAttemptPayload {
|
|
26
|
+
type: "webhook.attempt";
|
|
27
|
+
subscription_id: string;
|
|
28
|
+
event_id: string;
|
|
29
|
+
event_type: string;
|
|
30
|
+
attempt: number;
|
|
31
|
+
succeeded: boolean;
|
|
32
|
+
response_status: number | null;
|
|
33
|
+
latency_ms: number;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
livemode: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ForwardOutcome {
|
|
38
|
+
attempted: boolean;
|
|
39
|
+
/** Local-URL response status, or null when no attempt was made (no --forward-to). */
|
|
40
|
+
status: number | null;
|
|
41
|
+
/** Latency ms of the local-URL POST. */
|
|
42
|
+
latencyMs: number | null;
|
|
43
|
+
/** Error message if the forward attempt failed at the network layer. */
|
|
44
|
+
error: string | null;
|
|
45
|
+
}
|
|
46
|
+
export declare function formatAttemptHuman(payload: WebhookAttemptPayload, forward: ForwardOutcome | null): string;
|
|
47
|
+
export declare function formatAttemptJson(payload: WebhookAttemptPayload, forward: ForwardOutcome | null): string;
|
|
48
|
+
/** Lifecycle line for human mode (stderr). */
|
|
49
|
+
export declare function formatLifecycleHuman(message: string, kind: "info" | "warn" | "error"): string;
|
|
50
|
+
/** Lifecycle line for JSON mode (stderr). */
|
|
51
|
+
export declare function formatLifecycleJson(kind: "connecting" | "connected" | "disconnected" | "auth_failed" | "fatal" | "ready" | "shutdown" | "warning", extra?: Record<string, unknown>): string;
|
|
52
|
+
//# sourceMappingURL=listen-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-output.d.ts","sourceRoot":"","sources":["../../src/utils/listen-output.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,iBAAiB,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wCAAwC;IACxC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,wEAAwE;IACxE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GAAG,IAAI,GAC7B,MAAM,CAwBR;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GAAG,IAAI,GAC7B,MAAM,CAqBR;AAED,8CAA8C;AAC9C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAQ7F;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,EAC9G,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,MAAM,CAER"}
|