@paybond/kit 0.12.0 → 0.12.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/dist/agent/evidence.d.ts +10 -0
- package/dist/agent/evidence.js +16 -0
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/interceptor.js +4 -0
- package/dist/agent/receipt-client.d.ts +16 -1
- package/dist/agent/run.d.ts +3 -0
- package/dist/agent/run.js +1 -1
- package/dist/agent-mandate.d.ts +103 -0
- package/dist/agent-mandate.js +423 -0
- package/dist/agent-receipt-external-attestations.d.ts +27 -0
- package/dist/agent-receipt-external-attestations.js +61 -0
- package/dist/agent-receipt-pdf-export.d.ts +43 -0
- package/dist/agent-receipt-pdf-export.js +109 -0
- package/dist/agent-receipt-prevalidate.d.ts +7 -0
- package/dist/agent-receipt-prevalidate.js +63 -0
- package/dist/agent-receipt.d.ts +21 -1
- package/dist/agent-receipt.js +63 -4
- package/dist/cli/command-spec.js +50 -1
- package/dist/cli/commands/dev.js +10 -3
- package/dist/cli/commands/setup.js +19 -1
- package/dist/cli/commands/shopify.d.ts +53 -0
- package/dist/cli/commands/shopify.js +808 -0
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +13 -3
- package/dist/cli/router.js +15 -1
- package/dist/commerce-binding.d.ts +59 -0
- package/dist/commerce-binding.js +129 -0
- package/dist/doctor-completion.d.ts +16 -0
- package/dist/doctor-completion.js +157 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +43 -7
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +28 -9
- package/dist/policy/presets.d.ts +1 -1
- package/dist/policy/presets.js +1 -0
- package/dist/project-init.d.ts +1 -1
- package/dist/project-init.js +1 -0
- package/dist/protocol-receipt.d.ts +129 -0
- package/dist/protocol-receipt.js +620 -0
- package/dist/shopify/checkout.d.ts +29 -0
- package/dist/shopify/checkout.js +79 -0
- package/dist/shopify/evidence.d.ts +13 -0
- package/dist/shopify/evidence.js +85 -0
- package/dist/shopify/index.d.ts +8 -0
- package/dist/shopify/index.js +6 -0
- package/dist/shopify/instrument.d.ts +61 -0
- package/dist/shopify/instrument.js +52 -0
- package/dist/shopify/order.d.ts +8 -0
- package/dist/shopify/order.js +110 -0
- package/dist/shopify/types.d.ts +82 -0
- package/dist/shopify/types.js +4 -0
- package/dist/solutions/catalog.d.ts +1 -1
- package/dist/solutions/catalog.js +1 -1
- package/dist/stripe-commerce/evidence.d.ts +13 -0
- package/dist/stripe-commerce/evidence.js +164 -0
- package/dist/stripe-commerce/index.d.ts +3 -0
- package/dist/stripe-commerce/index.js +2 -0
- package/dist/stripe-commerce/metadata.d.ts +11 -0
- package/dist/stripe-commerce/metadata.js +34 -0
- package/dist/stripe-commerce/types.d.ts +38 -0
- package/dist/stripe-commerce/types.js +1 -0
- package/dist/template-init.d.ts +1 -1
- package/dist/template-init.js +6 -1
- package/package.json +1 -1
- package/policy/presets/stripe-commerce.yaml +19 -0
- package/solutions/stripe-commerce.json +19 -0
- package/templates/manifest.json +82 -10
- package/templates/openai-shopping-agent/package-lock.json +10 -10
- package/templates/openai-shopping-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-aws-operator/package-lock.json +7 -7
- package/templates/paybond-aws-operator/src/paybond.config.ts +1 -1
- package/templates/paybond-claude-agents-demo/package-lock.json +7 -7
- package/templates/paybond-claude-agents-demo/src/paybond.config.ts +1 -1
- package/templates/paybond-mastra-travel-agent/package-lock.json +30 -30
- package/templates/paybond-mastra-travel-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-mcp-coding-agent/package-lock.json +7 -7
- package/templates/paybond-mcp-coding-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-openai-agents-demo/package-lock.json +10 -10
- package/templates/paybond-openai-agents-demo/src/paybond.config.ts +1 -1
- package/templates/paybond-procurement-agent/package-lock.json +7 -7
- package/templates/paybond-procurement-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-shopify-shopping-agent/.env.example +3 -0
- package/templates/paybond-shopify-shopping-agent/.github/workflows/smoke.yml +20 -0
- package/templates/paybond-shopify-shopping-agent/LICENSE +201 -0
- package/templates/paybond-shopify-shopping-agent/README.md +29 -0
- package/templates/paybond-shopify-shopping-agent/package-lock.json +223 -0
- package/templates/paybond-shopify-shopping-agent/package.json +20 -0
- package/templates/paybond-shopify-shopping-agent/paybond.policy.yaml +22 -0
- package/templates/paybond-shopify-shopping-agent/src/index.ts +54 -0
- package/templates/paybond-shopify-shopping-agent/src/paybond.config.ts +51 -0
- package/templates/paybond-shopify-shopping-agent/tsconfig.json +13 -0
- package/templates/paybond-stripe-agent-demo/.env.example +9 -0
- package/templates/paybond-stripe-agent-demo/.github/workflows/smoke.yml +20 -0
- package/templates/paybond-stripe-agent-demo/LICENSE +201 -0
- package/templates/paybond-stripe-agent-demo/README.md +50 -0
- package/templates/paybond-stripe-agent-demo/package-lock.json +223 -0
- package/templates/paybond-stripe-agent-demo/package.json +20 -0
- package/templates/paybond-stripe-agent-demo/paybond.policy.yaml +22 -0
- package/templates/paybond-stripe-agent-demo/src/charge-customer.ts +237 -0
- package/templates/paybond-stripe-agent-demo/src/index.ts +78 -0
- package/templates/paybond-stripe-agent-demo/src/paybond.config.ts +51 -0
- package/templates/paybond-stripe-agent-demo/tsconfig.json +13 -0
- package/templates/paybond-travel-agent/package-lock.json +13 -13
- package/templates/paybond-travel-agent/src/paybond.config.ts +1 -1
- package/templates/paybond-vercel-shopping-agent/package-lock.json +7 -7
- package/templates/paybond-vercel-shopping-agent/src/paybond.config.ts +1 -1
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getOrder } from "../../shopify/order.js";
|
|
5
|
+
import { getSolutionSmokeDefaults } from "../../solutions/catalog.js";
|
|
6
|
+
import { devTraceUrl } from "../../dev/trace-buffer.js";
|
|
7
|
+
import { colorize, shouldUseColor } from "../color.js";
|
|
8
|
+
import { withGateway } from "../context.js";
|
|
9
|
+
import { resolveApiKey } from "../credentials.js";
|
|
10
|
+
import { consumeBooleanFlag, consumeFlag } from "../globals.js";
|
|
11
|
+
import { requireSecureGatewayUrl } from "../../gateway-url.js";
|
|
12
|
+
import { CliError } from "../types.js";
|
|
13
|
+
import { handleAgentSandboxSmoke } from "./agent.js";
|
|
14
|
+
import { assertOfflineDevCredentialsSafe, beginOfflineDevSession } from "../offline-session.js";
|
|
15
|
+
let commandHooks = {};
|
|
16
|
+
/** Test hook: override external CLI discovery and execution. */
|
|
17
|
+
export function setShopifyCommandHooks(hooks) {
|
|
18
|
+
commandHooks = hooks;
|
|
19
|
+
}
|
|
20
|
+
function shopifyCliError(message, options) {
|
|
21
|
+
return new CliError(message, {
|
|
22
|
+
category: options.category ?? "validation",
|
|
23
|
+
code: options.code,
|
|
24
|
+
details: options.details ?? {},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function defaultWhichExecutable(name) {
|
|
28
|
+
const lookup = process.platform === "win32" ? "where" : "which";
|
|
29
|
+
const result = await defaultRunCommand(lookup, [name]);
|
|
30
|
+
if (result.code !== 0) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const line = result.stdout.split(/\r?\n/).find((entry) => entry.trim().length > 0);
|
|
34
|
+
return line?.trim() ?? null;
|
|
35
|
+
}
|
|
36
|
+
function defaultRunCommand(command, args) {
|
|
37
|
+
return new Promise((resolvePromise) => {
|
|
38
|
+
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
39
|
+
let stdout = "";
|
|
40
|
+
let stderr = "";
|
|
41
|
+
child.stdout.on("data", (chunk) => {
|
|
42
|
+
stdout += chunk.toString("utf8");
|
|
43
|
+
});
|
|
44
|
+
child.stderr.on("data", (chunk) => {
|
|
45
|
+
stderr += chunk.toString("utf8");
|
|
46
|
+
});
|
|
47
|
+
child.on("error", (err) => {
|
|
48
|
+
resolvePromise({ code: 127, stdout: "", stderr: err.message });
|
|
49
|
+
});
|
|
50
|
+
child.on("close", (code) => {
|
|
51
|
+
resolvePromise({ code: code ?? 1, stdout, stderr });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function whichExecutable(name) {
|
|
56
|
+
return (commandHooks.whichExecutable ?? defaultWhichExecutable)(name);
|
|
57
|
+
}
|
|
58
|
+
async function runCommand(command, args) {
|
|
59
|
+
return (commandHooks.runCommand ?? defaultRunCommand)(command, args);
|
|
60
|
+
}
|
|
61
|
+
/** Parse `client_id` from a linked `shopify.app.toml` in the working directory. */
|
|
62
|
+
export async function readShopifyAppToml(cwd) {
|
|
63
|
+
const path = join(cwd, "shopify.app.toml");
|
|
64
|
+
try {
|
|
65
|
+
const content = await readFile(path, "utf8");
|
|
66
|
+
const match = content.match(/^\s*client_id\s*=\s*"([^"]+)"/m);
|
|
67
|
+
return { exists: true, clientId: match?.[1], path };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { exists: false };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resolveShopDomain(raw, cwd) {
|
|
74
|
+
const fromArg = raw?.trim();
|
|
75
|
+
if (fromArg) {
|
|
76
|
+
return fromArg.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
77
|
+
}
|
|
78
|
+
const fromEnv = process.env.SHOPIFY_DEV_STORE?.trim();
|
|
79
|
+
if (fromEnv) {
|
|
80
|
+
return fromEnv.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
function formatDoctorChecklistLine(check, useColor) {
|
|
85
|
+
const prefix = check.ok ? colorize("✓", "green", useColor) : colorize("✗", "yellow", useColor);
|
|
86
|
+
return `${prefix} ${check.name}: ${check.message}`;
|
|
87
|
+
}
|
|
88
|
+
export function formatShopifyDoctorChecklist(checks, useColor) {
|
|
89
|
+
const lines = checks.map((check) => formatDoctorChecklistLine(check, useColor));
|
|
90
|
+
const summary = checks.every((check) => check.ok) ? "pass" : "fail";
|
|
91
|
+
lines.push(colorize(`shopify doctor: ${summary}`, summary === "pass" ? "green" : "yellow", useColor));
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
94
|
+
export function buildShopifyNextStepsBanner(shopDomain) {
|
|
95
|
+
const shop = shopDomain ?? "paybond-agent-commerce-dev.myshopify.com";
|
|
96
|
+
return [
|
|
97
|
+
"",
|
|
98
|
+
"── Shopify next steps ──",
|
|
99
|
+
" Terminal 1: paybond dev trace",
|
|
100
|
+
" Terminal 2: cd examples/shopify-dev-loop && shopify app dev",
|
|
101
|
+
` Smoke: paybond shopify checkout smoke --shop ${shop}`,
|
|
102
|
+
" Doctor: paybond shopify doctor",
|
|
103
|
+
" Docs: https://docs.paybond.ai/kit/shopify-cli",
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
/** Resolve a Paybond gateway origin to the Shopify sandbox webhook path. */
|
|
107
|
+
export function resolveShopifyWebhookAddress(gatewayBase, tunnel) {
|
|
108
|
+
const origin = (tunnel?.trim() || gatewayBase).trim().replace(/\/$/, "");
|
|
109
|
+
const secure = requireSecureGatewayUrl(origin);
|
|
110
|
+
return `${secure}/webhooks/sandbox/shopify`;
|
|
111
|
+
}
|
|
112
|
+
export function buildShopifyWebhookTriggerCommand(input) {
|
|
113
|
+
const args = ["app", "webhook", "trigger", `--topic=${input.topic}`, `--address=${input.address}`];
|
|
114
|
+
if (input.clientId && !input.clientId.startsWith("env:")) {
|
|
115
|
+
args.push(`--client-id=${input.clientId}`);
|
|
116
|
+
}
|
|
117
|
+
return args;
|
|
118
|
+
}
|
|
119
|
+
async function fetchSettlementConfig(ctx) {
|
|
120
|
+
try {
|
|
121
|
+
await resolveApiKey(ctx.globals, ctx.cwd);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const result = await withGateway(ctx, async (gateway) => {
|
|
128
|
+
const body = await gateway.getJson("/v1/admin/settlement/config");
|
|
129
|
+
return { data: body };
|
|
130
|
+
});
|
|
131
|
+
return result.data;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function railReadinessForShopifyPayments(config) {
|
|
138
|
+
const readiness = config?.rail_readiness?.find((entry) => entry.rail === "shopify_payments_app");
|
|
139
|
+
if (!readiness) {
|
|
140
|
+
return {
|
|
141
|
+
name: "payments_rail_readiness",
|
|
142
|
+
ok: false,
|
|
143
|
+
message: "shopify_payments_app readiness unavailable (login and link the Paybond Payments app)",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
name: "payments_rail_readiness",
|
|
148
|
+
ok: readiness.ready === true,
|
|
149
|
+
message: readiness.message ?? (readiness.ready ? "shopify_payments_app ready" : "not ready"),
|
|
150
|
+
details: { rail: readiness.rail, ready: readiness.ready },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Locate a Paybond Payments `shopify.app.toml` for local dev (cwd or monorepo app path). */
|
|
154
|
+
export async function readShopifyPaymentsAppToml(cwd) {
|
|
155
|
+
const candidates = [
|
|
156
|
+
join(cwd, "shopify.app.toml"),
|
|
157
|
+
join(cwd, "apps/shopify-payments/shopify.app.toml"),
|
|
158
|
+
];
|
|
159
|
+
for (const path of candidates) {
|
|
160
|
+
try {
|
|
161
|
+
const content = await readFile(path, "utf8");
|
|
162
|
+
const match = content.match(/^\s*client_id\s*=\s*"([^"]+)"/m);
|
|
163
|
+
return { exists: true, clientId: match?.[1], path };
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// try next candidate
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { exists: false };
|
|
170
|
+
}
|
|
171
|
+
export function buildShopifyPaymentsNextStepsBanner(shopDomain) {
|
|
172
|
+
const shop = shopDomain ?? "paybond-agent-commerce-dev.myshopify.com";
|
|
173
|
+
return [
|
|
174
|
+
"",
|
|
175
|
+
"── Shopify Payments app next steps ──",
|
|
176
|
+
" Terminal 1: paybond dev trace",
|
|
177
|
+
" Terminal 2: cd apps/shopify-payments && shopify app dev",
|
|
178
|
+
` Smoke: paybond shopify payments smoke --shop ${shop}`,
|
|
179
|
+
" Doctor: paybond shopify payments doctor",
|
|
180
|
+
" Docs: https://docs.paybond.ai/guides/configure-shopify-settlement",
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
function railReadinessForShopify(config) {
|
|
184
|
+
const readiness = config?.rail_readiness?.find((entry) => entry.rail === "shopify_authorized_order");
|
|
185
|
+
if (!readiness) {
|
|
186
|
+
return {
|
|
187
|
+
name: "rail_readiness",
|
|
188
|
+
ok: false,
|
|
189
|
+
message: "shopify_authorized_order readiness unavailable (login and link a shop)",
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
name: "rail_readiness",
|
|
194
|
+
ok: readiness.ready === true,
|
|
195
|
+
message: readiness.message ?? (readiness.ready ? "shopify_authorized_order ready" : "not ready"),
|
|
196
|
+
details: { rail: readiness.rail, ready: readiness.ready },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Readiness checks for Paybond + Shopify local development. */
|
|
200
|
+
export async function runShopifyDoctorChecks(ctx) {
|
|
201
|
+
const checks = [];
|
|
202
|
+
const shopifyPath = await whichExecutable("shopify");
|
|
203
|
+
if (shopifyPath) {
|
|
204
|
+
const version = await runCommand(shopifyPath, ["version"]);
|
|
205
|
+
const message = version.stdout.trim() || version.stderr.trim() || "shopify CLI found";
|
|
206
|
+
checks.push({
|
|
207
|
+
name: "shopify_cli",
|
|
208
|
+
ok: version.code === 0,
|
|
209
|
+
message: version.code === 0 ? message.split("\n")[0] ?? message : message,
|
|
210
|
+
details: { path: shopifyPath },
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
checks.push({
|
|
215
|
+
name: "shopify_cli",
|
|
216
|
+
ok: false,
|
|
217
|
+
message: "not on PATH — install: npm install -g @shopify/cli@latest",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const ucpPath = await whichExecutable("ucp");
|
|
221
|
+
if (ucpPath) {
|
|
222
|
+
const version = await runCommand(ucpPath, ["--version"]);
|
|
223
|
+
const message = version.stdout.trim() || version.stderr.trim() || "ucp CLI found";
|
|
224
|
+
checks.push({
|
|
225
|
+
name: "ucp_cli",
|
|
226
|
+
ok: version.code === 0,
|
|
227
|
+
message: version.code === 0 ? message.split("\n")[0] ?? message : message,
|
|
228
|
+
details: { path: ucpPath },
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
checks.push({
|
|
233
|
+
name: "ucp_cli",
|
|
234
|
+
ok: false,
|
|
235
|
+
message: "not on PATH (optional) — install: npm install -g @shopify/ucp-cli",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const appToml = await readShopifyAppToml(ctx.cwd);
|
|
239
|
+
checks.push({
|
|
240
|
+
name: "shopify_app_toml",
|
|
241
|
+
ok: appToml.exists,
|
|
242
|
+
message: appToml.exists
|
|
243
|
+
? `found ${appToml.path ?? "shopify.app.toml"}`
|
|
244
|
+
: "shopify.app.toml not found in cwd — run shopify app config link",
|
|
245
|
+
details: appToml.clientId ? { client_id: appToml.clientId } : undefined,
|
|
246
|
+
});
|
|
247
|
+
const shopDomain = resolveShopDomain(undefined, ctx.cwd);
|
|
248
|
+
checks.push({
|
|
249
|
+
name: "dev_store",
|
|
250
|
+
ok: Boolean(shopDomain),
|
|
251
|
+
message: shopDomain
|
|
252
|
+
? `SHOPIFY_DEV_STORE=${shopDomain}`
|
|
253
|
+
: "set SHOPIFY_DEV_STORE in .env.local or pass --shop",
|
|
254
|
+
});
|
|
255
|
+
const settlement = await fetchSettlementConfig(ctx);
|
|
256
|
+
checks.push({
|
|
257
|
+
name: "paybond_shop_linked",
|
|
258
|
+
ok: settlement?.shopify_linked_shop_configured === true,
|
|
259
|
+
message: settlement?.shopify_linked_shop_configured
|
|
260
|
+
? `linked shop ${settlement.shopify_shop_domain_masked ?? "(masked)"}`
|
|
261
|
+
: "link a shop in Console → Configuration → Settlement",
|
|
262
|
+
});
|
|
263
|
+
checks.push({
|
|
264
|
+
name: "manual_capture",
|
|
265
|
+
ok: settlement ? settlement.shopify_manual_capture_required !== true : false,
|
|
266
|
+
message: settlement?.shopify_manual_capture_required
|
|
267
|
+
? "enable manual payment capture in Shopify Admin (required for shopify_authorized_order)"
|
|
268
|
+
: settlement
|
|
269
|
+
? "manual capture prerequisite satisfied or not required"
|
|
270
|
+
: "skipped (missing settlement config)",
|
|
271
|
+
});
|
|
272
|
+
checks.push(railReadinessForShopify(settlement));
|
|
273
|
+
return checks;
|
|
274
|
+
}
|
|
275
|
+
export async function handleShopifyDoctor(ctx, argv) {
|
|
276
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
277
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
278
|
+
}
|
|
279
|
+
if (argv.length > 0) {
|
|
280
|
+
throw shopifyCliError(`unexpected arguments: ${argv.join(" ")}`, {
|
|
281
|
+
code: "cli.usage.unexpected_args",
|
|
282
|
+
category: "usage",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const checks = await runShopifyDoctorChecks(ctx);
|
|
286
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
287
|
+
return {
|
|
288
|
+
data: {
|
|
289
|
+
checks,
|
|
290
|
+
summary: checks.every((check) => check.ok) ? "pass" : "fail",
|
|
291
|
+
checklist_lines: formatShopifyDoctorChecklist(checks, useColor),
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
export async function handleShopifyLink(ctx, argv) {
|
|
296
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
297
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
298
|
+
}
|
|
299
|
+
if (argv.length > 0) {
|
|
300
|
+
throw shopifyCliError(`unexpected arguments: ${argv.join(" ")}`, {
|
|
301
|
+
code: "cli.usage.unexpected_args",
|
|
302
|
+
category: "usage",
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const appToml = await readShopifyAppToml(ctx.cwd);
|
|
306
|
+
const clientId = appToml.clientId && !appToml.clientId.startsWith("env:")
|
|
307
|
+
? appToml.clientId
|
|
308
|
+
: process.env.SHOPIFY_FLAG_CLIENT_ID?.trim();
|
|
309
|
+
const configLink = clientId
|
|
310
|
+
? `shopify app config link --client-id=${clientId}`
|
|
311
|
+
: "shopify app config link";
|
|
312
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
313
|
+
const lines = [
|
|
314
|
+
colorize("Shopify + Paybond link workflow", "cyan", useColor),
|
|
315
|
+
"",
|
|
316
|
+
"1. paybond login",
|
|
317
|
+
"2. Console → Configuration → Settlement → Link Shopify store",
|
|
318
|
+
" https://paybond.ai/console/configuration/settlement",
|
|
319
|
+
`3. ${configLink}`,
|
|
320
|
+
"",
|
|
321
|
+
"Agents must not hold Shopify offline access tokens — linking is tenant-admin only.",
|
|
322
|
+
];
|
|
323
|
+
return {
|
|
324
|
+
data: {
|
|
325
|
+
steps: [
|
|
326
|
+
{ step: 1, action: "paybond login" },
|
|
327
|
+
{ step: 2, action: "link_shop_in_console", url: "https://paybond.ai/console/configuration/settlement" },
|
|
328
|
+
{ step: 3, action: configLink },
|
|
329
|
+
],
|
|
330
|
+
config_link_command: configLink,
|
|
331
|
+
checklist_lines: lines,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
export async function handleShopifyDev(ctx, argv) {
|
|
336
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
337
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
338
|
+
}
|
|
339
|
+
let rest = argv;
|
|
340
|
+
const tunnelFlag = consumeFlag(rest, "--tunnel");
|
|
341
|
+
rest = tunnelFlag.rest;
|
|
342
|
+
if (rest.length > 0) {
|
|
343
|
+
throw shopifyCliError(`unexpected arguments: ${rest.join(" ")}`, {
|
|
344
|
+
code: "cli.usage.unexpected_args",
|
|
345
|
+
category: "usage",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
const traceUrl = devTraceUrl();
|
|
349
|
+
const webhookAddress = resolveShopifyWebhookAddress(ctx.globals.gateway, tunnelFlag.value);
|
|
350
|
+
const appToml = await readShopifyAppToml(ctx.cwd);
|
|
351
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
352
|
+
const lines = [
|
|
353
|
+
colorize("Paybond + Shopify local dev (two terminals)", "cyan", useColor),
|
|
354
|
+
"",
|
|
355
|
+
"Terminal 1 — Paybond trace:",
|
|
356
|
+
` paybond dev trace # ${traceUrl}`,
|
|
357
|
+
"",
|
|
358
|
+
"Terminal 2 — Shopify app dev:",
|
|
359
|
+
appToml.exists ? " shopify app dev" : " shopify app config link && shopify app dev",
|
|
360
|
+
"",
|
|
361
|
+
`Webhook address (sandbox): ${webhookAddress}`,
|
|
362
|
+
"",
|
|
363
|
+
"Terminal 3 — smoke:",
|
|
364
|
+
" paybond shopify checkout smoke --shop $SHOPIFY_DEV_STORE",
|
|
365
|
+
];
|
|
366
|
+
return {
|
|
367
|
+
data: {
|
|
368
|
+
trace_url: traceUrl,
|
|
369
|
+
shopify_command: "shopify app dev",
|
|
370
|
+
webhook_address: webhookAddress,
|
|
371
|
+
checklist_lines: lines,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
export async function handleShopifyWebhookTrigger(ctx, argv) {
|
|
376
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
377
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
378
|
+
}
|
|
379
|
+
let rest = argv;
|
|
380
|
+
const topicFlag = consumeFlag(rest, "--topic");
|
|
381
|
+
rest = topicFlag.rest;
|
|
382
|
+
const gatewayFlag = consumeFlag(rest, "--gateway");
|
|
383
|
+
rest = gatewayFlag.rest;
|
|
384
|
+
const addressFlag = consumeFlag(rest, "--address");
|
|
385
|
+
rest = addressFlag.rest;
|
|
386
|
+
const dryRunFlag = consumeBooleanFlag(rest, "--dry-run");
|
|
387
|
+
rest = dryRunFlag.rest;
|
|
388
|
+
if (rest.length > 0) {
|
|
389
|
+
throw shopifyCliError(`unexpected arguments: ${rest.join(" ")}`, {
|
|
390
|
+
code: "cli.usage.unexpected_args",
|
|
391
|
+
category: "usage",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const topic = topicFlag.value?.trim() || "orders/paid";
|
|
395
|
+
const address = addressFlag.value?.trim() ||
|
|
396
|
+
resolveShopifyWebhookAddress(gatewayFlag.value ?? ctx.globals.gateway, undefined);
|
|
397
|
+
const appToml = await readShopifyAppToml(ctx.cwd);
|
|
398
|
+
const clientId = appToml.clientId && !appToml.clientId.startsWith("env:")
|
|
399
|
+
? appToml.clientId
|
|
400
|
+
: process.env.SHOPIFY_FLAG_CLIENT_ID?.trim();
|
|
401
|
+
const shopifyPath = await whichExecutable("shopify");
|
|
402
|
+
if (!shopifyPath) {
|
|
403
|
+
throw shopifyCliError("shopify CLI not on PATH — install: npm install -g @shopify/cli@latest", {
|
|
404
|
+
code: "cli.shopify.missing_cli",
|
|
405
|
+
category: "environment",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const triggerArgs = buildShopifyWebhookTriggerCommand({ topic, address, clientId });
|
|
409
|
+
const commandLine = `shopify ${triggerArgs.join(" ")}`;
|
|
410
|
+
if (dryRunFlag.present) {
|
|
411
|
+
return {
|
|
412
|
+
data: {
|
|
413
|
+
topic,
|
|
414
|
+
address,
|
|
415
|
+
command: commandLine,
|
|
416
|
+
dry_run: true,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const result = await runCommand(shopifyPath, triggerArgs);
|
|
421
|
+
if (result.code !== 0) {
|
|
422
|
+
throw shopifyCliError(result.stderr.trim() || result.stdout.trim() || "shopify webhook trigger failed", {
|
|
423
|
+
code: "cli.shopify.webhook_trigger_failed",
|
|
424
|
+
details: { command: commandLine, exit_code: result.code },
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
data: {
|
|
429
|
+
topic,
|
|
430
|
+
address,
|
|
431
|
+
command: commandLine,
|
|
432
|
+
stdout: result.stdout.trim(),
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
export async function handleShopifyCheckoutSmoke(ctx, argv) {
|
|
437
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
438
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
439
|
+
}
|
|
440
|
+
let rest = argv;
|
|
441
|
+
const shopFlag = consumeFlag(rest, "--shop");
|
|
442
|
+
rest = shopFlag.rest;
|
|
443
|
+
const spendFlag = consumeFlag(rest, "--requested-spend-cents");
|
|
444
|
+
rest = spendFlag.rest;
|
|
445
|
+
const offlineFlag = consumeBooleanFlag(rest, "--offline");
|
|
446
|
+
rest = offlineFlag.rest;
|
|
447
|
+
if (rest.length > 0) {
|
|
448
|
+
throw shopifyCliError(`unexpected arguments: ${rest.join(" ")}`, {
|
|
449
|
+
code: "cli.usage.unexpected_args",
|
|
450
|
+
category: "usage",
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
const defaults = getSolutionSmokeDefaults("shopping");
|
|
454
|
+
const shop = resolveShopDomain(shopFlag.value, ctx.cwd) ?? "paybond-agent-commerce-dev.myshopify.com";
|
|
455
|
+
const spendCents = spendFlag.value ? Number.parseInt(spendFlag.value, 10) : defaults.requestedSpendCents;
|
|
456
|
+
if (!Number.isFinite(spendCents) || spendCents <= 0) {
|
|
457
|
+
throw shopifyCliError("invalid --requested-spend-cents", {
|
|
458
|
+
code: "cli.usage.invalid_spend",
|
|
459
|
+
category: "usage",
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const resultBody = {
|
|
463
|
+
...defaults.resultBody,
|
|
464
|
+
order_id: "gid://shopify/Order/123",
|
|
465
|
+
shop,
|
|
466
|
+
};
|
|
467
|
+
const smokeArgv = [
|
|
468
|
+
"--preset",
|
|
469
|
+
"shopping",
|
|
470
|
+
"--requested-spend-cents",
|
|
471
|
+
String(spendCents),
|
|
472
|
+
"--result-body",
|
|
473
|
+
JSON.stringify(resultBody),
|
|
474
|
+
];
|
|
475
|
+
if (offlineFlag.present) {
|
|
476
|
+
await assertOfflineDevCredentialsSafe(ctx);
|
|
477
|
+
}
|
|
478
|
+
const offlineSession = offlineFlag.present ? beginOfflineDevSession(ctx) : null;
|
|
479
|
+
const smokeCtx = offlineSession?.ctx ?? ctx;
|
|
480
|
+
const smokeResult = await handleAgentSandboxSmoke(smokeCtx, smokeArgv);
|
|
481
|
+
offlineSession?.restore();
|
|
482
|
+
const ucpPath = await whichExecutable("ucp");
|
|
483
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
484
|
+
const lines = [
|
|
485
|
+
colorize("shopify checkout smoke", "cyan", useColor),
|
|
486
|
+
ucpPath
|
|
487
|
+
? colorize("ucp CLI detected — use createCheckoutWithBinding for live UCP checkout", "dim", useColor)
|
|
488
|
+
: colorize("ucp CLI not on PATH — used paybond agent sandbox smoke fallback", "dim", useColor),
|
|
489
|
+
`shop: ${shop}`,
|
|
490
|
+
`binding note_attributes: paybond_intent_id + tenant_id (injected by Kit on live checkout)`,
|
|
491
|
+
];
|
|
492
|
+
const checklist = Array.isArray(smokeResult.data.checklist_lines)
|
|
493
|
+
? smokeResult.data.checklist_lines
|
|
494
|
+
: [];
|
|
495
|
+
return {
|
|
496
|
+
data: {
|
|
497
|
+
...smokeResult.data,
|
|
498
|
+
shop,
|
|
499
|
+
ucp_available: Boolean(ucpPath),
|
|
500
|
+
checklist_lines: [...lines, ...checklist],
|
|
501
|
+
},
|
|
502
|
+
warnings: smokeResult.warnings,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
export async function handleShopifyOrderShow(ctx, argv) {
|
|
506
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
507
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
508
|
+
}
|
|
509
|
+
const orderId = argv[0]?.trim();
|
|
510
|
+
let rest = argv.slice(1);
|
|
511
|
+
const shopFlag = consumeFlag(rest, "--shop");
|
|
512
|
+
rest = shopFlag.rest;
|
|
513
|
+
if (!orderId || rest.length > 0) {
|
|
514
|
+
throw shopifyCliError("usage: paybond shopify order show <id> [--shop <domain>]", {
|
|
515
|
+
code: "cli.usage.invalid_args",
|
|
516
|
+
category: "usage",
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
const shop = resolveShopDomain(shopFlag.value, ctx.cwd);
|
|
520
|
+
if (!shop) {
|
|
521
|
+
throw shopifyCliError("missing --shop or SHOPIFY_DEV_STORE", {
|
|
522
|
+
code: "cli.shopify.missing_shop",
|
|
523
|
+
category: "usage",
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const order = await getOrder({ shopDomain: shop, orderId, fetchUcp: ctx.fetch ?? fetch });
|
|
527
|
+
return {
|
|
528
|
+
data: {
|
|
529
|
+
order,
|
|
530
|
+
binding: order.binding,
|
|
531
|
+
financial_status: order.financial_status,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
export async function handleShopifyCaptureReady(ctx, argv) {
|
|
536
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
537
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
538
|
+
}
|
|
539
|
+
if (argv.length > 0) {
|
|
540
|
+
throw shopifyCliError(`unexpected arguments: ${argv.join(" ")}`, {
|
|
541
|
+
code: "cli.usage.unexpected_args",
|
|
542
|
+
category: "usage",
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
const settlement = await fetchSettlementConfig(ctx);
|
|
546
|
+
if (!settlement) {
|
|
547
|
+
throw shopifyCliError("settlement config unavailable — run paybond login", {
|
|
548
|
+
code: "cli.shopify.missing_settlement",
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const readiness = railReadinessForShopify(settlement);
|
|
552
|
+
const checks = [
|
|
553
|
+
{
|
|
554
|
+
name: "shop_linked",
|
|
555
|
+
ok: settlement.shopify_linked_shop_configured === true,
|
|
556
|
+
message: settlement.shopify_linked_shop_configured
|
|
557
|
+
? `linked ${settlement.shopify_shop_domain_masked ?? "shop"}`
|
|
558
|
+
: "link a Shopify shop in Console",
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "manual_capture",
|
|
562
|
+
ok: settlement.shopify_manual_capture_required !== true,
|
|
563
|
+
message: settlement.shopify_manual_capture_required
|
|
564
|
+
? "enable manual payment capture in Shopify Admin"
|
|
565
|
+
: "manual capture prerequisite satisfied",
|
|
566
|
+
},
|
|
567
|
+
readiness,
|
|
568
|
+
];
|
|
569
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
570
|
+
const ready = checks.every((check) => check.ok);
|
|
571
|
+
return {
|
|
572
|
+
data: {
|
|
573
|
+
ready,
|
|
574
|
+
checks,
|
|
575
|
+
checklist_lines: formatShopifyDoctorChecklist(checks, useColor),
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
export async function runShopifyPaymentsDoctorChecks(ctx) {
|
|
580
|
+
const checks = [];
|
|
581
|
+
const shopifyPath = await whichExecutable("shopify");
|
|
582
|
+
checks.push({
|
|
583
|
+
name: "shopify_cli",
|
|
584
|
+
ok: Boolean(shopifyPath),
|
|
585
|
+
message: shopifyPath
|
|
586
|
+
? `found at ${shopifyPath}`
|
|
587
|
+
: "not on PATH — install: npm install -g @shopify/cli@latest",
|
|
588
|
+
details: shopifyPath ? { path: shopifyPath } : undefined,
|
|
589
|
+
});
|
|
590
|
+
const appToml = await readShopifyPaymentsAppToml(ctx.cwd);
|
|
591
|
+
checks.push({
|
|
592
|
+
name: "payments_app_toml",
|
|
593
|
+
ok: appToml.exists,
|
|
594
|
+
message: appToml.exists
|
|
595
|
+
? `found ${appToml.path ?? "shopify.app.toml"}`
|
|
596
|
+
: "shopify.app.toml not found — run: cd apps/shopify-payments && shopify app config link",
|
|
597
|
+
details: appToml.clientId ? { client_id: appToml.clientId } : undefined,
|
|
598
|
+
});
|
|
599
|
+
const shopDomain = resolveShopDomain(undefined, ctx.cwd);
|
|
600
|
+
checks.push({
|
|
601
|
+
name: "dev_store",
|
|
602
|
+
ok: Boolean(shopDomain),
|
|
603
|
+
message: shopDomain
|
|
604
|
+
? `SHOPIFY_DEV_STORE=${shopDomain}`
|
|
605
|
+
: "set SHOPIFY_DEV_STORE in .env.local or pass --shop",
|
|
606
|
+
});
|
|
607
|
+
const settlement = await fetchSettlementConfig(ctx);
|
|
608
|
+
checks.push({
|
|
609
|
+
name: "payments_app_linked",
|
|
610
|
+
ok: settlement?.shopify_payments_linked === true,
|
|
611
|
+
message: settlement?.shopify_payments_linked
|
|
612
|
+
? `linked shop ${settlement.shopify_payments_shop_domain_masked ?? "(masked)"}`
|
|
613
|
+
: "link the Paybond Payments app in Console → Configuration → Settlement",
|
|
614
|
+
});
|
|
615
|
+
checks.push({
|
|
616
|
+
name: "payments_app_ready",
|
|
617
|
+
ok: settlement?.shopify_payments_app_ready === true,
|
|
618
|
+
message: settlement?.shopify_payments_app_ready
|
|
619
|
+
? "paymentsAppConfigure marked ready"
|
|
620
|
+
: settlement
|
|
621
|
+
? "mark the Paybond Payments app ready in Console after install"
|
|
622
|
+
: "skipped (missing settlement config)",
|
|
623
|
+
});
|
|
624
|
+
checks.push({
|
|
625
|
+
name: "payments_mtls",
|
|
626
|
+
ok: settlement?.shopify_payments_mtls_configured === true,
|
|
627
|
+
message: settlement?.shopify_payments_mtls_configured
|
|
628
|
+
? "Shopify Payments mTLS ingress configured"
|
|
629
|
+
: settlement
|
|
630
|
+
? "mTLS ingress not configured (required for production; optional on local dev tunnels)"
|
|
631
|
+
: "skipped (missing settlement config)",
|
|
632
|
+
});
|
|
633
|
+
checks.push(railReadinessForShopifyPayments(settlement));
|
|
634
|
+
return checks;
|
|
635
|
+
}
|
|
636
|
+
export async function handleShopifyPaymentsDoctor(ctx, argv) {
|
|
637
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
638
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
639
|
+
}
|
|
640
|
+
if (argv.length > 0) {
|
|
641
|
+
throw shopifyCliError(`unexpected arguments: ${argv.join(" ")}`, {
|
|
642
|
+
code: "cli.usage.unexpected_args",
|
|
643
|
+
category: "usage",
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const checks = await runShopifyPaymentsDoctorChecks(ctx);
|
|
647
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
648
|
+
const lines = checks.map((check) => formatDoctorChecklistLine(check, useColor));
|
|
649
|
+
const summary = checks.every((check) => check.ok) ? "pass" : "fail";
|
|
650
|
+
lines.push(colorize(`shopify payments doctor: ${summary}`, summary === "pass" ? "green" : "yellow", useColor));
|
|
651
|
+
return {
|
|
652
|
+
data: {
|
|
653
|
+
checks,
|
|
654
|
+
summary,
|
|
655
|
+
checklist_lines: lines,
|
|
656
|
+
next_steps: buildShopifyPaymentsNextStepsBanner(resolveShopDomain(undefined, ctx.cwd)),
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
export async function handleShopifyPaymentsSmoke(ctx, argv) {
|
|
661
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
662
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
663
|
+
}
|
|
664
|
+
let rest = argv;
|
|
665
|
+
const shopFlag = consumeFlag(rest, "--shop");
|
|
666
|
+
rest = shopFlag.rest;
|
|
667
|
+
const spendFlag = consumeFlag(rest, "--requested-spend-cents");
|
|
668
|
+
rest = spendFlag.rest;
|
|
669
|
+
const offlineFlag = consumeBooleanFlag(rest, "--offline");
|
|
670
|
+
rest = offlineFlag.rest;
|
|
671
|
+
if (rest.length > 0) {
|
|
672
|
+
throw shopifyCliError(`unexpected arguments: ${rest.join(" ")}`, {
|
|
673
|
+
code: "cli.usage.unexpected_args",
|
|
674
|
+
category: "usage",
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
const defaults = getSolutionSmokeDefaults("shopping");
|
|
678
|
+
const shop = resolveShopDomain(shopFlag.value, ctx.cwd) ?? "paybond-agent-commerce-dev.myshopify.com";
|
|
679
|
+
const spendCents = spendFlag.value ? Number.parseInt(spendFlag.value, 10) : defaults.requestedSpendCents;
|
|
680
|
+
if (!Number.isFinite(spendCents) || spendCents <= 0) {
|
|
681
|
+
throw shopifyCliError("invalid --requested-spend-cents", {
|
|
682
|
+
code: "cli.usage.invalid_spend",
|
|
683
|
+
category: "usage",
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
const paymentSessionId = "paybond-smoke-payment-session";
|
|
687
|
+
const resultBody = {
|
|
688
|
+
...defaults.resultBody,
|
|
689
|
+
shop,
|
|
690
|
+
payment_session_id: paymentSessionId,
|
|
691
|
+
payment_session_gid: `gid://shopify/PaymentSession/${paymentSessionId}`,
|
|
692
|
+
test_mode: true,
|
|
693
|
+
settlement_rail: "shopify_payments_app",
|
|
694
|
+
};
|
|
695
|
+
const smokeArgv = [
|
|
696
|
+
"--preset",
|
|
697
|
+
"shopping",
|
|
698
|
+
"--requested-spend-cents",
|
|
699
|
+
String(spendCents),
|
|
700
|
+
"--result-body",
|
|
701
|
+
JSON.stringify(resultBody),
|
|
702
|
+
];
|
|
703
|
+
if (offlineFlag.present) {
|
|
704
|
+
await assertOfflineDevCredentialsSafe(ctx);
|
|
705
|
+
}
|
|
706
|
+
const offlineSession = offlineFlag.present ? beginOfflineDevSession(ctx) : null;
|
|
707
|
+
const smokeCtx = offlineSession?.ctx ?? ctx;
|
|
708
|
+
const smokeResult = await handleAgentSandboxSmoke(smokeCtx, smokeArgv);
|
|
709
|
+
offlineSession?.restore();
|
|
710
|
+
const useColor = shouldUseColor(ctx.globals);
|
|
711
|
+
const lines = [
|
|
712
|
+
colorize("shopify payments smoke", "cyan", useColor),
|
|
713
|
+
"binding transaction_metadata: tenant_id + paybond_intent_id",
|
|
714
|
+
`shop: ${shop}`,
|
|
715
|
+
"live dev store: cd apps/shopify-payments && shopify app dev",
|
|
716
|
+
"then complete a test-mode checkout and run: paybond shopify payments session show <payment_session_id>",
|
|
717
|
+
];
|
|
718
|
+
const checklist = Array.isArray(smokeResult.data.checklist_lines)
|
|
719
|
+
? smokeResult.data.checklist_lines
|
|
720
|
+
: [];
|
|
721
|
+
return {
|
|
722
|
+
data: {
|
|
723
|
+
...smokeResult.data,
|
|
724
|
+
shop,
|
|
725
|
+
payment_session_id: paymentSessionId,
|
|
726
|
+
checklist_lines: [...lines, ...checklist, ...buildShopifyPaymentsNextStepsBanner(shop)],
|
|
727
|
+
},
|
|
728
|
+
warnings: smokeResult.warnings,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
export async function handleShopifyPaymentsSessionShow(ctx, argv) {
|
|
732
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
733
|
+
throw new CliError("help", { category: "usage", code: "cli.help" });
|
|
734
|
+
}
|
|
735
|
+
const sessionId = argv[0]?.trim();
|
|
736
|
+
let rest = argv.slice(1);
|
|
737
|
+
const shopFlag = consumeFlag(rest, "--shop");
|
|
738
|
+
rest = shopFlag.rest;
|
|
739
|
+
if (!sessionId || rest.length > 0) {
|
|
740
|
+
throw shopifyCliError("usage: paybond shopify payments session show <id> [--shop <domain>]", {
|
|
741
|
+
code: "cli.usage.invalid_args",
|
|
742
|
+
category: "usage",
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
const shop = resolveShopDomain(shopFlag.value, ctx.cwd);
|
|
746
|
+
try {
|
|
747
|
+
await resolveApiKey(ctx.globals, ctx.cwd);
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
throw shopifyCliError("session show requires paybond login", {
|
|
751
|
+
code: "cli.shopify.missing_credentials",
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
const result = await withGateway(ctx, async (gateway) => {
|
|
755
|
+
const body = await gateway.getJson(`/v1/admin/shopify/payments/sessions/${encodeURIComponent(sessionId)}`);
|
|
756
|
+
return { data: body };
|
|
757
|
+
});
|
|
758
|
+
const session = result.data;
|
|
759
|
+
if (shop && typeof session.shop_domain === "string" && session.shop_domain !== shop) {
|
|
760
|
+
throw shopifyCliError("session shop_domain does not match --shop", {
|
|
761
|
+
code: "cli.shopify.session_shop_mismatch",
|
|
762
|
+
details: { shop_domain: session.shop_domain, shop },
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return { data: { session } };
|
|
766
|
+
}
|
|
767
|
+
export async function handleShopify(ctx, subcommand, third, fourth, argv) {
|
|
768
|
+
if (subcommand === "payments") {
|
|
769
|
+
if (third === "doctor") {
|
|
770
|
+
return handleShopifyPaymentsDoctor(ctx, argv);
|
|
771
|
+
}
|
|
772
|
+
if (third === "smoke") {
|
|
773
|
+
return handleShopifyPaymentsSmoke(ctx, argv);
|
|
774
|
+
}
|
|
775
|
+
if (third === "session" && fourth === "show") {
|
|
776
|
+
return handleShopifyPaymentsSessionShow(ctx, argv);
|
|
777
|
+
}
|
|
778
|
+
throw shopifyCliError(`unknown shopify payments subcommand: shopify payments ${third ?? ""}`, {
|
|
779
|
+
code: "cli.usage.unknown_command",
|
|
780
|
+
category: "usage",
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
if (subcommand === "doctor") {
|
|
784
|
+
return handleShopifyDoctor(ctx, argv);
|
|
785
|
+
}
|
|
786
|
+
if (subcommand === "link") {
|
|
787
|
+
return handleShopifyLink(ctx, argv);
|
|
788
|
+
}
|
|
789
|
+
if (subcommand === "dev") {
|
|
790
|
+
return handleShopifyDev(ctx, argv);
|
|
791
|
+
}
|
|
792
|
+
if (subcommand === "webhook" && third === "trigger") {
|
|
793
|
+
return handleShopifyWebhookTrigger(ctx, argv);
|
|
794
|
+
}
|
|
795
|
+
if (subcommand === "checkout" && third === "smoke") {
|
|
796
|
+
return handleShopifyCheckoutSmoke(ctx, argv);
|
|
797
|
+
}
|
|
798
|
+
if (subcommand === "order" && third === "show") {
|
|
799
|
+
return handleShopifyOrderShow(ctx, argv);
|
|
800
|
+
}
|
|
801
|
+
if (subcommand === "capture" && third === "ready") {
|
|
802
|
+
return handleShopifyCaptureReady(ctx, argv);
|
|
803
|
+
}
|
|
804
|
+
throw shopifyCliError(`unknown shopify subcommand: shopify ${subcommand}`, {
|
|
805
|
+
code: "cli.usage.unknown_command",
|
|
806
|
+
category: "usage",
|
|
807
|
+
});
|
|
808
|
+
}
|