@saastemly/voidcommerce 0.2.0 → 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/dist/catalog.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  SIGN_IN,
15
15
  TAX,
16
16
  UI
17
- } from "./index-yk361vn2.js";
17
+ } from "./index-z4qazajn.js";
18
18
  export {
19
19
  UI,
20
20
  TAX,
package/dist/cli.js CHANGED
@@ -22,16 +22,16 @@ import {
22
22
  runVoid,
23
23
  version,
24
24
  voidAppsIn
25
- } from "./index-9x9q9ng2.js";
25
+ } from "./index-xdsp97e4.js";
26
26
  import {
27
27
  LAYOUTS,
28
28
  readManifest,
29
29
  validate,
30
30
  zoneOf
31
- } from "./index-0vt6g4cb.js";
31
+ } from "./index-zh3tmj08.js";
32
32
  import {
33
33
  GROUPS
34
- } from "./index-yk361vn2.js";
34
+ } from "./index-z4qazajn.js";
35
35
 
36
36
  // src/deploy/index.ts
37
37
  import color from "picocolors";
@@ -20,4 +20,17 @@ export declare function renderNotificationsTs(): string;
20
20
  * because the choice lives on the line, not in the catalogue.
21
21
  */
22
22
  export declare function renderMintTs(): string;
23
+ /**
24
+ * Where parcels leave from. Written once, then yours.
25
+ *
26
+ * Every carrier needs an origin to quote against, and it is shop data rather
27
+ * than a credential — a postcode changes when you move, not when a key
28
+ * rotates. So it is a file you edit, not an env var, and it ships with your
29
+ * own details already in the shape the carriers want.
30
+ *
31
+ * DHL in particular rejects a shipment whose origin has no name and phone,
32
+ * which is a failure that shows up at the first real order rather than at
33
+ * deploy, so both are marked rather than left blank.
34
+ */
35
+ export declare function renderShippingTs(manifest: Manifest): string;
23
36
  export declare function renderErpTs(): string;
@@ -11,11 +11,11 @@ import {
11
11
  workerHosts,
12
12
  writeManifest,
13
13
  zone
14
- } from "./index-0vt6g4cb.js";
14
+ } from "./index-zh3tmj08.js";
15
15
  import {
16
16
  CHOICES,
17
17
  __require
18
- } from "./index-yk361vn2.js";
18
+ } from "./index-z4qazajn.js";
19
19
 
20
20
  // src/generate/auth.ts
21
21
  var VAT = {
@@ -41,14 +41,29 @@ function renderAuthTs(manifest) {
41
41
  const CARRIER_CALLS = {
42
42
  "dhl-express": {
43
43
  label: "DHL Express",
44
- call: `dhlExpressProvider({ apiKey: must(env, "DHL_API_KEY") })`,
44
+ call: `dhlExpressProvider({ apiKey: must(env, "DHL_API_KEY"), apiSecret: must(env, "DHL_API_SECRET"), accountNumber: must(env, "DHL_ACCOUNT_NUMBER"), from: shipFrom })`,
45
45
  import: `import { dhlExpressProvider } from "@saastemly/better-commerce/providers/dhl-express";`
46
46
  },
47
- shippo: { label: "Shippo", call: `shippoProvider({ apiKey: must(env, "SHIPPO_API_KEY") })`, import: `import { shippoProvider } from "@saastemly/better-commerce/providers/shippo";` },
48
- easypost: { label: "EasyPost", call: `easypostProvider({ apiKey: must(env, "EASYPOST_API_KEY") })`, import: `import { easypostProvider } from "@saastemly/better-commerce/providers/easypost";` },
49
- ups: { label: "UPS", call: `upsProvider({ clientId: must(env, "UPS_CLIENT_ID"), clientSecret: must(env, "UPS_CLIENT_SECRET") })`, import: `import { upsProvider } from "@saastemly/better-commerce/providers/ups";` },
50
- fedex: { label: "FedEx", call: `fedexProvider({ clientId: must(env, "FEDEX_CLIENT_ID"), clientSecret: must(env, "FEDEX_CLIENT_SECRET") })`, import: `import { fedexProvider } from "@saastemly/better-commerce/providers/fedex";` },
51
- "royal-mail": { label: "Royal Mail", call: `royalMailProvider({ clientId: must(env, "ROYAL_MAIL_CLIENT_ID") })`, import: `import { royalMailProvider } from "@saastemly/better-commerce/providers/royal-mail";` },
47
+ shippo: {
48
+ label: "Shippo",
49
+ call: `shippoProvider({ apiToken: must(env, "SHIPPO_API_TOKEN"), from: shipFrom })`,
50
+ import: `import { shippoProvider } from "@saastemly/better-commerce/providers/shippo";`
51
+ },
52
+ easypost: {
53
+ label: "EasyPost",
54
+ call: `easypostProvider({ apiKey: must(env, "EASYPOST_API_KEY"), from: shipFrom })`,
55
+ import: `import { easypostProvider } from "@saastemly/better-commerce/providers/easypost";`
56
+ },
57
+ ups: {
58
+ label: "UPS",
59
+ call: `upsProvider({ clientId: must(env, "UPS_CLIENT_ID"), clientSecret: must(env, "UPS_CLIENT_SECRET"), accountNumber: must(env, "UPS_ACCOUNT_NUMBER"), from: shipFrom })`,
60
+ import: `import { upsProvider } from "@saastemly/better-commerce/providers/ups";`
61
+ },
62
+ fedex: {
63
+ label: "FedEx",
64
+ call: `fedexProvider({ clientId: must(env, "FEDEX_CLIENT_ID"), clientSecret: must(env, "FEDEX_CLIENT_SECRET"), accountNumber: must(env, "FEDEX_ACCOUNT_NUMBER"), from: shipFrom })`,
65
+ import: `import { fedexProvider } from "@saastemly/better-commerce/providers/fedex";`
66
+ },
52
67
  nft: {
53
68
  label: "A token, not a parcel",
54
69
  call: `nftDelivery({ mint: mintProvider, destinationFor })`,
@@ -58,6 +73,8 @@ import { mintProvider, destinationFor } from "./lib/mint.ts";`
58
73
  };
59
74
  const carriers = Object.keys(CARRIER_CALLS).filter((id) => has(manifest, id));
60
75
  const carrierImports = carriers.map((id) => CARRIER_CALLS[id].import);
76
+ if (carriers.some((id) => id !== "nft"))
77
+ carrierImports.push(`import { shipFrom } from "./lib/shipping.ts";`);
61
78
  const wantsNft = has(manifest, "nft");
62
79
  const carrierFulfillment = carriers.length ? `, deliveryFulfillment([${carriers.map((id) => CARRIER_CALLS[id].call).join(", ")}])` : "";
63
80
  const nftDefault = carriers.length ? `, defaultProvider: "delivery:${carriers.join("+")}"` : "";
@@ -1419,7 +1436,7 @@ import color from "picocolors";
1419
1436
  // package.json
1420
1437
  var package_default = {
1421
1438
  name: "@saastemly/voidcommerce",
1422
- version: "0.2.0",
1439
+ version: "0.2.1",
1423
1440
  description: "Void, with a shop in it. `vc init` walks you through Better Auth, betterCommerce and every plugin; everything else passes through to `void`.",
1424
1441
  type: "module",
1425
1442
  license: "MIT",
@@ -2197,6 +2214,25 @@ export async function destinationFor(orderId: string): Promise<string | null> {
2197
2214
  }
2198
2215
  `;
2199
2216
  }
2217
+ function renderShippingTs(manifest) {
2218
+ return `import type { DeliveryAddress } from "@saastemly/better-commerce/providers/delivery";
2219
+
2220
+ /**
2221
+ * The address every parcel is quoted and shipped FROM.
2222
+ *
2223
+ * TODO VERIFY: this is a placeholder. A carrier quotes against it, and DHL
2224
+ * refuses a shipment whose origin has no \`name\` and \`phone\`.
2225
+ */
2226
+ export const shipFrom: DeliveryAddress = {
2227
+ name: "${manifest.shop.name}",
2228
+ line1: "TODO VERIFY: street",
2229
+ city: "TODO VERIFY: town",
2230
+ postalCode: "TODO VERIFY",
2231
+ country: "${manifest.shop.country}",
2232
+ phone: "TODO VERIFY: +44…",
2233
+ };
2234
+ `;
2235
+ }
2200
2236
  function renderErpTs() {
2201
2237
  return `import { businessCentralProvider } from "@saastemly/better-commerce/providers/business-central";
2202
2238
  import type { FulfillmentProvider } from "@saastemly/better-commerce/plugins/fulfillments";
@@ -2305,6 +2341,9 @@ async function generateApi(root, dir, manifest, result, opts) {
2305
2341
  if (has(manifest, "nft")) {
2306
2342
  await put(root, at("lib/mint.ts"), renderMintTs(), result, own);
2307
2343
  }
2344
+ if (["dhl-express", "shippo", "easypost", "ups", "fedex"].some((id) => has(manifest, id))) {
2345
+ await put(root, at("lib/shipping.ts"), renderShippingTs(manifest), result, own);
2346
+ }
2308
2347
  if (has(manifest, "business-central")) {
2309
2348
  await put(root, at("lib/erp.ts"), renderErpTs(), result, own);
2310
2349
  }
@@ -443,11 +443,11 @@ var CARRIERS = {
443
443
  intro: "Live rates at checkout, quoted per carrier. Flat-rate shipping needs none of these.",
444
444
  kind: "multiselect",
445
445
  choices: [
446
- { id: "shippo", label: "Shippo", hint: "many carriers through one API", env: [{ key: "SHIPPO_API_KEY", breaks: "calculated rates fall back to flat" }] },
446
+ { id: "shippo", label: "Shippo", hint: "many carriers through one API", env: [{ key: "SHIPPO_API_TOKEN", breaks: "calculated rates fall back to flat" }] },
447
447
  { id: "easypost", label: "EasyPost", hint: "the same, US-leaning", env: [{ key: "EASYPOST_API_KEY", breaks: "calculated rates fall back to flat" }] },
448
- { id: "ups", label: "UPS", hint: "your own UPS account, for negotiated rates an aggregator cannot see", env: [{ key: "UPS_CLIENT_ID", breaks: "UPS rates are not quoted" }, { key: "UPS_CLIENT_SECRET", breaks: "the same" }] },
449
- { 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" }] },
450
- { id: "dhl-express", label: "DHL Express", hint: "international", env: [{ key: "DHL_API_KEY", breaks: "DHL rates are not quoted" }] },
448
+ { id: "ups", label: "UPS", hint: "your own UPS account, for negotiated rates an aggregator cannot see", env: [{ key: "UPS_CLIENT_ID", breaks: "UPS rates are not quoted" }, { key: "UPS_CLIENT_SECRET", breaks: "the same" }, { key: "UPS_ACCOUNT_NUMBER", breaks: "UPS has no account to bill the transport to" }] },
449
+ { 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" }] },
450
+ { 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" }] },
451
451
  { 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" }] },
452
452
  { id: "royal-mail", label: "Royal Mail", hint: "UK post, for a shop whose customers are mostly in Britain", env: [{ key: "ROYAL_MAIL_CLIENT_ID", breaks: "Royal Mail rates are not quoted" }] },
453
453
  { 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" }] },
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CHOICES,
3
3
  GROUPS
4
- } from "./index-yk361vn2.js";
4
+ } from "./index-z4qazajn.js";
5
5
 
6
6
  // src/manifest.ts
7
7
  import { readFile, writeFile } from "node:fs/promises";
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  routeProblem,
58
58
  strictDependencies,
59
59
  upsertJsonc
60
- } from "./index-9x9q9ng2.js";
60
+ } from "./index-xdsp97e4.js";
61
61
  import {
62
62
  LAYOUTS,
63
63
  MANIFEST_FILE,
@@ -77,7 +77,7 @@ import {
77
77
  writeManifest,
78
78
  zone,
79
79
  zoneOf
80
- } from "./index-0vt6g4cb.js";
80
+ } from "./index-zh3tmj08.js";
81
81
  import {
82
82
  AUTH_PLUGINS,
83
83
  CARRIERS,
@@ -94,7 +94,7 @@ import {
94
94
  SIGN_IN,
95
95
  TAX,
96
96
  UI
97
- } from "./index-yk361vn2.js";
97
+ } from "./index-z4qazajn.js";
98
98
  export {
99
99
  zoneOf,
100
100
  zone,
package/dist/manifest.js CHANGED
@@ -17,8 +17,8 @@ import {
17
17
  writeManifest,
18
18
  zone,
19
19
  zoneOf
20
- } from "./index-0vt6g4cb.js";
21
- import"./index-yk361vn2.js";
20
+ } from "./index-zh3tmj08.js";
21
+ import"./index-z4qazajn.js";
22
22
  export {
23
23
  zoneOf,
24
24
  zone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saastemly/voidcommerce",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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
@@ -513,11 +513,11 @@ export const CARRIERS: Group = {
513
513
  intro: "Live rates at checkout, quoted per carrier. Flat-rate shipping needs none of these.",
514
514
  kind: "multiselect",
515
515
  choices: [
516
- { id: "shippo", label: "Shippo", hint: "many carriers through one API", env: [{ key: "SHIPPO_API_KEY", breaks: "calculated rates fall back to flat" }] },
516
+ { id: "shippo", label: "Shippo", hint: "many carriers through one API", env: [{ key: "SHIPPO_API_TOKEN", breaks: "calculated rates fall back to flat" }] },
517
517
  { id: "easypost", label: "EasyPost", hint: "the same, US-leaning", env: [{ key: "EASYPOST_API_KEY", breaks: "calculated rates fall back to flat" }] },
518
- { id: "ups", label: "UPS", hint: "your own UPS account, for negotiated rates an aggregator cannot see", env: [{ key: "UPS_CLIENT_ID", breaks: "UPS rates are not quoted" }, { key: "UPS_CLIENT_SECRET", breaks: "the same" }] },
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" }] },
520
- { id: "dhl-express", label: "DHL Express", hint: "international", env: [{ key: "DHL_API_KEY", breaks: "DHL rates are not quoted" }] },
518
+ { id: "ups", label: "UPS", hint: "your own UPS account, for negotiated rates an aggregator cannot see", env: [{ key: "UPS_CLIENT_ID", breaks: "UPS rates are not quoted" }, { key: "UPS_CLIENT_SECRET", breaks: "the same" }, { key: "UPS_ACCOUNT_NUMBER", breaks: "UPS has no account to bill the transport to" }] },
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
+ { 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
  { id: "royal-mail", label: "Royal Mail", hint: "UK post, for a shop whose customers are mostly in Britain", env: [{ key: "ROYAL_MAIL_CLIENT_ID", breaks: "Royal Mail rates are not quoted" }] },
523
523
  { 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" }] },
@@ -66,16 +66,33 @@ export function renderAuthTs(manifest: Manifest): string {
66
66
  * follows — including the token, which quotes zero and ships nothing.
67
67
  */
68
68
  const CARRIER_CALLS: Record<string, { call: string; label: string; import: string }> = {
69
+ // Every real carrier needs the ORIGIN it ships from, which is shop data
70
+ // rather than a credential — so it lives in an owned lib/shipping.ts.
69
71
  "dhl-express": {
70
72
  label: "DHL Express",
71
- call: `dhlExpressProvider({ apiKey: must(env, "DHL_API_KEY") })`,
73
+ call: `dhlExpressProvider({ apiKey: must(env, "DHL_API_KEY"), apiSecret: must(env, "DHL_API_SECRET"), accountNumber: must(env, "DHL_ACCOUNT_NUMBER"), from: shipFrom })`,
72
74
  import: `import { dhlExpressProvider } from "@saastemly/better-commerce/providers/dhl-express";`,
73
75
  },
74
- shippo: { label: "Shippo", call: `shippoProvider({ apiKey: must(env, "SHIPPO_API_KEY") })`, import: `import { shippoProvider } from "@saastemly/better-commerce/providers/shippo";` },
75
- easypost: { label: "EasyPost", call: `easypostProvider({ apiKey: must(env, "EASYPOST_API_KEY") })`, import: `import { easypostProvider } from "@saastemly/better-commerce/providers/easypost";` },
76
- ups: { label: "UPS", call: `upsProvider({ clientId: must(env, "UPS_CLIENT_ID"), clientSecret: must(env, "UPS_CLIENT_SECRET") })`, import: `import { upsProvider } from "@saastemly/better-commerce/providers/ups";` },
77
- fedex: { label: "FedEx", call: `fedexProvider({ clientId: must(env, "FEDEX_CLIENT_ID"), clientSecret: must(env, "FEDEX_CLIENT_SECRET") })`, import: `import { fedexProvider } from "@saastemly/better-commerce/providers/fedex";` },
78
- "royal-mail": { label: "Royal Mail", call: `royalMailProvider({ clientId: must(env, "ROYAL_MAIL_CLIENT_ID") })`, import: `import { royalMailProvider } from "@saastemly/better-commerce/providers/royal-mail";` },
76
+ shippo: {
77
+ label: "Shippo",
78
+ call: `shippoProvider({ apiToken: must(env, "SHIPPO_API_TOKEN"), from: shipFrom })`,
79
+ import: `import { shippoProvider } from "@saastemly/better-commerce/providers/shippo";`,
80
+ },
81
+ easypost: {
82
+ label: "EasyPost",
83
+ call: `easypostProvider({ apiKey: must(env, "EASYPOST_API_KEY"), from: shipFrom })`,
84
+ import: `import { easypostProvider } from "@saastemly/better-commerce/providers/easypost";`,
85
+ },
86
+ ups: {
87
+ label: "UPS",
88
+ call: `upsProvider({ clientId: must(env, "UPS_CLIENT_ID"), clientSecret: must(env, "UPS_CLIENT_SECRET"), accountNumber: must(env, "UPS_ACCOUNT_NUMBER"), from: shipFrom })`,
89
+ import: `import { upsProvider } from "@saastemly/better-commerce/providers/ups";`,
90
+ },
91
+ fedex: {
92
+ label: "FedEx",
93
+ call: `fedexProvider({ clientId: must(env, "FEDEX_CLIENT_ID"), clientSecret: must(env, "FEDEX_CLIENT_SECRET"), accountNumber: must(env, "FEDEX_ACCOUNT_NUMBER"), from: shipFrom })`,
94
+ import: `import { fedexProvider } from "@saastemly/better-commerce/providers/fedex";`,
95
+ },
79
96
  nft: {
80
97
  label: "A token, not a parcel",
81
98
  call: `nftDelivery({ mint: mintProvider, destinationFor })`,
@@ -84,6 +101,8 @@ export function renderAuthTs(manifest: Manifest): string {
84
101
  };
85
102
  const carriers = Object.keys(CARRIER_CALLS).filter((id) => has(manifest, id));
86
103
  const carrierImports = carriers.map((id) => CARRIER_CALLS[id]!.import);
104
+ // The token needs no origin; every other carrier does.
105
+ if (carriers.some((id) => id !== "nft")) carrierImports.push(`import { shipFrom } from "./lib/shipping.ts";`);
87
106
  const wantsNft = has(manifest, "nft");
88
107
  // One bridge for every carrier: which ships is decided by whose rate the
89
108
  // buyer paid for, so a token order mints and a parcel order posts.
@@ -50,7 +50,7 @@ import {
50
50
  renderVoidPatch,
51
51
  strictDependencies,
52
52
  } from "./strict";
53
- import { renderDomainTs, renderErpTs, renderMintTs, renderNotificationsTs, renderPaymentTs } from "./support";
53
+ import { renderDomainTs, renderErpTs, renderMintTs, renderNotificationsTs, renderPaymentTs, renderShippingTs } from "./support";
54
54
 
55
55
  /**
56
56
  * Turn a manifest into files.
@@ -173,6 +173,10 @@ async function generateApi(root: string, dir: string, manifest: Manifest, result
173
173
  if (has(manifest, "nft")) {
174
174
  await put(root, at("lib/mint.ts"), renderMintTs(), result, own);
175
175
  }
176
+ // Any carrier but the token quotes against an origin, which is shop data.
177
+ if (["dhl-express", "shippo", "easypost", "ups", "fedex"].some((id) => has(manifest, id))) {
178
+ await put(root, at("lib/shipping.ts"), renderShippingTs(manifest), result, own);
179
+ }
176
180
  if (has(manifest, "business-central")) {
177
181
  await put(root, at("lib/erp.ts"), renderErpTs(), result, own);
178
182
  }
@@ -258,6 +258,38 @@ export async function destinationFor(orderId: string): Promise<string | null> {
258
258
  `;
259
259
  }
260
260
 
261
+ /**
262
+ * Where parcels leave from. Written once, then yours.
263
+ *
264
+ * Every carrier needs an origin to quote against, and it is shop data rather
265
+ * than a credential — a postcode changes when you move, not when a key
266
+ * rotates. So it is a file you edit, not an env var, and it ships with your
267
+ * own details already in the shape the carriers want.
268
+ *
269
+ * DHL in particular rejects a shipment whose origin has no name and phone,
270
+ * which is a failure that shows up at the first real order rather than at
271
+ * deploy, so both are marked rather than left blank.
272
+ */
273
+ export function renderShippingTs(manifest: Manifest): string {
274
+ return `import type { DeliveryAddress } from "@saastemly/better-commerce/providers/delivery";
275
+
276
+ /**
277
+ * The address every parcel is quoted and shipped FROM.
278
+ *
279
+ * TODO VERIFY: this is a placeholder. A carrier quotes against it, and DHL
280
+ * refuses a shipment whose origin has no \`name\` and \`phone\`.
281
+ */
282
+ export const shipFrom: DeliveryAddress = {
283
+ name: "${manifest.shop.name}",
284
+ line1: "TODO VERIFY: street",
285
+ city: "TODO VERIFY: town",
286
+ postalCode: "TODO VERIFY",
287
+ country: "${manifest.shop.country}",
288
+ phone: "TODO VERIFY: +44…",
289
+ };
290
+ `;
291
+ }
292
+
261
293
  export function renderErpTs(): string {
262
294
  return `import { businessCentralProvider } from "@saastemly/better-commerce/providers/business-central";
263
295
  import type { FulfillmentProvider } from "@saastemly/better-commerce/plugins/fulfillments";