@proxy-checkout/cli 0.1.0-prx-128.114.1 → 0.1.0-prx-124.117.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 +4 -2
- package/dist/cjs/auth.d.cts +5 -0
- package/dist/cjs/auth.d.ts +5 -0
- package/dist/cjs/auth.js +16 -3
- 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/config.js +2 -0
- package/dist/cjs/generated/stripe-integration-capabilities-version.d.cts +1 -0
- package/dist/cjs/generated/stripe-integration-capabilities-version.d.ts +1 -0
- package/dist/cjs/generated/stripe-integration-capabilities-version.js +5 -0
- package/dist/cjs/stripe-profile.d.cts +13 -0
- package/dist/cjs/stripe-profile.d.ts +13 -0
- package/dist/cjs/stripe-profile.js +42 -0
- package/dist/cjs/webhooks.d.cts +6 -1
- package/dist/cjs/webhooks.d.ts +6 -1
- package/dist/cjs/webhooks.js +22 -19
- package/dist/esm/auth.d.ts +5 -0
- package/dist/esm/auth.js +15 -4
- package/dist/esm/cli.d.ts +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/config.js +2 -0
- package/dist/esm/generated/stripe-integration-capabilities-version.d.ts +1 -0
- package/dist/esm/generated/stripe-integration-capabilities-version.js +2 -0
- package/dist/esm/stripe-profile.d.ts +13 -0
- package/dist/esm/stripe-profile.js +38 -0
- package/dist/esm/webhooks.d.ts +6 -1
- package/dist/esm/webhooks.js +22 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ proxy listen \
|
|
|
45
45
|
|
|
46
46
|
The listener has a two-minute lease renewed every 30 seconds. Proxy prepares the canonical body and signature headers, the CLI forwards those exact bytes to the loopback URL, and the server records the real local outcome in its normal attempt/retry state machine. The source HTTPS endpoint continues to receive its independent normal delivery. Only `localhost`, `127.0.0.1`, and `::1` HTTP(S) destinations are allowed.
|
|
47
47
|
|
|
48
|
-
Control-C closes the listener. Logout closes resources owned by the credential. The server expires abandoned listeners after a crash.
|
|
48
|
+
Control-C closes the listener. Logout closes resources owned by the credential. If a device or local credential is lost, an authorized Portal user can revoke the CLI credential from API Keys; remote revocation also closes its active local listeners and temporary Stripe ingress. The server expires abandoned listeners after a crash.
|
|
49
49
|
|
|
50
50
|
## Optional Stripe CLI orchestration
|
|
51
51
|
|
|
@@ -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/auth.d.cts
CHANGED
|
@@ -37,4 +37,9 @@ export declare function logout(input: {
|
|
|
37
37
|
signal?: AbortSignal;
|
|
38
38
|
token: string;
|
|
39
39
|
}): Promise<void>;
|
|
40
|
+
export declare function validateVerificationUrl(value: string): string;
|
|
41
|
+
export declare function browserOpenCommand(operatingSystem: NodeJS.Platform, url: string): {
|
|
42
|
+
args: string[];
|
|
43
|
+
command: string;
|
|
44
|
+
};
|
|
40
45
|
export {};
|
package/dist/cjs/auth.d.ts
CHANGED
|
@@ -37,4 +37,9 @@ export declare function logout(input: {
|
|
|
37
37
|
signal?: AbortSignal;
|
|
38
38
|
token: string;
|
|
39
39
|
}): Promise<void>;
|
|
40
|
+
export declare function validateVerificationUrl(value: string): string;
|
|
41
|
+
export declare function browserOpenCommand(operatingSystem: NodeJS.Platform, url: string): {
|
|
42
|
+
args: string[];
|
|
43
|
+
command: string;
|
|
44
|
+
};
|
|
40
45
|
export {};
|
package/dist/cjs/auth.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.login = login;
|
|
4
4
|
exports.status = status;
|
|
5
5
|
exports.logout = logout;
|
|
6
|
+
exports.validateVerificationUrl = validateVerificationUrl;
|
|
7
|
+
exports.browserOpenCommand = browserOpenCommand;
|
|
6
8
|
const node_child_process_1 = require("node:child_process");
|
|
7
9
|
const node_os_1 = require("node:os");
|
|
8
10
|
const errors_js_1 = require("./errors.js");
|
|
@@ -243,7 +245,8 @@ function validateVerificationUrl(value) {
|
|
|
243
245
|
catch {
|
|
244
246
|
throw new errors_js_1.CliError("Proxy returned an invalid browser approval URL", "cli_invalid_auth_response", errors_js_1.cliExitCodes.network);
|
|
245
247
|
}
|
|
246
|
-
const
|
|
248
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
249
|
+
const loopback = ["127.0.0.1", "::1", "localhost"].includes(hostname);
|
|
247
250
|
if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
|
|
248
251
|
url.username ||
|
|
249
252
|
url.password) {
|
|
@@ -252,8 +255,7 @@ function validateVerificationUrl(value) {
|
|
|
252
255
|
return url.toString();
|
|
253
256
|
}
|
|
254
257
|
async function openBrowser(url) {
|
|
255
|
-
const command = (0, node_os_1.platform)()
|
|
256
|
-
const args = (0, node_os_1.platform)() === "win32" ? ["/c", "start", "", url] : [url];
|
|
258
|
+
const { args, command } = browserOpenCommand((0, node_os_1.platform)(), url);
|
|
257
259
|
return new Promise((resolve) => {
|
|
258
260
|
const child = (0, node_child_process_1.spawn)(command, args, { detached: true, shell: false, stdio: "ignore" });
|
|
259
261
|
child.once("error", () => resolve(false));
|
|
@@ -263,6 +265,17 @@ async function openBrowser(url) {
|
|
|
263
265
|
});
|
|
264
266
|
});
|
|
265
267
|
}
|
|
268
|
+
function browserOpenCommand(operatingSystem, url) {
|
|
269
|
+
if (operatingSystem === "darwin") {
|
|
270
|
+
return { args: [url], command: "open" };
|
|
271
|
+
}
|
|
272
|
+
if (operatingSystem === "win32") {
|
|
273
|
+
// Avoid cmd.exe entirely: even with shell=false, `cmd /c start` reparses URL
|
|
274
|
+
// metacharacters. Explorer receives the URL as one process argument.
|
|
275
|
+
return { args: [url], command: "explorer.exe" };
|
|
276
|
+
}
|
|
277
|
+
return { args: [url], command: "xdg-open" };
|
|
278
|
+
}
|
|
266
279
|
function deviceExpired() {
|
|
267
280
|
return new errors_js_1.CliError("Device authorization expired", "cli_device_authorization_expired", errors_js_1.cliExitCodes.authentication);
|
|
268
281
|
}
|
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.0-prx-
|
|
3
|
+
export declare const cliVersion = "0.1.0-prx-124.117.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.0-prx-
|
|
3
|
+
export declare const cliVersion = "0.1.0-prx-124.117.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.0-prx-
|
|
12
|
+
exports.cliVersion = "0.1.0-prx-124.117.1";
|
|
13
13
|
async function runCli(argv, options = {}) {
|
|
14
14
|
const io = options.io ?? {
|
|
15
15
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
package/dist/cjs/config.js
CHANGED
|
@@ -46,6 +46,8 @@ function run() {
|
|
|
46
46
|
if (operation === "read") {
|
|
47
47
|
query.setObjectForKey(true, $("r_Data"));
|
|
48
48
|
query.setObjectForKey($("m_LimitOne"), $("m_Limit"));
|
|
49
|
+
// In JXA's Objective-C bridge, nullary $() creates the mutable CFTypeRef
|
|
50
|
+
// out reference that SecItemCopyMatching fills with the returned NSData.
|
|
49
51
|
const result = $();
|
|
50
52
|
const status = $.SecItemCopyMatching(query, result);
|
|
51
53
|
if (status === -25300) return "missing";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const stripeIntegrationCapabilitiesManifestVersion: 2;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const stripeIntegrationCapabilitiesManifestVersion: 2;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripeIntegrationCapabilitiesManifestVersion = void 0;
|
|
4
|
+
// Generated by tools/compliance/sync-stripe-integration-capabilities.mjs.
|
|
5
|
+
exports.stripeIntegrationCapabilitiesManifestVersion = 2;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 validateStripeIngressProfile(profile: StripeIngressProfileRecord): StripeIngressProfileRecord;
|
|
9
|
+
export declare function assertCreatedStripeIngressProfile(expected: StripeIngressProfileRecord, actual: {
|
|
10
|
+
event_types: string[];
|
|
11
|
+
manifest_version: number;
|
|
12
|
+
profile_hash: string;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 validateStripeIngressProfile(profile: StripeIngressProfileRecord): StripeIngressProfileRecord;
|
|
9
|
+
export declare function assertCreatedStripeIngressProfile(expected: StripeIngressProfileRecord, actual: {
|
|
10
|
+
event_types: string[];
|
|
11
|
+
manifest_version: number;
|
|
12
|
+
profile_hash: string;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateStripeIngressProfile = validateStripeIngressProfile;
|
|
4
|
+
exports.assertCreatedStripeIngressProfile = assertCreatedStripeIngressProfile;
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
const stripe_integration_capabilities_version_js_1 = require("./generated/stripe-integration-capabilities-version.js");
|
|
7
|
+
const stripeEventPattern = /^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/;
|
|
8
|
+
const capabilityPattern = /^[a-z0-9_]+(?:\.[a-z0-9_]+)+$/;
|
|
9
|
+
const sha256Pattern = /^[0-9a-f]{64}$/;
|
|
10
|
+
const maximumEntries = 100;
|
|
11
|
+
function validateStripeIngressProfile(profile) {
|
|
12
|
+
if (profile.manifest_version !== stripe_integration_capabilities_version_js_1.stripeIntegrationCapabilitiesManifestVersion) {
|
|
13
|
+
throw profileError("Stripe ingress profile uses an incompatible manifest version");
|
|
14
|
+
}
|
|
15
|
+
if (!profile.merchant_psp_config_id.startsWith("pspcfg_") ||
|
|
16
|
+
!sha256Pattern.test(profile.profile_hash)) {
|
|
17
|
+
throw profileError("Stripe ingress profile identity is invalid");
|
|
18
|
+
}
|
|
19
|
+
assertSortedUnique(profile.event_types, stripeEventPattern, false, "event types");
|
|
20
|
+
assertSortedUnique(profile.effective_capability_ids, capabilityPattern, true, "capability ids");
|
|
21
|
+
return profile;
|
|
22
|
+
}
|
|
23
|
+
function assertCreatedStripeIngressProfile(expected, actual) {
|
|
24
|
+
assertSortedUnique(actual.event_types, stripeEventPattern, false, "event types");
|
|
25
|
+
if (actual.manifest_version !== expected.manifest_version ||
|
|
26
|
+
actual.profile_hash !== expected.profile_hash ||
|
|
27
|
+
JSON.stringify(actual.event_types) !== JSON.stringify(expected.event_types)) {
|
|
28
|
+
throw profileError("Stripe ingress profile changed while the listener was starting");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertSortedUnique(values, pattern, allowEmpty, label) {
|
|
32
|
+
if (!Array.isArray(values) ||
|
|
33
|
+
(!allowEmpty && values.length === 0) ||
|
|
34
|
+
values.length > maximumEntries ||
|
|
35
|
+
values.some((value) => !pattern.test(value)) ||
|
|
36
|
+
values.some((value, index) => index > 0 && values[index - 1] >= value)) {
|
|
37
|
+
throw profileError(`Stripe ingress profile ${label} are invalid`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function profileError(message) {
|
|
41
|
+
return new errors_js_1.CliError(message, "stripe_ingress_profile_unsupported", errors_js_1.cliExitCodes.dependency);
|
|
42
|
+
}
|
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,16 @@ 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
|
+
getStripeIngressProfile(listenerId: string, pspConfigId?: string, signal?: AbortSignal): Promise<StripeIngressProfileRecord>;
|
|
53
58
|
}
|
|
54
59
|
export declare function runListener(input: {
|
|
55
60
|
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,16 @@ 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
|
+
getStripeIngressProfile(listenerId: string, pspConfigId?: string, signal?: AbortSignal): Promise<StripeIngressProfileRecord>;
|
|
53
58
|
}
|
|
54
59
|
export declare function runListener(input: {
|
|
55
60
|
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,21 @@ 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 getStripeIngressProfile(listenerId, pspConfigId, signal) {
|
|
79
|
+
const query = pspConfigId ? `?${new URLSearchParams({ psp_config_id: pspConfigId })}` : "";
|
|
80
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress-profile${query}`, { signal })).stripe_ingress_profile;
|
|
81
|
+
}
|
|
86
82
|
}
|
|
87
83
|
exports.ProxyWebhooksClient = ProxyWebhooksClient;
|
|
88
84
|
async function runListener(input) {
|
|
@@ -103,15 +99,21 @@ async function runListener(input) {
|
|
|
103
99
|
if (input.signal.aborted)
|
|
104
100
|
throw listenerInterrupted();
|
|
105
101
|
if (input.stripe) {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
102
|
+
const stripeProfile = (0, stripe_profile_js_1.validateStripeIngressProfile)(await input.client.getStripeIngressProfile(listener.id, input.pspConfigId, input.signal));
|
|
103
|
+
const webhookSecret = await captureStripeWebhookSecret(stripeProfile.event_types, input.signal);
|
|
104
|
+
const ingress = await input.client.createStripeIngress(listener.id, webhookSecret, stripeProfile.profile_hash, input.pspConfigId, input.signal);
|
|
105
|
+
(0, stripe_profile_js_1.assertCreatedStripeIngressProfile)(stripeProfile, ingress);
|
|
106
|
+
stripeProcess = await startStripeListener(ingress.inbound_url, ingress.event_types, input.output, (error) => {
|
|
109
107
|
stripeProcessFailure = stripeListenerStartError(error);
|
|
110
108
|
});
|
|
111
109
|
input.output.event("stripe.started", {
|
|
110
|
+
effective_capability_ids: stripeProfile.effective_capability_ids,
|
|
111
|
+
event_types: ingress.event_types,
|
|
112
112
|
expires_at: ingress.expires_at,
|
|
113
113
|
ingress_id: ingress.id,
|
|
114
|
+
manifest_version: ingress.manifest_version,
|
|
114
115
|
merchant_psp_config_id: ingress.merchant_psp_config_id,
|
|
116
|
+
profile_hash: ingress.profile_hash,
|
|
115
117
|
}, "Stripe CLI test webhook orchestration is active.");
|
|
116
118
|
}
|
|
117
119
|
if (input.signal.aborted)
|
|
@@ -169,6 +171,7 @@ async function runListener(input) {
|
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
async function forwardSelectedDelivery(input) {
|
|
174
|
+
const destination = validateLoopbackDestination(input.forwardTo);
|
|
172
175
|
const claim = await input.client.claim(input.listenerId, {
|
|
173
176
|
...(input.deliveryId ? { deliveryId: input.deliveryId } : {}),
|
|
174
177
|
...(input.replayOutboxEventId ? { replayOutboxEventId: input.replayOutboxEventId } : {}),
|
|
@@ -176,7 +179,7 @@ async function forwardSelectedDelivery(input) {
|
|
|
176
179
|
if (!claim) {
|
|
177
180
|
throw new errors_js_1.CliError("Selected delivery or replay is not currently claimable", "cli_delivery_not_claimable", errors_js_1.cliExitCodes.conflict);
|
|
178
181
|
}
|
|
179
|
-
const completion = await forwardClaim(input.client, claim,
|
|
182
|
+
const completion = await forwardClaim(input.client, claim, destination, input.signal);
|
|
180
183
|
return {
|
|
181
184
|
attempt_id: claim.attempt_id,
|
|
182
185
|
attempt_number: claim.attempt_number,
|
|
@@ -215,8 +218,8 @@ async function forwardClaim(client, claim, destination, signal) {
|
|
|
215
218
|
responseStatusCode,
|
|
216
219
|
});
|
|
217
220
|
}
|
|
218
|
-
async function captureStripeWebhookSecret(signal) {
|
|
219
|
-
const result = await captureProcess("stripe", ["listen", "--events",
|
|
221
|
+
async function captureStripeWebhookSecret(eventTypes, signal) {
|
|
222
|
+
const result = await captureProcess("stripe", ["listen", "--events", eventTypes.join(","), "--print-secret"], signal, 30_000);
|
|
220
223
|
if (signal.aborted)
|
|
221
224
|
throw listenerInterrupted();
|
|
222
225
|
const match = `${result.stdout}\n${result.stderr}`.match(/\bwhsec_[A-Za-z0-9_-]{8,512}\b/);
|
|
@@ -228,8 +231,8 @@ async function captureStripeWebhookSecret(signal) {
|
|
|
228
231
|
function listenerInterrupted() {
|
|
229
232
|
return new errors_js_1.CliError("Local webhook listener interrupted", "cli_listener_interrupted", errors_js_1.cliExitCodes.interrupted);
|
|
230
233
|
}
|
|
231
|
-
function startStripeListener(inboundUrl, output, onError) {
|
|
232
|
-
const child = (0, node_child_process_1.spawn)("stripe", ["listen", "--events",
|
|
234
|
+
function startStripeListener(inboundUrl, eventTypes, output, onError) {
|
|
235
|
+
const child = (0, node_child_process_1.spawn)("stripe", ["listen", "--events", eventTypes.join(","), "--forward-to", inboundUrl], {
|
|
233
236
|
shell: false,
|
|
234
237
|
stdio: ["pipe", "pipe", "pipe"],
|
|
235
238
|
});
|
package/dist/esm/auth.d.ts
CHANGED
|
@@ -37,4 +37,9 @@ export declare function logout(input: {
|
|
|
37
37
|
signal?: AbortSignal;
|
|
38
38
|
token: string;
|
|
39
39
|
}): Promise<void>;
|
|
40
|
+
export declare function validateVerificationUrl(value: string): string;
|
|
41
|
+
export declare function browserOpenCommand(operatingSystem: NodeJS.Platform, url: string): {
|
|
42
|
+
args: string[];
|
|
43
|
+
command: string;
|
|
44
|
+
};
|
|
40
45
|
export {};
|
package/dist/esm/auth.js
CHANGED
|
@@ -230,7 +230,7 @@ function normalizeCredentialName(name) {
|
|
|
230
230
|
}
|
|
231
231
|
return value;
|
|
232
232
|
}
|
|
233
|
-
function validateVerificationUrl(value) {
|
|
233
|
+
export function validateVerificationUrl(value) {
|
|
234
234
|
let url;
|
|
235
235
|
try {
|
|
236
236
|
url = new URL(value);
|
|
@@ -238,7 +238,8 @@ function validateVerificationUrl(value) {
|
|
|
238
238
|
catch {
|
|
239
239
|
throw new CliError("Proxy returned an invalid browser approval URL", "cli_invalid_auth_response", cliExitCodes.network);
|
|
240
240
|
}
|
|
241
|
-
const
|
|
241
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
242
|
+
const loopback = ["127.0.0.1", "::1", "localhost"].includes(hostname);
|
|
242
243
|
if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
|
|
243
244
|
url.username ||
|
|
244
245
|
url.password) {
|
|
@@ -247,8 +248,7 @@ function validateVerificationUrl(value) {
|
|
|
247
248
|
return url.toString();
|
|
248
249
|
}
|
|
249
250
|
async function openBrowser(url) {
|
|
250
|
-
const command = platform()
|
|
251
|
-
const args = platform() === "win32" ? ["/c", "start", "", url] : [url];
|
|
251
|
+
const { args, command } = browserOpenCommand(platform(), url);
|
|
252
252
|
return new Promise((resolve) => {
|
|
253
253
|
const child = spawn(command, args, { detached: true, shell: false, stdio: "ignore" });
|
|
254
254
|
child.once("error", () => resolve(false));
|
|
@@ -258,6 +258,17 @@ async function openBrowser(url) {
|
|
|
258
258
|
});
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
|
+
export function browserOpenCommand(operatingSystem, url) {
|
|
262
|
+
if (operatingSystem === "darwin") {
|
|
263
|
+
return { args: [url], command: "open" };
|
|
264
|
+
}
|
|
265
|
+
if (operatingSystem === "win32") {
|
|
266
|
+
// Avoid cmd.exe entirely: even with shell=false, `cmd /c start` reparses URL
|
|
267
|
+
// metacharacters. Explorer receives the URL as one process argument.
|
|
268
|
+
return { args: [url], command: "explorer.exe" };
|
|
269
|
+
}
|
|
270
|
+
return { args: [url], command: "xdg-open" };
|
|
271
|
+
}
|
|
261
272
|
function deviceExpired() {
|
|
262
273
|
return new CliError("Device authorization expired", "cli_device_authorization_expired", cliExitCodes.authentication);
|
|
263
274
|
}
|
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.0-prx-
|
|
3
|
+
export declare const cliVersion = "0.1.0-prx-124.117.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.0-prx-
|
|
8
|
+
export const cliVersion = "0.1.0-prx-124.117.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),
|
package/dist/esm/config.js
CHANGED
|
@@ -41,6 +41,8 @@ function run() {
|
|
|
41
41
|
if (operation === "read") {
|
|
42
42
|
query.setObjectForKey(true, $("r_Data"));
|
|
43
43
|
query.setObjectForKey($("m_LimitOne"), $("m_Limit"));
|
|
44
|
+
// In JXA's Objective-C bridge, nullary $() creates the mutable CFTypeRef
|
|
45
|
+
// out reference that SecItemCopyMatching fills with the returned NSData.
|
|
44
46
|
const result = $();
|
|
45
47
|
const status = $.SecItemCopyMatching(query, result);
|
|
46
48
|
if (status === -25300) return "missing";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const stripeIntegrationCapabilitiesManifestVersion: 2;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 validateStripeIngressProfile(profile: StripeIngressProfileRecord): StripeIngressProfileRecord;
|
|
9
|
+
export declare function assertCreatedStripeIngressProfile(expected: StripeIngressProfileRecord, actual: {
|
|
10
|
+
event_types: string[];
|
|
11
|
+
manifest_version: number;
|
|
12
|
+
profile_hash: string;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CliError, cliExitCodes } from "./errors.js";
|
|
2
|
+
import { stripeIntegrationCapabilitiesManifestVersion } 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 validateStripeIngressProfile(profile) {
|
|
8
|
+
if (profile.manifest_version !== stripeIntegrationCapabilitiesManifestVersion) {
|
|
9
|
+
throw profileError("Stripe ingress profile uses an incompatible manifest version");
|
|
10
|
+
}
|
|
11
|
+
if (!profile.merchant_psp_config_id.startsWith("pspcfg_") ||
|
|
12
|
+
!sha256Pattern.test(profile.profile_hash)) {
|
|
13
|
+
throw profileError("Stripe ingress profile identity is invalid");
|
|
14
|
+
}
|
|
15
|
+
assertSortedUnique(profile.event_types, stripeEventPattern, false, "event types");
|
|
16
|
+
assertSortedUnique(profile.effective_capability_ids, capabilityPattern, true, "capability ids");
|
|
17
|
+
return profile;
|
|
18
|
+
}
|
|
19
|
+
export function assertCreatedStripeIngressProfile(expected, actual) {
|
|
20
|
+
assertSortedUnique(actual.event_types, stripeEventPattern, false, "event types");
|
|
21
|
+
if (actual.manifest_version !== expected.manifest_version ||
|
|
22
|
+
actual.profile_hash !== expected.profile_hash ||
|
|
23
|
+
JSON.stringify(actual.event_types) !== JSON.stringify(expected.event_types)) {
|
|
24
|
+
throw profileError("Stripe ingress profile changed while the listener was starting");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function assertSortedUnique(values, pattern, allowEmpty, label) {
|
|
28
|
+
if (!Array.isArray(values) ||
|
|
29
|
+
(!allowEmpty && values.length === 0) ||
|
|
30
|
+
values.length > maximumEntries ||
|
|
31
|
+
values.some((value) => !pattern.test(value)) ||
|
|
32
|
+
values.some((value, index) => index > 0 && values[index - 1] >= value)) {
|
|
33
|
+
throw profileError(`Stripe ingress profile ${label} are invalid`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function profileError(message) {
|
|
37
|
+
return new CliError(message, "stripe_ingress_profile_unsupported", cliExitCodes.dependency);
|
|
38
|
+
}
|
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,16 @@ 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
|
+
getStripeIngressProfile(listenerId: string, pspConfigId?: string, signal?: AbortSignal): Promise<StripeIngressProfileRecord>;
|
|
53
58
|
}
|
|
54
59
|
export declare function runListener(input: {
|
|
55
60
|
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, 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,21 @@ 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 getStripeIngressProfile(listenerId, pspConfigId, signal) {
|
|
74
|
+
const query = pspConfigId ? `?${new URLSearchParams({ psp_config_id: pspConfigId })}` : "";
|
|
75
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress-profile${query}`, { signal })).stripe_ingress_profile;
|
|
76
|
+
}
|
|
81
77
|
}
|
|
82
78
|
export async function runListener(input) {
|
|
83
79
|
const destination = validateLoopbackDestination(input.forwardTo);
|
|
@@ -97,15 +93,21 @@ export async function runListener(input) {
|
|
|
97
93
|
if (input.signal.aborted)
|
|
98
94
|
throw listenerInterrupted();
|
|
99
95
|
if (input.stripe) {
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
96
|
+
const stripeProfile = validateStripeIngressProfile(await input.client.getStripeIngressProfile(listener.id, input.pspConfigId, input.signal));
|
|
97
|
+
const webhookSecret = await captureStripeWebhookSecret(stripeProfile.event_types, input.signal);
|
|
98
|
+
const ingress = await input.client.createStripeIngress(listener.id, webhookSecret, stripeProfile.profile_hash, input.pspConfigId, input.signal);
|
|
99
|
+
assertCreatedStripeIngressProfile(stripeProfile, ingress);
|
|
100
|
+
stripeProcess = await startStripeListener(ingress.inbound_url, ingress.event_types, input.output, (error) => {
|
|
103
101
|
stripeProcessFailure = stripeListenerStartError(error);
|
|
104
102
|
});
|
|
105
103
|
input.output.event("stripe.started", {
|
|
104
|
+
effective_capability_ids: stripeProfile.effective_capability_ids,
|
|
105
|
+
event_types: ingress.event_types,
|
|
106
106
|
expires_at: ingress.expires_at,
|
|
107
107
|
ingress_id: ingress.id,
|
|
108
|
+
manifest_version: ingress.manifest_version,
|
|
108
109
|
merchant_psp_config_id: ingress.merchant_psp_config_id,
|
|
110
|
+
profile_hash: ingress.profile_hash,
|
|
109
111
|
}, "Stripe CLI test webhook orchestration is active.");
|
|
110
112
|
}
|
|
111
113
|
if (input.signal.aborted)
|
|
@@ -163,6 +165,7 @@ export async function runListener(input) {
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
export async function forwardSelectedDelivery(input) {
|
|
168
|
+
const destination = validateLoopbackDestination(input.forwardTo);
|
|
166
169
|
const claim = await input.client.claim(input.listenerId, {
|
|
167
170
|
...(input.deliveryId ? { deliveryId: input.deliveryId } : {}),
|
|
168
171
|
...(input.replayOutboxEventId ? { replayOutboxEventId: input.replayOutboxEventId } : {}),
|
|
@@ -170,7 +173,7 @@ export async function forwardSelectedDelivery(input) {
|
|
|
170
173
|
if (!claim) {
|
|
171
174
|
throw new CliError("Selected delivery or replay is not currently claimable", "cli_delivery_not_claimable", cliExitCodes.conflict);
|
|
172
175
|
}
|
|
173
|
-
const completion = await forwardClaim(input.client, claim,
|
|
176
|
+
const completion = await forwardClaim(input.client, claim, destination, input.signal);
|
|
174
177
|
return {
|
|
175
178
|
attempt_id: claim.attempt_id,
|
|
176
179
|
attempt_number: claim.attempt_number,
|
|
@@ -209,8 +212,8 @@ async function forwardClaim(client, claim, destination, signal) {
|
|
|
209
212
|
responseStatusCode,
|
|
210
213
|
});
|
|
211
214
|
}
|
|
212
|
-
async function captureStripeWebhookSecret(signal) {
|
|
213
|
-
const result = await captureProcess("stripe", ["listen", "--events",
|
|
215
|
+
async function captureStripeWebhookSecret(eventTypes, signal) {
|
|
216
|
+
const result = await captureProcess("stripe", ["listen", "--events", eventTypes.join(","), "--print-secret"], signal, 30_000);
|
|
214
217
|
if (signal.aborted)
|
|
215
218
|
throw listenerInterrupted();
|
|
216
219
|
const match = `${result.stdout}\n${result.stderr}`.match(/\bwhsec_[A-Za-z0-9_-]{8,512}\b/);
|
|
@@ -222,8 +225,8 @@ async function captureStripeWebhookSecret(signal) {
|
|
|
222
225
|
function listenerInterrupted() {
|
|
223
226
|
return new CliError("Local webhook listener interrupted", "cli_listener_interrupted", cliExitCodes.interrupted);
|
|
224
227
|
}
|
|
225
|
-
function startStripeListener(inboundUrl, output, onError) {
|
|
226
|
-
const child = spawn("stripe", ["listen", "--events",
|
|
228
|
+
function startStripeListener(inboundUrl, eventTypes, output, onError) {
|
|
229
|
+
const child = spawn("stripe", ["listen", "--events", eventTypes.join(","), "--forward-to", inboundUrl], {
|
|
227
230
|
shell: false,
|
|
228
231
|
stdio: ["pipe", "pipe", "pipe"],
|
|
229
232
|
});
|