@proxy-checkout/cli 0.1.0 → 0.1.1-prx-124.139.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cjs/cli.d.cts +1 -1
- package/dist/cjs/cli.d.ts +1 -1
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/generated/stripe-integration-capabilities-version.d.cts +2 -0
- package/dist/cjs/generated/stripe-integration-capabilities-version.d.ts +2 -0
- package/dist/cjs/generated/stripe-integration-capabilities-version.js +15 -0
- package/dist/cjs/stripe-profile.d.cts +14 -0
- package/dist/cjs/stripe-profile.d.ts +14 -0
- package/dist/cjs/stripe-profile.js +49 -0
- package/dist/cjs/webhooks.d.cts +12 -1
- package/dist/cjs/webhooks.d.ts +12 -1
- package/dist/cjs/webhooks.js +63 -23
- package/dist/esm/cli.d.ts +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/generated/stripe-integration-capabilities-version.d.ts +2 -0
- package/dist/esm/generated/stripe-integration-capabilities-version.js +12 -0
- package/dist/esm/stripe-profile.d.ts +14 -0
- package/dist/esm/stripe-profile.js +44 -0
- package/dist/esm/webhooks.d.ts +12 -1
- package/dist/esm/webhooks.js +63 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,7 +64,9 @@ proxy listen \
|
|
|
64
64
|
--forward-to http://127.0.0.1:3000/webhooks
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Before capturing a signing secret, the CLI asks the authenticated Proxy API for the exact sorted Stripe event union implied by Proxy's existing legacy lifecycle bridge plus the selected configuration's enabled capabilities. Both Stripe CLI processes use that union. Temporary-ingress creation includes a non-secret profile hash that Proxy recomputes under the configuration lock; stale or unsupported profiles fail before secret persistence. No separate local event list or new dashboard configuration is required.
|
|
68
|
+
|
|
69
|
+
Structured `stripe.started` output includes the selected configuration, effective capability IDs, exact events, manifest version, non-secret profile hash, ingress ID, and expiry for agent/CI evidence. The Stripe signing secret is captured only in child-process memory, never echoed or placed in argv, and stored only inside Proxy's existing encrypted temporary PSP ingress boundary. The CLI never receives the merchant's Stripe API credential. Both the Stripe ingress and local listener close on exit or expire server-side after a crash.
|
|
68
70
|
|
|
69
71
|
## Advanced delivery controls
|
|
70
72
|
|
package/dist/cjs/cli.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliConfigStore } from "./config.js";
|
|
2
2
|
import { type CliIo } from "./output.js";
|
|
3
|
-
export declare const cliVersion = "0.1.
|
|
3
|
+
export declare const cliVersion = "0.1.1-prx-124.139.1";
|
|
4
4
|
export interface RunCliOptions {
|
|
5
5
|
configStore?: CliConfigStore;
|
|
6
6
|
environment?: NodeJS.ProcessEnv;
|
package/dist/cjs/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliConfigStore } from "./config.js";
|
|
2
2
|
import { type CliIo } from "./output.js";
|
|
3
|
-
export declare const cliVersion = "0.1.
|
|
3
|
+
export declare const cliVersion = "0.1.1-prx-124.139.1";
|
|
4
4
|
export interface RunCliOptions {
|
|
5
5
|
configStore?: CliConfigStore;
|
|
6
6
|
environment?: NodeJS.ProcessEnv;
|
package/dist/cjs/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const config_js_1 = require("./config.js");
|
|
|
9
9
|
const errors_js_1 = require("./errors.js");
|
|
10
10
|
const output_js_1 = require("./output.js");
|
|
11
11
|
const webhooks_js_1 = require("./webhooks.js");
|
|
12
|
-
exports.cliVersion = "0.1.
|
|
12
|
+
exports.cliVersion = "0.1.1-prx-124.139.1";
|
|
13
13
|
async function runCli(argv, options = {}) {
|
|
14
14
|
const io = options.io ?? {
|
|
15
15
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const stripeIntegrationCapabilitiesManifestVersion: 2;
|
|
2
|
+
export declare const stripeLegacyCliEventTypes: readonly ["checkout.session.completed", "customer.subscription.deleted", "customer.subscription.updated", "invoice.paid", "invoice.payment_failed", "payment_intent.canceled", "payment_intent.payment_failed", "payment_intent.succeeded"];
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const stripeIntegrationCapabilitiesManifestVersion: 2;
|
|
2
|
+
export declare const stripeLegacyCliEventTypes: readonly ["checkout.session.completed", "customer.subscription.deleted", "customer.subscription.updated", "invoice.paid", "invoice.payment_failed", "payment_intent.canceled", "payment_intent.payment_failed", "payment_intent.succeeded"];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripeLegacyCliEventTypes = exports.stripeIntegrationCapabilitiesManifestVersion = void 0;
|
|
4
|
+
// Generated by tools/compliance/sync-stripe-integration-capabilities.mjs.
|
|
5
|
+
exports.stripeIntegrationCapabilitiesManifestVersion = 2;
|
|
6
|
+
exports.stripeLegacyCliEventTypes = [
|
|
7
|
+
"checkout.session.completed",
|
|
8
|
+
"customer.subscription.deleted",
|
|
9
|
+
"customer.subscription.updated",
|
|
10
|
+
"invoice.paid",
|
|
11
|
+
"invoice.payment_failed",
|
|
12
|
+
"payment_intent.canceled",
|
|
13
|
+
"payment_intent.payment_failed",
|
|
14
|
+
"payment_intent.succeeded",
|
|
15
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface StripeIngressProfileRecord {
|
|
2
|
+
effective_capability_ids: string[];
|
|
3
|
+
event_types: string[];
|
|
4
|
+
manifest_version: number;
|
|
5
|
+
merchant_psp_config_id: string;
|
|
6
|
+
profile_hash: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function canonicalLegacyStripeIngressEventTypes(): string[];
|
|
9
|
+
export declare function validateStripeIngressProfile(profile: StripeIngressProfileRecord): StripeIngressProfileRecord;
|
|
10
|
+
export declare function assertCreatedStripeIngressProfile(expected: StripeIngressProfileRecord, actual: {
|
|
11
|
+
event_types: string[];
|
|
12
|
+
manifest_version: number;
|
|
13
|
+
profile_hash: string;
|
|
14
|
+
}): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface StripeIngressProfileRecord {
|
|
2
|
+
effective_capability_ids: string[];
|
|
3
|
+
event_types: string[];
|
|
4
|
+
manifest_version: number;
|
|
5
|
+
merchant_psp_config_id: string;
|
|
6
|
+
profile_hash: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function canonicalLegacyStripeIngressEventTypes(): string[];
|
|
9
|
+
export declare function validateStripeIngressProfile(profile: StripeIngressProfileRecord): StripeIngressProfileRecord;
|
|
10
|
+
export declare function assertCreatedStripeIngressProfile(expected: StripeIngressProfileRecord, actual: {
|
|
11
|
+
event_types: string[];
|
|
12
|
+
manifest_version: number;
|
|
13
|
+
profile_hash: string;
|
|
14
|
+
}): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalLegacyStripeIngressEventTypes = canonicalLegacyStripeIngressEventTypes;
|
|
4
|
+
exports.validateStripeIngressProfile = validateStripeIngressProfile;
|
|
5
|
+
exports.assertCreatedStripeIngressProfile = assertCreatedStripeIngressProfile;
|
|
6
|
+
const errors_js_1 = require("./errors.js");
|
|
7
|
+
const stripe_integration_capabilities_version_js_1 = require("./generated/stripe-integration-capabilities-version.js");
|
|
8
|
+
const stripeEventPattern = /^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/;
|
|
9
|
+
const capabilityPattern = /^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/;
|
|
10
|
+
const sha256Pattern = /^[0-9a-f]{64}$/;
|
|
11
|
+
const maximumEntries = 100;
|
|
12
|
+
function canonicalLegacyStripeIngressEventTypes() {
|
|
13
|
+
return [...stripe_integration_capabilities_version_js_1.stripeLegacyCliEventTypes];
|
|
14
|
+
}
|
|
15
|
+
function validateStripeIngressProfile(profile) {
|
|
16
|
+
if (profile.manifest_version !== stripe_integration_capabilities_version_js_1.stripeIntegrationCapabilitiesManifestVersion) {
|
|
17
|
+
throw profileError("Stripe ingress profile uses an incompatible manifest version");
|
|
18
|
+
}
|
|
19
|
+
if (!profile.merchant_psp_config_id.startsWith("pspcfg_") ||
|
|
20
|
+
!sha256Pattern.test(profile.profile_hash)) {
|
|
21
|
+
throw profileError("Stripe ingress profile identity is invalid");
|
|
22
|
+
}
|
|
23
|
+
assertSortedUnique(profile.event_types, stripeEventPattern, false, "event types");
|
|
24
|
+
assertSortedUnique(profile.effective_capability_ids, capabilityPattern, true, "capability ids");
|
|
25
|
+
return profile;
|
|
26
|
+
}
|
|
27
|
+
function assertCreatedStripeIngressProfile(expected, actual) {
|
|
28
|
+
assertSortedUnique(actual.event_types, stripeEventPattern, false, "event types");
|
|
29
|
+
if (actual.manifest_version !== expected.manifest_version ||
|
|
30
|
+
actual.profile_hash !== expected.profile_hash ||
|
|
31
|
+
JSON.stringify(actual.event_types) !== JSON.stringify(expected.event_types)) {
|
|
32
|
+
throw profileError("Stripe ingress profile changed while the listener was starting");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function assertSortedUnique(values, pattern, allowEmpty, label) {
|
|
36
|
+
if (!Array.isArray(values) ||
|
|
37
|
+
(!allowEmpty && values.length === 0) ||
|
|
38
|
+
values.length > maximumEntries ||
|
|
39
|
+
values.some((value) => !pattern.test(value)) ||
|
|
40
|
+
values.some((value, index) => {
|
|
41
|
+
const previous = values[index - 1];
|
|
42
|
+
return previous !== undefined && previous >= value;
|
|
43
|
+
})) {
|
|
44
|
+
throw profileError(`Stripe ingress profile ${label} are invalid`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function profileError(message) {
|
|
48
|
+
return new errors_js_1.CliError(message, "stripe_ingress_profile_unsupported", errors_js_1.cliExitCodes.dependency);
|
|
49
|
+
}
|
package/dist/cjs/webhooks.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CliOutput } from "./output.js";
|
|
2
|
+
import { type StripeIngressProfileRecord } from "./stripe-profile.js";
|
|
2
3
|
export interface WebhookEndpointRecord {
|
|
3
4
|
event_schema_version: string;
|
|
4
5
|
event_types: string[] | null;
|
|
@@ -44,12 +45,22 @@ export declare class ProxyWebhooksClient {
|
|
|
44
45
|
errorClass: "network_error" | "timeout" | null;
|
|
45
46
|
responseStatusCode: number | null;
|
|
46
47
|
}): Promise<unknown>;
|
|
47
|
-
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
48
|
+
createStripeIngress(listenerId: string, webhookSecret: string, profileHash: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
49
|
+
event_types: string[];
|
|
48
50
|
expires_at: string;
|
|
49
51
|
id: string;
|
|
50
52
|
inbound_url: string;
|
|
53
|
+
manifest_version: number;
|
|
51
54
|
merchant_psp_config_id: string;
|
|
55
|
+
profile_hash: string;
|
|
52
56
|
}>;
|
|
57
|
+
createLegacyStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
58
|
+
expires_at: string;
|
|
59
|
+
id: string;
|
|
60
|
+
inbound_url: string;
|
|
61
|
+
merchant_psp_config_id: string;
|
|
62
|
+
}>;
|
|
63
|
+
getStripeIngressProfile(listenerId: string, pspConfigId?: string, signal?: AbortSignal): Promise<StripeIngressProfileRecord>;
|
|
53
64
|
}
|
|
54
65
|
export declare function runListener(input: {
|
|
55
66
|
client: ProxyWebhooksClient;
|
package/dist/cjs/webhooks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CliOutput } from "./output.js";
|
|
2
|
+
import { type StripeIngressProfileRecord } from "./stripe-profile.js";
|
|
2
3
|
export interface WebhookEndpointRecord {
|
|
3
4
|
event_schema_version: string;
|
|
4
5
|
event_types: string[] | null;
|
|
@@ -44,12 +45,22 @@ export declare class ProxyWebhooksClient {
|
|
|
44
45
|
errorClass: "network_error" | "timeout" | null;
|
|
45
46
|
responseStatusCode: number | null;
|
|
46
47
|
}): Promise<unknown>;
|
|
47
|
-
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
48
|
+
createStripeIngress(listenerId: string, webhookSecret: string, profileHash: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
49
|
+
event_types: string[];
|
|
48
50
|
expires_at: string;
|
|
49
51
|
id: string;
|
|
50
52
|
inbound_url: string;
|
|
53
|
+
manifest_version: number;
|
|
51
54
|
merchant_psp_config_id: string;
|
|
55
|
+
profile_hash: string;
|
|
52
56
|
}>;
|
|
57
|
+
createLegacyStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
58
|
+
expires_at: string;
|
|
59
|
+
id: string;
|
|
60
|
+
inbound_url: string;
|
|
61
|
+
merchant_psp_config_id: string;
|
|
62
|
+
}>;
|
|
63
|
+
getStripeIngressProfile(listenerId: string, pspConfigId?: string, signal?: AbortSignal): Promise<StripeIngressProfileRecord>;
|
|
53
64
|
}
|
|
54
65
|
export declare function runListener(input: {
|
|
55
66
|
client: ProxyWebhooksClient;
|
package/dist/cjs/webhooks.js
CHANGED
|
@@ -7,19 +7,10 @@ const node_child_process_1 = require("node:child_process");
|
|
|
7
7
|
const errors_js_1 = require("./errors.js");
|
|
8
8
|
const http_client_js_1 = require("./http-client.js");
|
|
9
9
|
const output_js_1 = require("./output.js");
|
|
10
|
+
const stripe_profile_js_1 = require("./stripe-profile.js");
|
|
10
11
|
const claimPollIntervalMs = 1_000;
|
|
11
12
|
const heartbeatIntervalMs = 30_000;
|
|
12
13
|
const localForwardTimeoutMs = 30_000;
|
|
13
|
-
const stripeEvents = [
|
|
14
|
-
"checkout.session.completed",
|
|
15
|
-
"customer.subscription.deleted",
|
|
16
|
-
"customer.subscription.updated",
|
|
17
|
-
"invoice.paid",
|
|
18
|
-
"invoice.payment_failed",
|
|
19
|
-
"payment_intent.canceled",
|
|
20
|
-
"payment_intent.payment_failed",
|
|
21
|
-
"payment_intent.succeeded",
|
|
22
|
-
].join(",");
|
|
23
14
|
class ProxyWebhooksClient {
|
|
24
15
|
api;
|
|
25
16
|
constructor(apiBaseUrl, token, fetchImpl) {
|
|
@@ -73,16 +64,31 @@ class ProxyWebhooksClient {
|
|
|
73
64
|
},
|
|
74
65
|
});
|
|
75
66
|
}
|
|
76
|
-
async createStripeIngress(listenerId, webhookSecret, pspConfigId, signal) {
|
|
67
|
+
async createStripeIngress(listenerId, webhookSecret, profileHash, pspConfigId, signal) {
|
|
77
68
|
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress`, {
|
|
78
69
|
method: "POST",
|
|
79
70
|
body: {
|
|
80
71
|
...(pspConfigId ? { psp_config_id: pspConfigId } : {}),
|
|
72
|
+
profile_hash: profileHash,
|
|
81
73
|
webhook_secret: webhookSecret,
|
|
82
74
|
},
|
|
83
75
|
signal,
|
|
84
76
|
})).stripe_ingress;
|
|
85
77
|
}
|
|
78
|
+
async createLegacyStripeIngress(listenerId, webhookSecret, pspConfigId, signal) {
|
|
79
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
body: {
|
|
82
|
+
...(pspConfigId ? { psp_config_id: pspConfigId } : {}),
|
|
83
|
+
webhook_secret: webhookSecret,
|
|
84
|
+
},
|
|
85
|
+
signal,
|
|
86
|
+
})).stripe_ingress;
|
|
87
|
+
}
|
|
88
|
+
async getStripeIngressProfile(listenerId, pspConfigId, signal) {
|
|
89
|
+
const query = pspConfigId ? `?${new URLSearchParams({ psp_config_id: pspConfigId })}` : "";
|
|
90
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress-profile${query}`, { signal })).stripe_ingress_profile;
|
|
91
|
+
}
|
|
86
92
|
}
|
|
87
93
|
exports.ProxyWebhooksClient = ProxyWebhooksClient;
|
|
88
94
|
async function runListener(input) {
|
|
@@ -103,16 +109,47 @@ async function runListener(input) {
|
|
|
103
109
|
if (input.signal.aborted)
|
|
104
110
|
throw listenerInterrupted();
|
|
105
111
|
if (input.stripe) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
let stripeProfile;
|
|
113
|
+
try {
|
|
114
|
+
stripeProfile = (0, stripe_profile_js_1.validateStripeIngressProfile)(await input.client.getStripeIngressProfile(listener.id, input.pspConfigId, input.signal));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (!isMissingStripeIngressProfileRoute(error))
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
const eventTypes = stripeProfile
|
|
121
|
+
? stripeProfile.event_types
|
|
122
|
+
: (0, stripe_profile_js_1.canonicalLegacyStripeIngressEventTypes)();
|
|
123
|
+
const webhookSecret = await captureStripeWebhookSecret(eventTypes, input.signal);
|
|
124
|
+
let ingress;
|
|
125
|
+
if (stripeProfile) {
|
|
126
|
+
const profiledIngress = await input.client.createStripeIngress(listener.id, webhookSecret, stripeProfile.profile_hash, input.pspConfigId, input.signal);
|
|
127
|
+
(0, stripe_profile_js_1.assertCreatedStripeIngressProfile)(stripeProfile, profiledIngress);
|
|
128
|
+
ingress = profiledIngress;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
ingress = await input.client.createLegacyStripeIngress(listener.id, webhookSecret, input.pspConfigId, input.signal);
|
|
132
|
+
}
|
|
133
|
+
stripeProcess = await startStripeListener(ingress.inbound_url, eventTypes, input.output, (error) => {
|
|
109
134
|
stripeProcessFailure = stripeListenerStartError(error);
|
|
110
135
|
});
|
|
111
|
-
input.output.event("stripe.started",
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
input.output.event("stripe.started", stripeProfile
|
|
137
|
+
? {
|
|
138
|
+
effective_capability_ids: stripeProfile.effective_capability_ids,
|
|
139
|
+
event_types: eventTypes,
|
|
140
|
+
expires_at: ingress.expires_at,
|
|
141
|
+
ingress_id: ingress.id,
|
|
142
|
+
manifest_version: stripeProfile.manifest_version,
|
|
143
|
+
merchant_psp_config_id: ingress.merchant_psp_config_id,
|
|
144
|
+
profile_hash: stripeProfile.profile_hash,
|
|
145
|
+
}
|
|
146
|
+
: {
|
|
147
|
+
compatibility_mode: "legacy_api",
|
|
148
|
+
event_types: eventTypes,
|
|
149
|
+
expires_at: ingress.expires_at,
|
|
150
|
+
ingress_id: ingress.id,
|
|
151
|
+
merchant_psp_config_id: ingress.merchant_psp_config_id,
|
|
152
|
+
}, "Stripe CLI test webhook orchestration is active.");
|
|
116
153
|
}
|
|
117
154
|
if (input.signal.aborted)
|
|
118
155
|
throw listenerInterrupted();
|
|
@@ -168,6 +205,9 @@ async function runListener(input) {
|
|
|
168
205
|
}
|
|
169
206
|
}
|
|
170
207
|
}
|
|
208
|
+
function isMissingStripeIngressProfileRoute(error) {
|
|
209
|
+
return error instanceof errors_js_1.CliError && error.code === "http_404" && error.details?.status === 404;
|
|
210
|
+
}
|
|
171
211
|
async function forwardSelectedDelivery(input) {
|
|
172
212
|
const destination = validateLoopbackDestination(input.forwardTo);
|
|
173
213
|
const claim = await input.client.claim(input.listenerId, {
|
|
@@ -216,8 +256,8 @@ async function forwardClaim(client, claim, destination, signal) {
|
|
|
216
256
|
responseStatusCode,
|
|
217
257
|
});
|
|
218
258
|
}
|
|
219
|
-
async function captureStripeWebhookSecret(signal) {
|
|
220
|
-
const result = await captureProcess("stripe", ["listen", "--events",
|
|
259
|
+
async function captureStripeWebhookSecret(eventTypes, signal) {
|
|
260
|
+
const result = await captureProcess("stripe", ["listen", "--events", eventTypes.join(","), "--print-secret"], signal, 30_000);
|
|
221
261
|
if (signal.aborted)
|
|
222
262
|
throw listenerInterrupted();
|
|
223
263
|
const match = `${result.stdout}\n${result.stderr}`.match(/\bwhsec_[A-Za-z0-9_-]{8,512}\b/);
|
|
@@ -229,8 +269,8 @@ async function captureStripeWebhookSecret(signal) {
|
|
|
229
269
|
function listenerInterrupted() {
|
|
230
270
|
return new errors_js_1.CliError("Local webhook listener interrupted", "cli_listener_interrupted", errors_js_1.cliExitCodes.interrupted);
|
|
231
271
|
}
|
|
232
|
-
function startStripeListener(inboundUrl, output, onError) {
|
|
233
|
-
const child = (0, node_child_process_1.spawn)("stripe", ["listen", "--events",
|
|
272
|
+
function startStripeListener(inboundUrl, eventTypes, output, onError) {
|
|
273
|
+
const child = (0, node_child_process_1.spawn)("stripe", ["listen", "--events", eventTypes.join(","), "--forward-to", inboundUrl], {
|
|
234
274
|
shell: false,
|
|
235
275
|
stdio: ["pipe", "pipe", "pipe"],
|
|
236
276
|
});
|
package/dist/esm/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliConfigStore } from "./config.js";
|
|
2
2
|
import { type CliIo } from "./output.js";
|
|
3
|
-
export declare const cliVersion = "0.1.
|
|
3
|
+
export declare const cliVersion = "0.1.1-prx-124.139.1";
|
|
4
4
|
export interface RunCliOptions {
|
|
5
5
|
configStore?: CliConfigStore;
|
|
6
6
|
environment?: NodeJS.ProcessEnv;
|
package/dist/esm/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CliConfigStore } from "./config.js";
|
|
|
5
5
|
import { asCliError, CliError, cliExitCodes, usageError } from "./errors.js";
|
|
6
6
|
import { CliOutput } from "./output.js";
|
|
7
7
|
import { forwardSelectedDelivery, ProxyWebhooksClient, runListener, } from "./webhooks.js";
|
|
8
|
-
export const cliVersion = "0.1.
|
|
8
|
+
export const cliVersion = "0.1.1-prx-124.139.1";
|
|
9
9
|
export async function runCli(argv, options = {}) {
|
|
10
10
|
const io = options.io ?? {
|
|
11
11
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const stripeIntegrationCapabilitiesManifestVersion: 2;
|
|
2
|
+
export declare const stripeLegacyCliEventTypes: readonly ["checkout.session.completed", "customer.subscription.deleted", "customer.subscription.updated", "invoice.paid", "invoice.payment_failed", "payment_intent.canceled", "payment_intent.payment_failed", "payment_intent.succeeded"];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Generated by tools/compliance/sync-stripe-integration-capabilities.mjs.
|
|
2
|
+
export const stripeIntegrationCapabilitiesManifestVersion = 2;
|
|
3
|
+
export const stripeLegacyCliEventTypes = [
|
|
4
|
+
"checkout.session.completed",
|
|
5
|
+
"customer.subscription.deleted",
|
|
6
|
+
"customer.subscription.updated",
|
|
7
|
+
"invoice.paid",
|
|
8
|
+
"invoice.payment_failed",
|
|
9
|
+
"payment_intent.canceled",
|
|
10
|
+
"payment_intent.payment_failed",
|
|
11
|
+
"payment_intent.succeeded",
|
|
12
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface StripeIngressProfileRecord {
|
|
2
|
+
effective_capability_ids: string[];
|
|
3
|
+
event_types: string[];
|
|
4
|
+
manifest_version: number;
|
|
5
|
+
merchant_psp_config_id: string;
|
|
6
|
+
profile_hash: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function canonicalLegacyStripeIngressEventTypes(): string[];
|
|
9
|
+
export declare function validateStripeIngressProfile(profile: StripeIngressProfileRecord): StripeIngressProfileRecord;
|
|
10
|
+
export declare function assertCreatedStripeIngressProfile(expected: StripeIngressProfileRecord, actual: {
|
|
11
|
+
event_types: string[];
|
|
12
|
+
manifest_version: number;
|
|
13
|
+
profile_hash: string;
|
|
14
|
+
}): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CliError, cliExitCodes } from "./errors.js";
|
|
2
|
+
import { stripeIntegrationCapabilitiesManifestVersion, stripeLegacyCliEventTypes, } from "./generated/stripe-integration-capabilities-version.js";
|
|
3
|
+
const stripeEventPattern = /^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/;
|
|
4
|
+
const capabilityPattern = /^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/;
|
|
5
|
+
const sha256Pattern = /^[0-9a-f]{64}$/;
|
|
6
|
+
const maximumEntries = 100;
|
|
7
|
+
export function canonicalLegacyStripeIngressEventTypes() {
|
|
8
|
+
return [...stripeLegacyCliEventTypes];
|
|
9
|
+
}
|
|
10
|
+
export function validateStripeIngressProfile(profile) {
|
|
11
|
+
if (profile.manifest_version !== stripeIntegrationCapabilitiesManifestVersion) {
|
|
12
|
+
throw profileError("Stripe ingress profile uses an incompatible manifest version");
|
|
13
|
+
}
|
|
14
|
+
if (!profile.merchant_psp_config_id.startsWith("pspcfg_") ||
|
|
15
|
+
!sha256Pattern.test(profile.profile_hash)) {
|
|
16
|
+
throw profileError("Stripe ingress profile identity is invalid");
|
|
17
|
+
}
|
|
18
|
+
assertSortedUnique(profile.event_types, stripeEventPattern, false, "event types");
|
|
19
|
+
assertSortedUnique(profile.effective_capability_ids, capabilityPattern, true, "capability ids");
|
|
20
|
+
return profile;
|
|
21
|
+
}
|
|
22
|
+
export function assertCreatedStripeIngressProfile(expected, actual) {
|
|
23
|
+
assertSortedUnique(actual.event_types, stripeEventPattern, false, "event types");
|
|
24
|
+
if (actual.manifest_version !== expected.manifest_version ||
|
|
25
|
+
actual.profile_hash !== expected.profile_hash ||
|
|
26
|
+
JSON.stringify(actual.event_types) !== JSON.stringify(expected.event_types)) {
|
|
27
|
+
throw profileError("Stripe ingress profile changed while the listener was starting");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function assertSortedUnique(values, pattern, allowEmpty, label) {
|
|
31
|
+
if (!Array.isArray(values) ||
|
|
32
|
+
(!allowEmpty && values.length === 0) ||
|
|
33
|
+
values.length > maximumEntries ||
|
|
34
|
+
values.some((value) => !pattern.test(value)) ||
|
|
35
|
+
values.some((value, index) => {
|
|
36
|
+
const previous = values[index - 1];
|
|
37
|
+
return previous !== undefined && previous >= value;
|
|
38
|
+
})) {
|
|
39
|
+
throw profileError(`Stripe ingress profile ${label} are invalid`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function profileError(message) {
|
|
43
|
+
return new CliError(message, "stripe_ingress_profile_unsupported", cliExitCodes.dependency);
|
|
44
|
+
}
|
package/dist/esm/webhooks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CliOutput } from "./output.js";
|
|
2
|
+
import { type StripeIngressProfileRecord } from "./stripe-profile.js";
|
|
2
3
|
export interface WebhookEndpointRecord {
|
|
3
4
|
event_schema_version: string;
|
|
4
5
|
event_types: string[] | null;
|
|
@@ -44,12 +45,22 @@ export declare class ProxyWebhooksClient {
|
|
|
44
45
|
errorClass: "network_error" | "timeout" | null;
|
|
45
46
|
responseStatusCode: number | null;
|
|
46
47
|
}): Promise<unknown>;
|
|
47
|
-
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
48
|
+
createStripeIngress(listenerId: string, webhookSecret: string, profileHash: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
49
|
+
event_types: string[];
|
|
48
50
|
expires_at: string;
|
|
49
51
|
id: string;
|
|
50
52
|
inbound_url: string;
|
|
53
|
+
manifest_version: number;
|
|
51
54
|
merchant_psp_config_id: string;
|
|
55
|
+
profile_hash: string;
|
|
52
56
|
}>;
|
|
57
|
+
createLegacyStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
58
|
+
expires_at: string;
|
|
59
|
+
id: string;
|
|
60
|
+
inbound_url: string;
|
|
61
|
+
merchant_psp_config_id: string;
|
|
62
|
+
}>;
|
|
63
|
+
getStripeIngressProfile(listenerId: string, pspConfigId?: string, signal?: AbortSignal): Promise<StripeIngressProfileRecord>;
|
|
53
64
|
}
|
|
54
65
|
export declare function runListener(input: {
|
|
55
66
|
client: ProxyWebhooksClient;
|
package/dist/esm/webhooks.js
CHANGED
|
@@ -2,19 +2,10 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { CliError, cliExitCodes } from "./errors.js";
|
|
3
3
|
import { ProxyApiClient } from "./http-client.js";
|
|
4
4
|
import { redactText } from "./output.js";
|
|
5
|
+
import { assertCreatedStripeIngressProfile, canonicalLegacyStripeIngressEventTypes, validateStripeIngressProfile, } from "./stripe-profile.js";
|
|
5
6
|
const claimPollIntervalMs = 1_000;
|
|
6
7
|
const heartbeatIntervalMs = 30_000;
|
|
7
8
|
const localForwardTimeoutMs = 30_000;
|
|
8
|
-
const stripeEvents = [
|
|
9
|
-
"checkout.session.completed",
|
|
10
|
-
"customer.subscription.deleted",
|
|
11
|
-
"customer.subscription.updated",
|
|
12
|
-
"invoice.paid",
|
|
13
|
-
"invoice.payment_failed",
|
|
14
|
-
"payment_intent.canceled",
|
|
15
|
-
"payment_intent.payment_failed",
|
|
16
|
-
"payment_intent.succeeded",
|
|
17
|
-
].join(",");
|
|
18
9
|
export class ProxyWebhooksClient {
|
|
19
10
|
api;
|
|
20
11
|
constructor(apiBaseUrl, token, fetchImpl) {
|
|
@@ -68,16 +59,31 @@ export class ProxyWebhooksClient {
|
|
|
68
59
|
},
|
|
69
60
|
});
|
|
70
61
|
}
|
|
71
|
-
async createStripeIngress(listenerId, webhookSecret, pspConfigId, signal) {
|
|
62
|
+
async createStripeIngress(listenerId, webhookSecret, profileHash, pspConfigId, signal) {
|
|
72
63
|
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress`, {
|
|
73
64
|
method: "POST",
|
|
74
65
|
body: {
|
|
75
66
|
...(pspConfigId ? { psp_config_id: pspConfigId } : {}),
|
|
67
|
+
profile_hash: profileHash,
|
|
76
68
|
webhook_secret: webhookSecret,
|
|
77
69
|
},
|
|
78
70
|
signal,
|
|
79
71
|
})).stripe_ingress;
|
|
80
72
|
}
|
|
73
|
+
async createLegacyStripeIngress(listenerId, webhookSecret, pspConfigId, signal) {
|
|
74
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
body: {
|
|
77
|
+
...(pspConfigId ? { psp_config_id: pspConfigId } : {}),
|
|
78
|
+
webhook_secret: webhookSecret,
|
|
79
|
+
},
|
|
80
|
+
signal,
|
|
81
|
+
})).stripe_ingress;
|
|
82
|
+
}
|
|
83
|
+
async getStripeIngressProfile(listenerId, pspConfigId, signal) {
|
|
84
|
+
const query = pspConfigId ? `?${new URLSearchParams({ psp_config_id: pspConfigId })}` : "";
|
|
85
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress-profile${query}`, { signal })).stripe_ingress_profile;
|
|
86
|
+
}
|
|
81
87
|
}
|
|
82
88
|
export async function runListener(input) {
|
|
83
89
|
const destination = validateLoopbackDestination(input.forwardTo);
|
|
@@ -97,16 +103,47 @@ export async function runListener(input) {
|
|
|
97
103
|
if (input.signal.aborted)
|
|
98
104
|
throw listenerInterrupted();
|
|
99
105
|
if (input.stripe) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
let stripeProfile;
|
|
107
|
+
try {
|
|
108
|
+
stripeProfile = validateStripeIngressProfile(await input.client.getStripeIngressProfile(listener.id, input.pspConfigId, input.signal));
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (!isMissingStripeIngressProfileRoute(error))
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
const eventTypes = stripeProfile
|
|
115
|
+
? stripeProfile.event_types
|
|
116
|
+
: canonicalLegacyStripeIngressEventTypes();
|
|
117
|
+
const webhookSecret = await captureStripeWebhookSecret(eventTypes, input.signal);
|
|
118
|
+
let ingress;
|
|
119
|
+
if (stripeProfile) {
|
|
120
|
+
const profiledIngress = await input.client.createStripeIngress(listener.id, webhookSecret, stripeProfile.profile_hash, input.pspConfigId, input.signal);
|
|
121
|
+
assertCreatedStripeIngressProfile(stripeProfile, profiledIngress);
|
|
122
|
+
ingress = profiledIngress;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
ingress = await input.client.createLegacyStripeIngress(listener.id, webhookSecret, input.pspConfigId, input.signal);
|
|
126
|
+
}
|
|
127
|
+
stripeProcess = await startStripeListener(ingress.inbound_url, eventTypes, input.output, (error) => {
|
|
103
128
|
stripeProcessFailure = stripeListenerStartError(error);
|
|
104
129
|
});
|
|
105
|
-
input.output.event("stripe.started",
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
input.output.event("stripe.started", stripeProfile
|
|
131
|
+
? {
|
|
132
|
+
effective_capability_ids: stripeProfile.effective_capability_ids,
|
|
133
|
+
event_types: eventTypes,
|
|
134
|
+
expires_at: ingress.expires_at,
|
|
135
|
+
ingress_id: ingress.id,
|
|
136
|
+
manifest_version: stripeProfile.manifest_version,
|
|
137
|
+
merchant_psp_config_id: ingress.merchant_psp_config_id,
|
|
138
|
+
profile_hash: stripeProfile.profile_hash,
|
|
139
|
+
}
|
|
140
|
+
: {
|
|
141
|
+
compatibility_mode: "legacy_api",
|
|
142
|
+
event_types: eventTypes,
|
|
143
|
+
expires_at: ingress.expires_at,
|
|
144
|
+
ingress_id: ingress.id,
|
|
145
|
+
merchant_psp_config_id: ingress.merchant_psp_config_id,
|
|
146
|
+
}, "Stripe CLI test webhook orchestration is active.");
|
|
110
147
|
}
|
|
111
148
|
if (input.signal.aborted)
|
|
112
149
|
throw listenerInterrupted();
|
|
@@ -162,6 +199,9 @@ export async function runListener(input) {
|
|
|
162
199
|
}
|
|
163
200
|
}
|
|
164
201
|
}
|
|
202
|
+
function isMissingStripeIngressProfileRoute(error) {
|
|
203
|
+
return error instanceof CliError && error.code === "http_404" && error.details?.status === 404;
|
|
204
|
+
}
|
|
165
205
|
export async function forwardSelectedDelivery(input) {
|
|
166
206
|
const destination = validateLoopbackDestination(input.forwardTo);
|
|
167
207
|
const claim = await input.client.claim(input.listenerId, {
|
|
@@ -210,8 +250,8 @@ async function forwardClaim(client, claim, destination, signal) {
|
|
|
210
250
|
responseStatusCode,
|
|
211
251
|
});
|
|
212
252
|
}
|
|
213
|
-
async function captureStripeWebhookSecret(signal) {
|
|
214
|
-
const result = await captureProcess("stripe", ["listen", "--events",
|
|
253
|
+
async function captureStripeWebhookSecret(eventTypes, signal) {
|
|
254
|
+
const result = await captureProcess("stripe", ["listen", "--events", eventTypes.join(","), "--print-secret"], signal, 30_000);
|
|
215
255
|
if (signal.aborted)
|
|
216
256
|
throw listenerInterrupted();
|
|
217
257
|
const match = `${result.stdout}\n${result.stderr}`.match(/\bwhsec_[A-Za-z0-9_-]{8,512}\b/);
|
|
@@ -223,8 +263,8 @@ async function captureStripeWebhookSecret(signal) {
|
|
|
223
263
|
function listenerInterrupted() {
|
|
224
264
|
return new CliError("Local webhook listener interrupted", "cli_listener_interrupted", cliExitCodes.interrupted);
|
|
225
265
|
}
|
|
226
|
-
function startStripeListener(inboundUrl, output, onError) {
|
|
227
|
-
const child = spawn("stripe", ["listen", "--events",
|
|
266
|
+
function startStripeListener(inboundUrl, eventTypes, output, onError) {
|
|
267
|
+
const child = spawn("stripe", ["listen", "--events", eventTypes.join(","), "--forward-to", inboundUrl], {
|
|
228
268
|
shell: false,
|
|
229
269
|
stdio: ["pipe", "pipe", "pipe"],
|
|
230
270
|
});
|