frontpage-host 0.1.29 → 0.2.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.
@@ -0,0 +1,315 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolveSlug } from "../site.js";
3
+ import { takeFlagValue as takeFlag, hasFlag, assertNoUnknownFlags } from "../argv.js";
4
+ import { CliHttpError, EXIT_OK, EXIT_USAGE, v1Get, v1Post, v1Patch, LONG_MODULE_TIMEOUT_MS, } from "../http/client.js";
5
+ import { emitErr, emitOk, emitUsage, isJson, requireYesOrTty, } from "../runtime.js";
6
+ import { openBrowser, resolveOpenUrl } from "../modules/oauth.js";
7
+ const USAGE = "frontpage payments status|connect|availability|buttons|activity|tax|invoices|recurring|customers … [--site <slug>] [--json]";
8
+ function usage(message) {
9
+ throw new CliHttpError(message, EXIT_USAGE, 0, "bad_request");
10
+ }
11
+ function idOf(work) {
12
+ const id = takeFlag(work, "--id") ??
13
+ (!work[0]?.startsWith("-") ? work.shift() : undefined);
14
+ if (!id || !/^[a-zA-Z0-9_-]{2,80}$/.test(id))
15
+ usage("A valid record id is required. Use the id returned by list or create.");
16
+ return encodeURIComponent(id);
17
+ }
18
+ async function payload(work) {
19
+ const file = takeFlag(work, "--file");
20
+ let body = {};
21
+ if (file) {
22
+ try {
23
+ const value = JSON.parse(await readFile(file, "utf8"));
24
+ if (!value || typeof value !== "object" || Array.isArray(value))
25
+ usage("--file must contain a JSON object.");
26
+ body = value;
27
+ }
28
+ catch (error) {
29
+ if (error instanceof CliHttpError)
30
+ throw error;
31
+ usage("Could not read a JSON object from --file.");
32
+ }
33
+ }
34
+ for (const [flag, key] of [
35
+ ["--title", "title"],
36
+ ["--receipt-template-id", "receiptTemplateId"],
37
+ ["--amount", "amount"],
38
+ ["--operation-key", "operationKey"],
39
+ ["--preview-hash", "previewHash"],
40
+ ["--description", "description"],
41
+ ["--kind", "kind"],
42
+ ["--collection", "collection"],
43
+ ["--cadence", "cadence"],
44
+ ["--first-date", "firstDate"],
45
+ ["--time-zone", "timeZone"],
46
+ ["--country", "country"],
47
+ ["--state", "state"],
48
+ ["--line1", "line1"],
49
+ ["--city", "city"],
50
+ ["--postal", "postal"],
51
+ ["--tax-code", "taxCode"],
52
+ ]) {
53
+ const value = takeFlag(work, flag);
54
+ if (value !== undefined) {
55
+ if (!value || value.startsWith("--"))
56
+ usage(`${flag} requires a value.`);
57
+ body[key] = value;
58
+ }
59
+ }
60
+ for (const [flag, key] of [
61
+ ["--price-cents", "priceCents"],
62
+ ["--min-cents", "minCents"],
63
+ ["--amount-cents", "amountCents"],
64
+ ["--due-days", "dueDays"],
65
+ ]) {
66
+ const value = takeFlag(work, flag);
67
+ if (value !== undefined) {
68
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
69
+ usage(`${flag} requires a whole number.`);
70
+ body[key] = Number(value);
71
+ }
72
+ }
73
+ const personId = takeFlag(work, "--person");
74
+ const email = takeFlag(work, "--email"), name = takeFlag(work, "--name");
75
+ if (personId && (email || name))
76
+ usage("Use either --person or an inline --email/--name customer.");
77
+ if (personId)
78
+ body.recipient = { personId };
79
+ else if (email || name)
80
+ body.recipient = {
81
+ ...(body.recipient && typeof body.recipient === "object"
82
+ ? body.recipient
83
+ : {}),
84
+ ...(email ? { email } : {}),
85
+ ...(name ? { name } : {}),
86
+ };
87
+ if (hasFlag(work, "--default-receipt")) {
88
+ if (body.receiptTemplateId !== undefined)
89
+ usage("Use --receipt-template-id or --default-receipt, not both.");
90
+ body.receiptTemplateId = null;
91
+ }
92
+ const active = takeFlag(work, "--active");
93
+ if (active !== undefined) {
94
+ if (active !== "true" && active !== "false")
95
+ usage("--active requires true or false.");
96
+ body.active = active === "true";
97
+ }
98
+ if (hasFlag(work, "--confirm-billing-contact"))
99
+ body.confirmBillingContact = true;
100
+ return body;
101
+ }
102
+ function noRest(work) {
103
+ assertNoUnknownFlags(work, "payments");
104
+ if (work.length)
105
+ usage(`Unexpected argument: ${work[0]}. Run frontpage help payments.`);
106
+ }
107
+ function confirm(body, action) {
108
+ const problem = requireYesOrTty(action);
109
+ if (problem)
110
+ usage(problem);
111
+ body.confirm = true;
112
+ }
113
+ function requireKey(body) {
114
+ if (typeof body.operationKey !== "string" ||
115
+ !/^[a-zA-Z0-9._:-]{16,120}$/.test(body.operationKey))
116
+ usage("Supply --operation-key with 16–120 letters, numbers, dots, colons, hyphens or underscores. Keep the same key when retrying.");
117
+ }
118
+ export async function runPayments(args) {
119
+ try {
120
+ const { slug, rest } = await resolveSlug(args);
121
+ const work = [...rest];
122
+ const group = work.shift();
123
+ if (!group)
124
+ return emitUsage("payments", USAGE);
125
+ const base = `/api/v1/sites/${encodeURIComponent(slug)}/payments`;
126
+ let path = "", method = "GET", body = {};
127
+ if (group === "status" || group === "settings")
128
+ path = group;
129
+ else if (group === "connect") {
130
+ const noOpen = hasFlag(work, "--no-open");
131
+ noRest(work);
132
+ const result = await v1Post(`${base}/connect`, {});
133
+ const url = resolveOpenUrl(result.url);
134
+ if (!url)
135
+ usage("The API did not return a trusted browser setup link.");
136
+ if (!noOpen && !isJson())
137
+ openBrowser(url);
138
+ if (isJson())
139
+ emitOk("payments", result);
140
+ else
141
+ console.log(`${result.message}\n${url}`);
142
+ return EXIT_OK;
143
+ }
144
+ else if (group === "availability") {
145
+ const mode = work.shift() ?? "preview";
146
+ if (!["preview", "on", "off"].includes(mode))
147
+ usage("Use payments availability preview|on|off.");
148
+ path = "availability";
149
+ if (mode !== "preview") {
150
+ body = await payload(work);
151
+ confirm(body, mode === "off"
152
+ ? "Stop new payments, pause recurring billing and void unpaid invoices"
153
+ : "Restore Payments without resuming stopped billing");
154
+ body.enabled = mode === "on";
155
+ if (!body.enabled && !body.previewHash) {
156
+ const preview = await v1Get(`${base}/availability`);
157
+ body.previewHash = preview.previewHash;
158
+ }
159
+ method = "POST";
160
+ }
161
+ }
162
+ else if (group === "buttons") {
163
+ const sub = work.shift() ?? "list";
164
+ if (sub === "list") {
165
+ const query = new URLSearchParams();
166
+ for (const key of ["limit", "offset", "archived"]) {
167
+ const value = takeFlag(work, `--${key}`);
168
+ if (value !== undefined)
169
+ query.set(key, value);
170
+ }
171
+ path = `buttons?${query}`;
172
+ }
173
+ else if (sub === "get")
174
+ path = `buttons/${idOf(work)}`;
175
+ else if (sub === "create") {
176
+ const id = takeFlag(work, "--id");
177
+ path = "buttons";
178
+ method = "POST";
179
+ body = await payload(work);
180
+ if (id)
181
+ body.itemId = id;
182
+ }
183
+ else if (sub === "update") {
184
+ path = `buttons/${idOf(work)}`;
185
+ method = "PATCH";
186
+ body = await payload(work);
187
+ }
188
+ else if (sub === "archive" || sub === "restore") {
189
+ path = `buttons/${idOf(work)}/${sub}`;
190
+ method = "POST";
191
+ body = await payload(work);
192
+ confirm(body, sub === "restore" ? "Restore this offering" : "Delete this offering from new payments");
193
+ }
194
+ else
195
+ usage("Use payments buttons list|get|create|update|archive|restore.");
196
+ }
197
+ else if (group === "activity") {
198
+ const sub = work.shift() ?? "list";
199
+ if (sub === "list")
200
+ path = "activity";
201
+ else if (sub === "get") {
202
+ const source = takeFlag(work, "--source");
203
+ if (!["order", "payment", "invoice", "recurring"].includes(source ?? ""))
204
+ usage("Use --source order|payment|invoice|recurring.");
205
+ const id = idOf(work);
206
+ path = `activity/${source}/${id}`;
207
+ }
208
+ else
209
+ usage("Use payments activity list|get.");
210
+ }
211
+ else if (group === "tax") {
212
+ const sub = work.shift() ?? "get";
213
+ if (sub === "get")
214
+ path = "tax";
215
+ else if (sub === "settings") {
216
+ path = "tax/settings";
217
+ method = "POST";
218
+ body = await payload(work);
219
+ confirm(body, "Save the supplied business tax settings");
220
+ }
221
+ else if (sub === "registrations") {
222
+ const verb = work.shift() ?? "list";
223
+ if (verb === "list")
224
+ path = "tax/registrations";
225
+ else if (verb === "add") {
226
+ path = "tax/registrations";
227
+ method = "POST";
228
+ body = await payload(work);
229
+ confirm(body, "Add the supplied tax registration");
230
+ }
231
+ else if (verb === "end") {
232
+ path = `tax/registrations/${idOf(work)}/end`;
233
+ method = "POST";
234
+ body = await payload(work);
235
+ confirm(body, "End this tax registration");
236
+ }
237
+ else
238
+ usage("Use payments tax registrations list|add|end.");
239
+ }
240
+ else
241
+ usage("Use payments tax get|settings|registrations.");
242
+ }
243
+ else if (group === "invoices" || group === "recurring") {
244
+ const sub = work.shift() ?? "list";
245
+ if (sub === "list") {
246
+ const query = new URLSearchParams();
247
+ for (const key of ["limit", "offset", "arrangementId"]) {
248
+ const value = takeFlag(work, `--${key === "arrangementId" ? "arrangement" : key}`);
249
+ if (value !== undefined)
250
+ query.set(key, value);
251
+ }
252
+ path = `${group}?${query}`;
253
+ }
254
+ else if (sub === "get")
255
+ path = `${group}/${idOf(work)}`;
256
+ else if (sub === "preview" ||
257
+ (group === "invoices" && sub === "send") ||
258
+ (group === "recurring" && sub === "create")) {
259
+ path = sub === "create" ? "recurring" : `${group}/${sub}`;
260
+ method = "POST";
261
+ body = await payload(work);
262
+ if (group === "recurring")
263
+ body.kind = "recurring";
264
+ if (sub !== "preview") {
265
+ requireKey(body);
266
+ confirm(body, sub === "send"
267
+ ? "Send this reviewed invoice"
268
+ : "Create this reviewed recurring billing request");
269
+ }
270
+ }
271
+ else if ((group === "invoices" && ["refresh", "resend", "void"].includes(sub)) ||
272
+ (group === "recurring" &&
273
+ ["refresh", "cancel", "resume", "resend-invitation"].includes(sub))) {
274
+ path = `${group}/${idOf(work)}/${sub}`;
275
+ method = "POST";
276
+ body = await payload(work);
277
+ if (["resend", "resume", "resend-invitation"].includes(sub))
278
+ requireKey(body);
279
+ if (sub !== "refresh")
280
+ confirm(body, `${sub} this ${group === "invoices" ? "invoice" : "billing arrangement"}`);
281
+ }
282
+ else
283
+ usage(`Unknown ${group} command. Run frontpage help payments.`);
284
+ }
285
+ else if (group === "customers") {
286
+ const sub = work.shift();
287
+ if (sub !== "billing-link" && sub !== "send-billing-link")
288
+ usage("Use payments customers billing-link|send-billing-link.");
289
+ path = `customers/${idOf(work)}/${sub}`;
290
+ method = "POST";
291
+ body = await payload(work);
292
+ requireKey(body);
293
+ if (sub === "send-billing-link")
294
+ confirm(body, "Email a private billing link to the customer's saved billing email");
295
+ }
296
+ else
297
+ return emitUsage("payments", USAGE);
298
+ noRest(work);
299
+ const result = method === "GET"
300
+ ? await v1Get(`${base}/${path}`)
301
+ : method === "PATCH"
302
+ ? await v1Patch(`${base}/${path}`, body)
303
+ : await v1Post(`${base}/${path}`, body, {
304
+ timeoutMs: LONG_MODULE_TIMEOUT_MS,
305
+ });
306
+ if (isJson())
307
+ emitOk("payments", result);
308
+ else
309
+ console.log(JSON.stringify(result, null, 2));
310
+ return EXIT_OK;
311
+ }
312
+ catch (error) {
313
+ return emitErr("payments", error);
314
+ }
315
+ }
@@ -3,10 +3,8 @@ import { resolveSlug } from "../site.js";
3
3
  import { CliHttpError, EXIT_OK, EXIT_USAGE, v1Get, v1Patch, v1Post, v1Put, } from "../http/client.js";
4
4
  import { assertNoUnknownFlags, hasFlag, takeFlag } from "../argv.js";
5
5
  import { emitErr, emitOk, emitUsage, isJson, requireYesOrTty } from "../runtime.js";
6
- import { openBrowser, resolveOpenUrl } from "../modules/oauth.js";
7
6
  import { SHOP_ID_RE, VARIANT_ID_RE } from "./shop-ids.js";
8
- const USAGE = "Usage: frontpage store items|variants|categories|settings|orders|payments|connect|status|disconnect|import … [--site <slug>] [--json]";
9
- const CONNECT_FINISH = "Open the Frontpage URL while logged in, finish Stripe in the browser, then re-run status. This CLI cannot complete OAuth.";
7
+ const USAGE = "Usage: frontpage store items|variants|categories|settings|orders|import … [--site <slug>] [--json]";
10
8
  const CENTS_HINT = "$49.99 is 4999";
11
9
  function takeAll(work, name) {
12
10
  const out = [];
@@ -142,7 +140,7 @@ async function readJsonFile(path) {
142
140
  function printItem(row) {
143
141
  const live = (row.variants ?? []).filter((v) => !v.archivedAt);
144
142
  const prices = live.map((v) => v.discountPriceCents != null ? `${v.discountPriceCents}/${v.priceCents}` : String(v.priceCents));
145
- const stocks = live.map((v) => (v.stock == null ? "unlimited" : String(v.stock)));
143
+ const stocks = live.map((v) => v.stock == null ? "unlimited" : String(v.stock));
146
144
  const bits = [
147
145
  row.checkout ?? "",
148
146
  row.amount ?? "",
@@ -218,7 +216,6 @@ export async function runShop(args) {
218
216
  work.splice(work.indexOf(itemId), 1);
219
217
  const title = takeFlag(work, "--title");
220
218
  const description = takeFlag(work, "--description");
221
- const checkout = takeFlag(work, "--checkout");
222
219
  const amount = takeFlag(work, "--amount");
223
220
  const minCents = parseCentsFlag(takeFlag(work, "--min-cents"), "minCents");
224
221
  const requiresShipping = parseBoolFlag(takeFlag(work, "--requires-shipping"), "requiresShipping");
@@ -238,8 +235,6 @@ export async function runShop(args) {
238
235
  body.title = title;
239
236
  if (description !== undefined)
240
237
  body.description = description;
241
- if (checkout)
242
- body.checkout = checkout;
243
238
  if (amount)
244
239
  body.amount = amount;
245
240
  if (minCents !== undefined)
@@ -254,13 +249,14 @@ export async function runShop(args) {
254
249
  body.images = images;
255
250
  if (body.amount == null)
256
251
  body.amount = "fixed";
257
- if (!Array.isArray(body.variants) && priceCents !== undefined) {
252
+ const initialPrice = priceCents ?? (body.amount === "open" && typeof body.minCents === "number" ? body.minCents : undefined);
253
+ if (!Array.isArray(body.variants) && initialPrice !== undefined) {
258
254
  const stock = stockRaw === undefined ? undefined : stockRaw === "unlimited" ? null : parseCountFlag(stockRaw, "stock");
259
255
  body.variants = [
260
256
  {
261
257
  variantId: variantId ?? "default",
262
258
  ...(options ? { options } : {}),
263
- priceCents,
259
+ priceCents: initialPrice,
264
260
  ...(discountCents !== undefined ? { discountPriceCents: discountCents } : {}),
265
261
  ...(stock !== undefined ? { stock } : {}),
266
262
  },
@@ -281,7 +277,6 @@ export async function runShop(args) {
281
277
  const itemId = requireShopId(work);
282
278
  const title = takeFlag(work, "--title");
283
279
  const description = takeFlag(work, "--description");
284
- const checkout = takeFlag(work, "--checkout");
285
280
  const amount = takeFlag(work, "--amount");
286
281
  const minCents = parseCentsFlag(takeFlag(work, "--min-cents"), "minCents");
287
282
  const requiresShipping = parseBoolFlag(takeFlag(work, "--requires-shipping"), "requiresShipping");
@@ -294,8 +289,6 @@ export async function runShop(args) {
294
289
  body.title = title;
295
290
  if (description !== undefined)
296
291
  body.description = description;
297
- if (checkout !== undefined)
298
- body.checkout = checkout;
299
292
  if (amount !== undefined)
300
293
  body.amount = amount;
301
294
  if (minCents !== undefined)
@@ -319,10 +312,10 @@ export async function runShop(args) {
319
312
  printItem(data.item);
320
313
  return EXIT_OK;
321
314
  }
322
- if (sub === "archive") {
315
+ if (sub === "archive" || sub === "restore") {
323
316
  const itemId = requireShopId(work);
324
317
  assertNoUnknownFlags(work, "store");
325
- const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/archive`, {});
318
+ const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/${sub}`, {});
326
319
  if (isJson()) {
327
320
  emitOk("store", data);
328
321
  return EXIT_OK;
@@ -466,11 +459,11 @@ export async function runShop(args) {
466
459
  printItem(data.item);
467
460
  return EXIT_OK;
468
461
  }
469
- if (sub === "archive") {
462
+ if (sub === "archive" || sub === "restore") {
470
463
  const itemId = requireShopId(work, "Item id", { allowIdFlag: false });
471
464
  const variantId = requireVariantId(takeFlag(work, "--id") ?? takeFlag(work, "--variant"));
472
465
  assertNoUnknownFlags(work, "store");
473
- const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/variants/${encodeURIComponent(variantId)}/archive`, {});
466
+ const data = await v1Post(`${base}/items/${encodeURIComponent(itemId)}/variants/${encodeURIComponent(variantId)}/${sub}`, {});
474
467
  if (isJson()) {
475
468
  emitOk("store", data);
476
469
  return EXIT_OK;
@@ -568,7 +561,9 @@ export async function runShop(args) {
568
561
  fulfill.pickupEnabled ? "pickup" : null,
569
562
  fulfill.shippingEnabled ? "delivery" : null,
570
563
  ].filter(Boolean);
571
- const fulfillText = methods.length > 0 ? methods.join("+") : fulfill.shipping ?? "(no fulfillment)";
564
+ const fulfillText = methods.length > 0
565
+ ? methods.join("+")
566
+ : (fulfill.shipping ?? "(no fulfillment)");
572
567
  const checkoutText = s.storeCheckout === "instant" ? "pay-now" : "cart";
573
568
  const feeText = fee.enabled ? `service-fee ${fee.cents ?? 0}` : "service-fee off";
574
569
  console.log(`${s.currency ?? "(no currency)"} ${fulfillText} ${checkoutText} ${feeText}`);
@@ -619,39 +614,54 @@ export async function runShop(args) {
619
614
  console.log("Fulfillment saved.");
620
615
  return EXIT_OK;
621
616
  }
622
- if (sub === "set-currency") {
623
- const currency = takeFlag(work, "--currency");
617
+ if (sub === "set-receipt") {
618
+ const templateId = takeFlag(work, "--template");
619
+ const stock = hasFlag(work, "--stock");
624
620
  assertNoUnknownFlags(work, "store");
625
- if (!currency) {
626
- throw new CliHttpError("Usage: frontpage store settings set-currency --currency usd (any Stripe presentment code such as usd or jpy)", EXIT_USAGE, 0, "bad_request");
627
- }
628
- const data = await v1Post(`${base}/settings/currency`, { currency });
621
+ if ((templateId === undefined) === !stock)
622
+ throw new CliHttpError("Use --template <id> or --stock, never both.", EXIT_USAGE, 0, "bad_request");
623
+ const data = await v1Post(`${base}/settings/receipt`, {
624
+ templateId: stock ? "" : templateId,
625
+ });
629
626
  if (isJson())
630
627
  emitOk("store", data);
631
628
  else
632
- console.log("Currency saved.");
629
+ console.log("Order receipt saved. No email was sent.");
633
630
  return EXIT_OK;
634
631
  }
635
- if (sub === "set-checkout") {
636
- const checkout = takeFlag(work, "--mode") ?? takeFlag(work, "--checkout");
632
+ if (sub === "availability") {
633
+ const mode = work.shift() ?? "get";
634
+ if (!["get", "on", "off"].includes(mode))
635
+ throw new CliHttpError("Use store settings availability get|on|off.", EXIT_USAGE, 0, "bad_request");
637
636
  assertNoUnknownFlags(work, "store");
638
- if (checkout !== "cart" && checkout !== "instant") {
639
- throw new CliHttpError("Usage: frontpage store settings set-checkout --mode cart|instant", EXIT_USAGE, 0, "bad_request");
640
- }
641
- const data = await v1Post(`${base}/settings/checkout`, { checkout });
637
+ if (mode !== "get") {
638
+ const problem = requireYesOrTty("Change Store availability");
639
+ if (problem)
640
+ throw new CliHttpError(problem, EXIT_USAGE, 0, "bad_request");
641
+ }
642
+ const data = mode === "get"
643
+ ? await v1Get(`${base}/settings/availability`)
644
+ : await v1Post(`${base}/settings/availability`, {
645
+ enabled: mode === "on",
646
+ confirm: true,
647
+ });
642
648
  if (isJson())
643
649
  emitOk("store", data);
644
650
  else
645
- console.log(checkout === "instant" ? "Checkout is Pay now." : "Checkout is add to cart.");
651
+ console.log(JSON.stringify(data, null, 2));
646
652
  return EXIT_OK;
647
653
  }
648
654
  if (sub === "set-checkout-fee") {
649
655
  const off = hasFlag(work, "--off");
650
656
  const cents = parseCentsFlag(takeFlag(work, "--cents"), "checkoutFeeCents");
657
+ const mode = takeFlag(work, "--mode");
658
+ const bps = parseCountFlag(takeFlag(work, "--bps"), "checkoutFeeBps");
651
659
  assertNoUnknownFlags(work, "store");
652
660
  const data = await v1Post(`${base}/settings/checkout-fee`, {
653
661
  enabled: off ? false : true,
654
662
  cents,
663
+ mode,
664
+ bps,
655
665
  });
656
666
  if (isJson())
657
667
  emitOk("store", data);
@@ -661,61 +671,6 @@ export async function runShop(args) {
661
671
  }
662
672
  return emitUsage("store", USAGE);
663
673
  }
664
- if (group === "connect") {
665
- const work = rest.slice(1);
666
- const noOpen = hasFlag(work, "--no-open");
667
- assertNoUnknownFlags(work, "store");
668
- const data = await v1Post(`${base}/connect`, { action: "connect" });
669
- const already = data.alreadyConnected === true;
670
- if (isJson()) {
671
- emitOk("store", data);
672
- }
673
- else {
674
- const url = resolveOpenUrl(data.connectUrl);
675
- if (url)
676
- console.log(url);
677
- console.log(typeof data.message === "string" ? data.message : already ? "Already connected." : CONNECT_FINISH);
678
- if (typeof data.warning === "string")
679
- console.log(data.warning);
680
- }
681
- const url = resolveOpenUrl(data.connectUrl);
682
- if (url && !noOpen && !already)
683
- openBrowser(url);
684
- return EXIT_OK;
685
- }
686
- if (group === "status") {
687
- const work = rest.slice(1);
688
- assertNoUnknownFlags(work, "store");
689
- const data = await v1Get(`${base}/connect`);
690
- if (isJson()) {
691
- emitOk("store", data);
692
- return EXIT_OK;
693
- }
694
- const connection = (data.connection ?? {});
695
- const display = connection.display ?? {};
696
- console.log(`connected ${connection.ready ? "yes" : "no"}`);
697
- console.log(`charges ${display.chargesEnabled == null ? "—" : display.chargesEnabled ? "yes" : "no"}`);
698
- console.log(`account ${display.accountPresent ? "present" : "none"}`);
699
- if (typeof data.sharedStripe === "string")
700
- console.log(`note ${data.sharedStripe}`);
701
- if (typeof data.connectUrl === "string")
702
- console.log(`connect ${data.connectUrl}`);
703
- return EXIT_OK;
704
- }
705
- if (group === "disconnect") {
706
- const work = rest.slice(1);
707
- assertNoUnknownFlags(work, "store");
708
- const refuse = requireYesOrTty("disconnect the store (shared Stripe account with payments)");
709
- if (refuse)
710
- return emitUsage("store", refuse);
711
- const data = await v1Post(`${base}/connect`, { action: "disconnect" });
712
- if (isJson()) {
713
- emitOk("store", data);
714
- return EXIT_OK;
715
- }
716
- console.log(typeof data.message === "string" ? data.message : "Disconnected.");
717
- return EXIT_OK;
718
- }
719
674
  if (group === "import") {
720
675
  const work = rest.slice(1);
721
676
  assertNoUnknownFlags(work, "store");
@@ -740,7 +695,7 @@ export async function runShop(args) {
740
695
  console.log(`${imported.length} imported ${skipped.length} skipped ${failed.length} failed`);
741
696
  return EXIT_OK;
742
697
  }
743
- if (group === "orders" || group === "payments") {
698
+ if (group === "orders") {
744
699
  const noun = group;
745
700
  const sub = rest[1] ?? "list";
746
701
  const work = rest.slice(2);
@@ -27,7 +27,9 @@ _frontpage_completions() {
27
27
  analytics) COMPREPLY=( $(compgen -W "dashboard summary query funnel timeseries compare sql" -- "\${cur}") ) ;;
28
28
  people) COMPREPLY=( $(compgen -W "list get add set note rm-note follow-up hide unhide" -- "\${cur}") ) ;;
29
29
  routines) COMPREPLY=( $(compgen -W "list get add set pause resume rm run runs" -- "\${cur}") ) ;;
30
- store) COMPREPLY=( $(compgen -W "items variants categories settings orders payments connect status disconnect import" -- "\${cur}") ) ;;
30
+ buttons) COMPREPLY=( $(compgen -W "list get create update archive restore" -- "\${cur}") ) ;;
31
+ payments) COMPREPLY=( $(compgen -W "status settings connect availability activity buttons tax invoices recurring customers --file --operation-key --preview-hash --person --email --description --amount-cents --cadence --first-date --time-zone --due-days --no-open --site --json --yes" -- "\${cur}") ) ;;
32
+ store) COMPREPLY=( $(compgen -W "items variants categories settings orders import" -- "\${cur}") ) ;;
31
33
  modules)
32
34
  if [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == google_reviews ]]; then
33
35
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect" -- "\${cur}") )
@@ -36,7 +38,7 @@ _frontpage_completions() {
36
38
  elif [[ \${COMP_CWORD} -ge 3 && (\${COMP_WORDS[2]} == callbar || \${COMP_WORDS[2]} == booking || \${COMP_WORDS[2]} == signups) ]]; then
37
39
  COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config get set" -- "\${cur}") )
38
40
  elif [[ \${COMP_CWORD} -ge 3 && \${COMP_WORDS[2]} == payments ]]; then
39
- COMPREPLY=( $(compgen -W "docs request-setup enable disable archive config status connect disconnect" -- "\${cur}") )
41
+ COMPREPLY=( $(compgen -W "docs" -- "\${cur}") )
40
42
  else
41
43
  COMPREPLY=( $(compgen -W "list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews" -- "\${cur}") )
42
44
  fi
@@ -72,7 +74,9 @@ _frontpage() {
72
74
  analytics) _values 'subcommand' dashboard summary query funnel timeseries compare sql ;;
73
75
  people) _values 'subcommand' list get add set note rm-note follow-up hide unhide ;;
74
76
  routines) _values 'subcommand' list get add set pause resume rm run runs ;;
75
- store) _values 'subcommand' items variants categories settings orders payments connect status disconnect import ;;
77
+ buttons) _values 'subcommand' list get create update archive restore ;;
78
+ payments) _values 'subcommand' status settings connect availability activity buttons tax invoices recurring customers ;;
79
+ store) _values 'subcommand' items variants categories settings orders import ;;
76
80
  modules)
77
81
  if [[ $words[2] == google_reviews ]]; then
78
82
  _values 'subcommand' docs request-setup enable disable archive config connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect
@@ -81,7 +85,7 @@ _frontpage() {
81
85
  elif [[ $words[2] == callbar || $words[2] == booking || $words[2] == signups ]]; then
82
86
  _values 'subcommand' docs request-setup enable disable archive config get set
83
87
  elif [[ $words[2] == payments ]]; then
84
- _values 'subcommand' docs request-setup enable disable archive config status connect disconnect
88
+ _values 'subcommand' docs
85
89
  else
86
90
  _values 'subcommand' list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews
87
91
  fi
@@ -110,12 +114,14 @@ export function generateFishCompletion() {
110
114
  `complete -c frontpage -n "__fish_seen_subcommand_from analytics" -a "dashboard summary query funnel timeseries compare sql"`,
111
115
  `complete -c frontpage -n "__fish_seen_subcommand_from people" -a "list get add set note rm-note follow-up hide unhide"`,
112
116
  `complete -c frontpage -n "__fish_seen_subcommand_from routines" -a "list get add set pause resume rm run runs"`,
113
- `complete -c frontpage -n "__fish_seen_subcommand_from store" -a "items variants categories settings orders payments connect status disconnect import"`,
117
+ `complete -c frontpage -n "__fish_seen_subcommand_from buttons" -a "list get create update archive restore"`,
118
+ `complete -c frontpage -n "__fish_seen_subcommand_from payments" -a "status settings connect availability activity buttons tax invoices recurring customers"
119
+ complete -c frontpage -n "__fish_seen_subcommand_from store" -a "items variants categories settings orders import"`,
114
120
  `complete -c frontpage -n "__fish_seen_subcommand_from modules" -a "list docs request-setup enable disable archive config callbar forms booking signups payments articles google_reviews"`,
115
121
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from google_reviews" -a "connect resolve status list-reviews hide show set-stars set-sort set-weight reset-weights write-review-url refresh disconnect"`,
116
122
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from forms" -a "get set destinations connect-destination remove-destination disconnect-destination attach-destination detach-destination request-destination-setup"`,
117
123
  `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from callbar booking signups" -a "get set"`,
118
- `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from payments" -a "status connect disconnect"`,
124
+ `complete -c frontpage -n "__fish_seen_subcommand_from modules; and __fish_seen_subcommand_from payments" -a "docs"`,
119
125
  `complete -c frontpage -n "__fish_seen_subcommand_from extensions" -a "list docs enable disable config"`,
120
126
  `complete -c frontpage -l site -d "Site subdomain slug"`,
121
127
  ];