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.
- package/AGENTS.md +8 -2
- package/CUSTOMER-INBOX.md +38 -5
- package/PAYMENTS.md +411 -0
- package/README.md +6 -0
- package/dist/argv.d.ts +2 -0
- package/dist/argv.js +8 -0
- package/dist/bin.js +3 -0
- package/dist/commands/payments.d.ts +1 -0
- package/dist/commands/payments.js +315 -0
- package/dist/commands/shop.js +42 -87
- package/dist/completions/generate.js +12 -6
- package/dist/registry.js +92 -43
- package/llms.txt +8 -2
- package/package.json +4 -3
|
@@ -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
|
+
}
|
package/dist/commands/shop.js
CHANGED
|
@@ -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|
|
|
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) =>
|
|
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
|
-
|
|
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)}
|
|
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)}
|
|
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
|
|
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-
|
|
623
|
-
const
|
|
617
|
+
if (sub === "set-receipt") {
|
|
618
|
+
const templateId = takeFlag(work, "--template");
|
|
619
|
+
const stock = hasFlag(work, "--stock");
|
|
624
620
|
assertNoUnknownFlags(work, "store");
|
|
625
|
-
if (!
|
|
626
|
-
throw new CliHttpError("
|
|
627
|
-
}
|
|
628
|
-
|
|
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("
|
|
629
|
+
console.log("Order receipt saved. No email was sent.");
|
|
633
630
|
return EXIT_OK;
|
|
634
631
|
}
|
|
635
|
-
if (sub === "
|
|
636
|
-
const
|
|
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 (
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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(
|
|
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"
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 "
|
|
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
|
];
|