@saastemly/voidcommerce 0.6.0 → 0.7.0
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/catalog.js +1 -1
- package/dist/cli.js +4 -4
- package/dist/generate/auth.d.ts +13 -0
- package/dist/{index-2qt2yh3z.js → index-6w8rwtsh.js} +2 -2
- package/dist/{index-f1wds190.js → index-85m0qkmg.js} +68 -11
- package/dist/{index-844b3qn9.js → index-mnb7fz2t.js} +12 -1
- package/dist/{index-xx5p4b8d.js → index-tjy6yygc.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/{keys-qwvt324e.js → keys-92tcdjrc.js} +3 -3
- package/dist/manifest.js +2 -2
- package/package.json +1 -1
- package/src/catalog.ts +12 -1
- package/src/generate/auth.ts +34 -2
- package/src/generate/index.ts +5 -3
- package/src/generate/support.ts +67 -3
package/dist/catalog.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
runVoid,
|
|
22
22
|
version,
|
|
23
23
|
voidAppsIn
|
|
24
|
-
} from "./index-
|
|
24
|
+
} from "./index-85m0qkmg.js";
|
|
25
25
|
import {
|
|
26
26
|
PRIVATE_KEY_VAR,
|
|
27
27
|
SECRETS_FILE,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
setVariable,
|
|
43
43
|
variableNames,
|
|
44
44
|
verifyCloudflareToken
|
|
45
|
-
} from "./index-
|
|
45
|
+
} from "./index-6w8rwtsh.js";
|
|
46
46
|
import {
|
|
47
47
|
LAYOUTS,
|
|
48
48
|
oneOrigin,
|
|
@@ -50,10 +50,10 @@ import {
|
|
|
50
50
|
validate,
|
|
51
51
|
writeManifest,
|
|
52
52
|
zoneOf
|
|
53
|
-
} from "./index-
|
|
53
|
+
} from "./index-tjy6yygc.js";
|
|
54
54
|
import {
|
|
55
55
|
GROUPS
|
|
56
|
-
} from "./index-
|
|
56
|
+
} from "./index-mnb7fz2t.js";
|
|
57
57
|
import {
|
|
58
58
|
__require
|
|
59
59
|
} from "./index-0v6na3yp.js";
|
package/dist/generate/auth.d.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
import { type Manifest } from "../manifest";
|
|
2
|
+
/**
|
|
3
|
+
* Carriers with a provider behind them, and whether each needs `lib/shipping.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Exported because `generate/index.ts` has to know which shops get that file,
|
|
6
|
+
* and a second hand-maintained list of carrier ids WILL drift from this one —
|
|
7
|
+
* it already did: adding Royal Mail here left the file ungenerated, so the
|
|
8
|
+
* generated `auth.ts` imported a price table that did not exist.
|
|
9
|
+
*
|
|
10
|
+
* The token needs nothing: no origin, no tariff. Everything else needs one or
|
|
11
|
+
* the other, so the rule is simply "any carrier but the token".
|
|
12
|
+
*/
|
|
13
|
+
export declare const WIRED_CARRIERS: readonly ["royal-mail", "dhl-express", "shippo", "easypost", "ups", "fedex", "nft"];
|
|
14
|
+
export declare const needsShippingConfig: (id: string) => boolean;
|
|
2
15
|
export declare function renderAuthTs(manifest: Manifest): string;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
hasFrontend,
|
|
4
4
|
isApex,
|
|
5
5
|
zone
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-tjy6yygc.js";
|
|
7
7
|
import {
|
|
8
8
|
__require,
|
|
9
9
|
__toESM
|
|
@@ -368,7 +368,7 @@ async function decryptSecrets(project) {
|
|
|
368
368
|
}
|
|
369
369
|
const expected = committedPublicKey(root);
|
|
370
370
|
if (expected) {
|
|
371
|
-
const { publicKeyFor } = await import("./keys-
|
|
371
|
+
const { publicKeyFor } = await import("./keys-92tcdjrc.js");
|
|
372
372
|
const derived = await publicKeyFor(privateKey);
|
|
373
373
|
if (derived && derived.toLowerCase() !== expected.toLowerCase()) {
|
|
374
374
|
return {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
renderEnvLocal,
|
|
10
10
|
renderEnvProduction,
|
|
11
11
|
renderEnvTs
|
|
12
|
-
} from "./index-
|
|
12
|
+
} from "./index-6w8rwtsh.js";
|
|
13
13
|
import {
|
|
14
14
|
MANIFEST_FILE,
|
|
15
15
|
has,
|
|
@@ -22,10 +22,10 @@ import {
|
|
|
22
22
|
workerHosts,
|
|
23
23
|
writeManifest,
|
|
24
24
|
zone
|
|
25
|
-
} from "./index-
|
|
25
|
+
} from "./index-tjy6yygc.js";
|
|
26
26
|
import {
|
|
27
27
|
CHOICES
|
|
28
|
-
} from "./index-
|
|
28
|
+
} from "./index-mnb7fz2t.js";
|
|
29
29
|
import {
|
|
30
30
|
__require
|
|
31
31
|
} from "./index-0v6na3yp.js";
|
|
@@ -48,10 +48,17 @@ var VAT = {
|
|
|
48
48
|
PL: { rate: 2300, name: "VAT" }
|
|
49
49
|
};
|
|
50
50
|
var social = (manifest, id, envPrefix) => has(manifest, id) ? ` ${id}: { clientId: must(env, "${envPrefix}_CLIENT_ID"), clientSecret: must(env, "${envPrefix}_CLIENT_SECRET") },` : null;
|
|
51
|
+
var WIRED_CARRIERS = ["royal-mail", "dhl-express", "shippo", "easypost", "ups", "fedex", "nft"];
|
|
52
|
+
var needsShippingConfig = (id) => WIRED_CARRIERS.includes(id) && id !== "nft";
|
|
51
53
|
function renderAuthTs(manifest) {
|
|
52
54
|
const { shop } = manifest;
|
|
53
55
|
const lines = [];
|
|
54
56
|
const CARRIER_CALLS = {
|
|
57
|
+
"royal-mail": {
|
|
58
|
+
label: "Royal Mail",
|
|
59
|
+
call: `royalMailProvider({ apiKey: must(env, "CLICK_DROP_AUTH_KEY"), services: royalMailServices, currency: "${shop.currency}", carrierName: "Royal Mail" })`,
|
|
60
|
+
import: `import { royalMailProvider } from "@saastemly/better-commerce/providers/royal-mail";`
|
|
61
|
+
},
|
|
55
62
|
"dhl-express": {
|
|
56
63
|
label: "DHL Express",
|
|
57
64
|
call: `dhlExpressProvider({ apiKey: must(env, "DHL_API_KEY"), apiSecret: must(env, "DHL_API_SECRET"), accountNumber: must(env, "DHL_ACCOUNT_NUMBER"), from: shipFrom })`,
|
|
@@ -86,8 +93,12 @@ import { mintProvider, destinationFor } from "./lib/mint.ts";`
|
|
|
86
93
|
};
|
|
87
94
|
const carriers = Object.keys(CARRIER_CALLS).filter((id) => has(manifest, id));
|
|
88
95
|
const carrierImports = carriers.map((id) => CARRIER_CALLS[id].import);
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
const shipping = [
|
|
97
|
+
...carriers.some((id) => id !== "nft" && id !== "royal-mail") ? ["shipFrom"] : [],
|
|
98
|
+
...carriers.includes("royal-mail") ? ["royalMailServices"] : []
|
|
99
|
+
];
|
|
100
|
+
if (shipping.length > 0)
|
|
101
|
+
carrierImports.push(`import { ${shipping.join(", ")} } from "./lib/shipping.ts";`);
|
|
91
102
|
const wantsNft = has(manifest, "nft");
|
|
92
103
|
const carrierFulfillment = carriers.length ? `, deliveryFulfillment([${carriers.map((id) => CARRIER_CALLS[id].call).join(", ")}])` : "";
|
|
93
104
|
const nftDefault = carriers.length ? `, defaultProvider: "delivery:${carriers.join("+")}"` : "";
|
|
@@ -1333,7 +1344,7 @@ import color from "picocolors";
|
|
|
1333
1344
|
// package.json
|
|
1334
1345
|
var package_default = {
|
|
1335
1346
|
name: "@saastemly/voidcommerce",
|
|
1336
|
-
version: "0.
|
|
1347
|
+
version: "0.7.0",
|
|
1337
1348
|
description: "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
|
|
1338
1349
|
type: "module",
|
|
1339
1350
|
license: "MIT",
|
|
@@ -2181,13 +2192,18 @@ export async function destinationFor(orderId: string): Promise<string | null> {
|
|
|
2181
2192
|
`;
|
|
2182
2193
|
}
|
|
2183
2194
|
function renderShippingTs(manifest) {
|
|
2184
|
-
|
|
2185
|
-
|
|
2195
|
+
const royalMail = has(manifest, "royal-mail");
|
|
2196
|
+
const origin = ["dhl-express", "shippo", "easypost", "ups", "fedex"].some((id) => has(manifest, id));
|
|
2197
|
+
const imports = [
|
|
2198
|
+
...origin ? [`import type { DeliveryAddress } from "@saastemly/better-commerce/providers/delivery";`] : [],
|
|
2199
|
+
...royalMail ? [`import type { RoyalMailService } from "@saastemly/better-commerce/providers/royal-mail";`] : []
|
|
2200
|
+
];
|
|
2201
|
+
const originBlock = `
|
|
2186
2202
|
/**
|
|
2187
2203
|
* The address every parcel is quoted and shipped FROM.
|
|
2188
2204
|
*
|
|
2189
|
-
* TODO VERIFY: this is a placeholder. A carrier quotes against it, and
|
|
2190
|
-
*
|
|
2205
|
+
* TODO VERIFY: this is a placeholder. A carrier quotes against it, and most
|
|
2206
|
+
* refuse a shipment whose origin has no \`name\` and \`phone\`.
|
|
2191
2207
|
*/
|
|
2192
2208
|
export const shipFrom: DeliveryAddress = {
|
|
2193
2209
|
name: "${manifest.shop.name}",
|
|
@@ -2198,6 +2214,9 @@ export const shipFrom: DeliveryAddress = {
|
|
|
2198
2214
|
phone: "TODO VERIFY: +44…",
|
|
2199
2215
|
};
|
|
2200
2216
|
`;
|
|
2217
|
+
return `${imports.join(`
|
|
2218
|
+
`)}
|
|
2219
|
+
${origin ? originBlock : ""}${royalMail ? ROYAL_MAIL_SERVICES() : ""}`;
|
|
2201
2220
|
}
|
|
2202
2221
|
function renderErpTs() {
|
|
2203
2222
|
return `import { businessCentralProvider } from "@saastemly/better-commerce/providers/business-central";
|
|
@@ -2226,6 +2245,44 @@ export const erpFulfillment = (currency: string): FulfillmentProvider =>
|
|
|
2226
2245
|
}).fulfillment();
|
|
2227
2246
|
`;
|
|
2228
2247
|
}
|
|
2248
|
+
var ROYAL_MAIL_SERVICES = () => `
|
|
2249
|
+
/**
|
|
2250
|
+
* What this shop charges for Royal Mail, by total parcel weight.
|
|
2251
|
+
*
|
|
2252
|
+
* TODO VERIFY: every \`amount\` below is a placeholder in pence. Replace them
|
|
2253
|
+
* with your own OBA rate card — Click & Drop → Settings → Shipping services
|
|
2254
|
+
* lists exactly which services your account has, and the codes here must be
|
|
2255
|
+
* among them. Service codes are account-specific: the published list is from
|
|
2256
|
+
* 2021 and Royal Mail warn that "available services are unique to your OBA
|
|
2257
|
+
* account and individual service agreements".
|
|
2258
|
+
*
|
|
2259
|
+
* The band whose \`maxGrams\` first covers the parcel wins, so order does not
|
|
2260
|
+
* matter. A parcel heavier than every band gets no rate at all rather than a
|
|
2261
|
+
* wrong one, and Click & Drop refuses anything over 30 kg.
|
|
2262
|
+
*/
|
|
2263
|
+
export const royalMailServices: RoyalMailService[] = [
|
|
2264
|
+
{
|
|
2265
|
+
code: "TPN24",
|
|
2266
|
+
name: "Tracked 24",
|
|
2267
|
+
estimatedDays: 1,
|
|
2268
|
+
bands: [
|
|
2269
|
+
{ maxGrams: 1000, amount: 0 /* TODO VERIFY */ },
|
|
2270
|
+
{ maxGrams: 2000, amount: 0 /* TODO VERIFY */ },
|
|
2271
|
+
{ maxGrams: 5000, amount: 0 /* TODO VERIFY */ },
|
|
2272
|
+
],
|
|
2273
|
+
},
|
|
2274
|
+
{
|
|
2275
|
+
code: "TPS48",
|
|
2276
|
+
name: "Tracked 48",
|
|
2277
|
+
estimatedDays: 2,
|
|
2278
|
+
bands: [
|
|
2279
|
+
{ maxGrams: 1000, amount: 0 /* TODO VERIFY */ },
|
|
2280
|
+
{ maxGrams: 2000, amount: 0 /* TODO VERIFY */ },
|
|
2281
|
+
{ maxGrams: 5000, amount: 0 /* TODO VERIFY */ },
|
|
2282
|
+
],
|
|
2283
|
+
},
|
|
2284
|
+
];
|
|
2285
|
+
`;
|
|
2229
2286
|
|
|
2230
2287
|
// src/generate/index.ts
|
|
2231
2288
|
async function exists(path) {
|
|
@@ -2314,7 +2371,7 @@ async function generateApi(root, dir, manifest, result, opts) {
|
|
|
2314
2371
|
if (has(manifest, "nft")) {
|
|
2315
2372
|
await put(root, at("lib/mint.ts"), renderMintTs(), result, own);
|
|
2316
2373
|
}
|
|
2317
|
-
if (
|
|
2374
|
+
if (WIRED_CARRIERS.some((id) => needsShippingConfig(id) && has(manifest, id))) {
|
|
2318
2375
|
await put(root, at("lib/shipping.ts"), renderShippingTs(manifest), result, own);
|
|
2319
2376
|
}
|
|
2320
2377
|
if (has(manifest, "business-central")) {
|
|
@@ -446,7 +446,18 @@ var CARRIERS = {
|
|
|
446
446
|
{ id: "fedex", label: "FedEx", hint: "your own FedEx account, for negotiated rates an aggregator cannot see", env: [{ key: "FEDEX_CLIENT_ID", breaks: "FedEx rates are not quoted" }, { key: "FEDEX_CLIENT_SECRET", breaks: "the same" }, { key: "FEDEX_ACCOUNT_NUMBER", breaks: "FedEx has no account to bill the transport to" }] },
|
|
447
447
|
{ id: "dhl-express", label: "DHL Express", hint: "international express, and the one worth having for a shop that posts abroad", env: [{ key: "DHL_API_KEY", breaks: "DHL rates are not quoted" }, { key: "DHL_API_SECRET", breaks: "the same" }, { key: "DHL_ACCOUNT_NUMBER", breaks: "DHL has no account to bill the transport to" }] },
|
|
448
448
|
{ id: "usps", label: "USPS", hint: "US domestic post — the cheapest way to ship a small parcel inside the US", env: [{ key: "USPS_CLIENT_ID", breaks: "USPS rates are not quoted" }, { key: "USPS_CLIENT_SECRET", breaks: "the same" }] },
|
|
449
|
-
{
|
|
449
|
+
{
|
|
450
|
+
id: "royal-mail",
|
|
451
|
+
label: "Royal Mail (Click & Drop)",
|
|
452
|
+
hint: "UK post. Needs an OBA business account — Click & Drop generates labels through the API only for those. Prices come from your own tariff table, because no Royal Mail API quotes rates",
|
|
453
|
+
env: [
|
|
454
|
+
{
|
|
455
|
+
key: "CLICK_DROP_AUTH_KEY",
|
|
456
|
+
breaks: "no label and no tracking number: orders are created in Click & Drop but never despatched",
|
|
457
|
+
where: "Click & Drop → Settings → Integrations → Click & Drop API"
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
},
|
|
450
461
|
{ id: "aramex", label: "Aramex", hint: "the carrier to reach the Gulf and wider Middle East reliably", env: [{ key: "ARAMEX_API_KEY", breaks: "Aramex rates are not quoted" }] },
|
|
451
462
|
{ id: "doordash-drive", label: "DoorDash Drive", hint: "same-day local delivery", env: [{ key: "DOORDASH_DEVELOPER_ID", breaks: "DoorDash is not offered" }] },
|
|
452
463
|
{ id: "stuart", label: "Stuart", hint: "same-day courier, Europe", env: [{ key: "STUART_CLIENT_ID", breaks: "Stuart is not offered" }] },
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
routeProblem,
|
|
52
52
|
strictDependencies,
|
|
53
53
|
upsertJsonc
|
|
54
|
-
} from "./index-
|
|
54
|
+
} from "./index-85m0qkmg.js";
|
|
55
55
|
import {
|
|
56
56
|
allEnvKeys,
|
|
57
57
|
envSummary,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
renderEnvLocal,
|
|
60
60
|
renderEnvProduction,
|
|
61
61
|
renderEnvTs
|
|
62
|
-
} from "./index-
|
|
62
|
+
} from "./index-6w8rwtsh.js";
|
|
63
63
|
import {
|
|
64
64
|
LAYOUTS,
|
|
65
65
|
MANIFEST_FILE,
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
writeManifest,
|
|
81
81
|
zone,
|
|
82
82
|
zoneOf
|
|
83
|
-
} from "./index-
|
|
83
|
+
} from "./index-tjy6yygc.js";
|
|
84
84
|
import {
|
|
85
85
|
AUTH_PLUGINS,
|
|
86
86
|
CARRIERS,
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
SIGN_IN,
|
|
98
98
|
TAX,
|
|
99
99
|
UI
|
|
100
|
-
} from "./index-
|
|
100
|
+
} from "./index-mnb7fz2t.js";
|
|
101
101
|
import"./index-0v6na3yp.js";
|
|
102
102
|
export {
|
|
103
103
|
zoneOf,
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
keysCommand,
|
|
6
6
|
provisionKey,
|
|
7
7
|
publicKeyFor
|
|
8
|
-
} from "./index-
|
|
9
|
-
import"./index-
|
|
10
|
-
import"./index-
|
|
8
|
+
} from "./index-6w8rwtsh.js";
|
|
9
|
+
import"./index-tjy6yygc.js";
|
|
10
|
+
import"./index-mnb7fz2t.js";
|
|
11
11
|
import"./index-0v6na3yp.js";
|
|
12
12
|
export {
|
|
13
13
|
publicKeyFor,
|
package/dist/manifest.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saastemly/voidcommerce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/catalog.ts
CHANGED
|
@@ -519,7 +519,18 @@ export const CARRIERS: Group = {
|
|
|
519
519
|
{ id: "fedex", label: "FedEx", hint: "your own FedEx account, for negotiated rates an aggregator cannot see", env: [{ key: "FEDEX_CLIENT_ID", breaks: "FedEx rates are not quoted" }, { key: "FEDEX_CLIENT_SECRET", breaks: "the same" }, { key: "FEDEX_ACCOUNT_NUMBER", breaks: "FedEx has no account to bill the transport to" }] },
|
|
520
520
|
{ id: "dhl-express", label: "DHL Express", hint: "international express, and the one worth having for a shop that posts abroad", env: [{ key: "DHL_API_KEY", breaks: "DHL rates are not quoted" }, { key: "DHL_API_SECRET", breaks: "the same" }, { key: "DHL_ACCOUNT_NUMBER", breaks: "DHL has no account to bill the transport to" }] },
|
|
521
521
|
{ id: "usps", label: "USPS", hint: "US domestic post — the cheapest way to ship a small parcel inside the US", env: [{ key: "USPS_CLIENT_ID", breaks: "USPS rates are not quoted" }, { key: "USPS_CLIENT_SECRET", breaks: "the same" }] },
|
|
522
|
-
{
|
|
522
|
+
{
|
|
523
|
+
id: "royal-mail",
|
|
524
|
+
label: "Royal Mail (Click & Drop)",
|
|
525
|
+
hint: "UK post. Needs an OBA business account — Click & Drop generates labels through the API only for those. Prices come from your own tariff table, because no Royal Mail API quotes rates",
|
|
526
|
+
env: [
|
|
527
|
+
{
|
|
528
|
+
key: "CLICK_DROP_AUTH_KEY",
|
|
529
|
+
breaks: "no label and no tracking number: orders are created in Click & Drop but never despatched",
|
|
530
|
+
where: "Click & Drop → Settings → Integrations → Click & Drop API",
|
|
531
|
+
},
|
|
532
|
+
],
|
|
533
|
+
},
|
|
523
534
|
{ id: "aramex", label: "Aramex", hint: "the carrier to reach the Gulf and wider Middle East reliably", env: [{ key: "ARAMEX_API_KEY", breaks: "Aramex rates are not quoted" }] },
|
|
524
535
|
{ id: "doordash-drive", label: "DoorDash Drive", hint: "same-day local delivery", env: [{ key: "DOORDASH_DEVELOPER_ID", breaks: "DoorDash is not offered" }] },
|
|
525
536
|
{ id: "stuart", label: "Stuart", hint: "same-day courier, Europe", env: [{ key: "STUART_CLIENT_ID", breaks: "Stuart is not offered" }] },
|
package/src/generate/auth.ts
CHANGED
|
@@ -49,6 +49,20 @@ const social = (manifest: Manifest, id: string, envPrefix: string) =>
|
|
|
49
49
|
? ` ${id}: { clientId: must(env, "${envPrefix}_CLIENT_ID"), clientSecret: must(env, "${envPrefix}_CLIENT_SECRET") },`
|
|
50
50
|
: null;
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Carriers with a provider behind them, and whether each needs `lib/shipping.ts`.
|
|
54
|
+
*
|
|
55
|
+
* Exported because `generate/index.ts` has to know which shops get that file,
|
|
56
|
+
* and a second hand-maintained list of carrier ids WILL drift from this one —
|
|
57
|
+
* it already did: adding Royal Mail here left the file ungenerated, so the
|
|
58
|
+
* generated `auth.ts` imported a price table that did not exist.
|
|
59
|
+
*
|
|
60
|
+
* The token needs nothing: no origin, no tariff. Everything else needs one or
|
|
61
|
+
* the other, so the rule is simply "any carrier but the token".
|
|
62
|
+
*/
|
|
63
|
+
export const WIRED_CARRIERS = ["royal-mail", "dhl-express", "shippo", "easypost", "ups", "fedex", "nft"] as const;
|
|
64
|
+
export const needsShippingConfig = (id: string): boolean => WIRED_CARRIERS.includes(id as never) && id !== "nft";
|
|
65
|
+
|
|
52
66
|
export function renderAuthTs(manifest: Manifest): string {
|
|
53
67
|
const { shop } = manifest;
|
|
54
68
|
const lines: Line[] = [];
|
|
@@ -68,6 +82,14 @@ export function renderAuthTs(manifest: Manifest): string {
|
|
|
68
82
|
const CARRIER_CALLS: Record<string, { call: string; label: string; import: string }> = {
|
|
69
83
|
// Every real carrier needs the ORIGIN it ships from, which is shop data
|
|
70
84
|
// rather than a credential — so it lives in an owned lib/shipping.ts.
|
|
85
|
+
"royal-mail": {
|
|
86
|
+
label: "Royal Mail",
|
|
87
|
+
// No `from`: Click & Drop posts from the account's registered
|
|
88
|
+
// address, so an origin here would be ignored. `services` is the
|
|
89
|
+
// price table in lib/shipping.ts, because nothing quotes Royal Mail.
|
|
90
|
+
call: `royalMailProvider({ apiKey: must(env, "CLICK_DROP_AUTH_KEY"), services: royalMailServices, currency: "${shop.currency}", carrierName: "Royal Mail" })`,
|
|
91
|
+
import: `import { royalMailProvider } from "@saastemly/better-commerce/providers/royal-mail";`,
|
|
92
|
+
},
|
|
71
93
|
"dhl-express": {
|
|
72
94
|
label: "DHL Express",
|
|
73
95
|
call: `dhlExpressProvider({ apiKey: must(env, "DHL_API_KEY"), apiSecret: must(env, "DHL_API_SECRET"), accountNumber: must(env, "DHL_ACCOUNT_NUMBER"), from: shipFrom })`,
|
|
@@ -101,8 +123,18 @@ export function renderAuthTs(manifest: Manifest): string {
|
|
|
101
123
|
};
|
|
102
124
|
const carriers = Object.keys(CARRIER_CALLS).filter((id) => has(manifest, id));
|
|
103
125
|
const carrierImports = carriers.map((id) => CARRIER_CALLS[id]!.import);
|
|
104
|
-
//
|
|
105
|
-
|
|
126
|
+
// One import from lib/shipping.ts, naming only what is actually used —
|
|
127
|
+
// an unused binding is a type error in a shop with `noUnusedLocals`.
|
|
128
|
+
//
|
|
129
|
+
// The token needs no origin. Royal Mail needs no origin either: Click &
|
|
130
|
+
// Drop posts from the address registered on the account, so a `from` here
|
|
131
|
+
// would be silently ignored. It needs the price table instead, because
|
|
132
|
+
// nothing quotes Royal Mail.
|
|
133
|
+
const shipping = [
|
|
134
|
+
...(carriers.some((id) => id !== "nft" && id !== "royal-mail") ? ["shipFrom"] : []),
|
|
135
|
+
...(carriers.includes("royal-mail") ? ["royalMailServices"] : []),
|
|
136
|
+
];
|
|
137
|
+
if (shipping.length > 0) carrierImports.push(`import { ${shipping.join(", ")} } from "./lib/shipping.ts";`);
|
|
106
138
|
const wantsNft = has(manifest, "nft");
|
|
107
139
|
// One bridge for every carrier: which ships is decided by whose rate the
|
|
108
140
|
// buyer paid for, so a token order mints and a parcel order posts.
|
package/src/generate/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, lstatSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { CHOICES } from "../catalog";
|
|
5
5
|
import { MANIFEST_FILE, type Manifest, has, isSingleApp, oneOrigin, packagesOf, specifier, workerHosts, writeManifest, zone } from "../manifest";
|
|
6
|
-
import { renderAuthTs } from "./auth";
|
|
6
|
+
import { renderAuthTs, WIRED_CARRIERS, needsShippingConfig } from "./auth";
|
|
7
7
|
import {
|
|
8
8
|
envSummary,
|
|
9
9
|
renderEnvExample,
|
|
@@ -189,8 +189,10 @@ async function generateApi(root: string, dir: string, manifest: Manifest, result
|
|
|
189
189
|
if (has(manifest, "nft")) {
|
|
190
190
|
await put(root, at("lib/mint.ts"), renderMintTs(), result, own);
|
|
191
191
|
}
|
|
192
|
-
// Any carrier but the token
|
|
193
|
-
|
|
192
|
+
// Any carrier but the token needs shop data of its own — an origin to quote
|
|
193
|
+
// against, or a price table. The list lives with the carriers themselves so
|
|
194
|
+
// it cannot drift from them.
|
|
195
|
+
if (WIRED_CARRIERS.some((id) => needsShippingConfig(id) && has(manifest, id))) {
|
|
194
196
|
await put(root, at("lib/shipping.ts"), renderShippingTs(manifest), result, own);
|
|
195
197
|
}
|
|
196
198
|
if (has(manifest, "business-central")) {
|
package/src/generate/support.ts
CHANGED
|
@@ -274,13 +274,22 @@ export async function destinationFor(orderId: string): Promise<string | null> {
|
|
|
274
274
|
* deploy, so both are marked rather than left blank.
|
|
275
275
|
*/
|
|
276
276
|
export function renderShippingTs(manifest: Manifest): string {
|
|
277
|
-
|
|
277
|
+
const royalMail = has(manifest, "royal-mail");
|
|
278
|
+
// Royal Mail posts from the address registered on the Click & Drop account,
|
|
279
|
+
// so an origin here would be ignored. Only carriers that QUOTE need one.
|
|
280
|
+
const origin = ["dhl-express", "shippo", "easypost", "ups", "fedex"].some((id) => has(manifest, id));
|
|
278
281
|
|
|
282
|
+
const imports = [
|
|
283
|
+
...(origin ? [`import type { DeliveryAddress } from "@saastemly/better-commerce/providers/delivery";`] : []),
|
|
284
|
+
...(royalMail ? [`import type { RoyalMailService } from "@saastemly/better-commerce/providers/royal-mail";`] : []),
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
const originBlock = `
|
|
279
288
|
/**
|
|
280
289
|
* The address every parcel is quoted and shipped FROM.
|
|
281
290
|
*
|
|
282
|
-
* TODO VERIFY: this is a placeholder. A carrier quotes against it, and
|
|
283
|
-
*
|
|
291
|
+
* TODO VERIFY: this is a placeholder. A carrier quotes against it, and most
|
|
292
|
+
* refuse a shipment whose origin has no \`name\` and \`phone\`.
|
|
284
293
|
*/
|
|
285
294
|
export const shipFrom: DeliveryAddress = {
|
|
286
295
|
name: "${manifest.shop.name}",
|
|
@@ -291,6 +300,8 @@ export const shipFrom: DeliveryAddress = {
|
|
|
291
300
|
phone: "TODO VERIFY: +44…",
|
|
292
301
|
};
|
|
293
302
|
`;
|
|
303
|
+
|
|
304
|
+
return `${imports.join("\n")}\n${origin ? originBlock : ""}${royalMail ? ROYAL_MAIL_SERVICES() : ""}`;
|
|
294
305
|
}
|
|
295
306
|
|
|
296
307
|
export function renderErpTs(): string {
|
|
@@ -320,3 +331,56 @@ export const erpFulfillment = (currency: string): FulfillmentProvider =>
|
|
|
320
331
|
}).fulfillment();
|
|
321
332
|
`;
|
|
322
333
|
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Royal Mail's price table — YOUR prices, because nothing will quote them.
|
|
338
|
+
*
|
|
339
|
+
* This is the one carrier here that cannot be asked what postage costs. No
|
|
340
|
+
* Royal Mail API quotes rates: not Click & Drop, not Shipping V3, not
|
|
341
|
+
* anything on the developer portal. Click & Drop imports orders and prints
|
|
342
|
+
* labels; postage is charged afterwards at whatever your OBA agreement says.
|
|
343
|
+
*
|
|
344
|
+
* So the shop quotes from this table, and the table has to match the rate
|
|
345
|
+
* card on your account or you will undercharge every order and find out at
|
|
346
|
+
* the invoice. The codes below are real Royal Mail service codes; the prices
|
|
347
|
+
* are NOT — they are placeholders, in pence.
|
|
348
|
+
*/
|
|
349
|
+
const ROYAL_MAIL_SERVICES = (): string => `
|
|
350
|
+
/**
|
|
351
|
+
* What this shop charges for Royal Mail, by total parcel weight.
|
|
352
|
+
*
|
|
353
|
+
* TODO VERIFY: every \`amount\` below is a placeholder in pence. Replace them
|
|
354
|
+
* with your own OBA rate card — Click & Drop → Settings → Shipping services
|
|
355
|
+
* lists exactly which services your account has, and the codes here must be
|
|
356
|
+
* among them. Service codes are account-specific: the published list is from
|
|
357
|
+
* 2021 and Royal Mail warn that "available services are unique to your OBA
|
|
358
|
+
* account and individual service agreements".
|
|
359
|
+
*
|
|
360
|
+
* The band whose \`maxGrams\` first covers the parcel wins, so order does not
|
|
361
|
+
* matter. A parcel heavier than every band gets no rate at all rather than a
|
|
362
|
+
* wrong one, and Click & Drop refuses anything over 30 kg.
|
|
363
|
+
*/
|
|
364
|
+
export const royalMailServices: RoyalMailService[] = [
|
|
365
|
+
{
|
|
366
|
+
code: "TPN24",
|
|
367
|
+
name: "Tracked 24",
|
|
368
|
+
estimatedDays: 1,
|
|
369
|
+
bands: [
|
|
370
|
+
{ maxGrams: 1000, amount: 0 /* TODO VERIFY */ },
|
|
371
|
+
{ maxGrams: 2000, amount: 0 /* TODO VERIFY */ },
|
|
372
|
+
{ maxGrams: 5000, amount: 0 /* TODO VERIFY */ },
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
code: "TPS48",
|
|
377
|
+
name: "Tracked 48",
|
|
378
|
+
estimatedDays: 2,
|
|
379
|
+
bands: [
|
|
380
|
+
{ maxGrams: 1000, amount: 0 /* TODO VERIFY */ },
|
|
381
|
+
{ maxGrams: 2000, amount: 0 /* TODO VERIFY */ },
|
|
382
|
+
{ maxGrams: 5000, amount: 0 /* TODO VERIFY */ },
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
`;
|