@jytextiles/medusa-plugin-faire-store-sync 0.1.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.
Files changed (104) hide show
  1. package/.medusa/server/src/__tests__/ingest-faire-order-support.spec.js +74 -0
  2. package/.medusa/server/src/__tests__/webhook.spec.js +77 -0
  3. package/.medusa/server/src/admin/index.js +960 -0
  4. package/.medusa/server/src/admin/index.mjs +959 -0
  5. package/.medusa/server/src/api/admin/faire/auth/authorize/route.js +12 -0
  6. package/.medusa/server/src/api/admin/faire/auth/callback/route.js +25 -0
  7. package/.medusa/server/src/api/admin/faire/auth/disconnect/route.js +12 -0
  8. package/.medusa/server/src/api/admin/faire/brand/route.js +14 -0
  9. package/.medusa/server/src/api/admin/faire/ingest/orders/[batchId]/route.js +27 -0
  10. package/.medusa/server/src/api/admin/faire/ingest/orders/route.js +20 -0
  11. package/.medusa/server/src/api/admin/faire/products/route.js +16 -0
  12. package/.medusa/server/src/api/admin/faire/settings/route.js +19 -0
  13. package/.medusa/server/src/api/admin/faire/status/product/[id]/route.js +30 -0
  14. package/.medusa/server/src/api/admin/faire/status/route.js +46 -0
  15. package/.medusa/server/src/api/admin/faire/sync/bulk/[batchId]/route.js +29 -0
  16. package/.medusa/server/src/api/admin/faire/sync/bulk/route.js +20 -0
  17. package/.medusa/server/src/api/admin/faire/sync/product/[id]/route.js +14 -0
  18. package/.medusa/server/src/api/admin/faire/syncs/[id]/route.js +32 -0
  19. package/.medusa/server/src/api/admin/faire/syncs/route.js +18 -0
  20. package/.medusa/server/src/api/webhooks/faire/route.js +125 -0
  21. package/.medusa/server/src/jobs/pull-faire-orders.js +32 -0
  22. package/.medusa/server/src/jobs/refresh-faire-token.js +25 -0
  23. package/.medusa/server/src/lib/faire-client.js +321 -0
  24. package/.medusa/server/src/lib/types.js +10 -0
  25. package/.medusa/server/src/lib/webhook.js +58 -0
  26. package/.medusa/server/src/links/product-faire-sync-link.js +57 -0
  27. package/.medusa/server/src/modules/faire-sync/index.js +15 -0
  28. package/.medusa/server/src/modules/faire-sync/migrations/Migration20260707211346.js +34 -0
  29. package/.medusa/server/src/modules/faire-sync/models/faire-order.js +21 -0
  30. package/.medusa/server/src/modules/faire-sync/models/faire-sync-account.js +17 -0
  31. package/.medusa/server/src/modules/faire-sync/models/faire-sync-batch.js +18 -0
  32. package/.medusa/server/src/modules/faire-sync/models/faire-sync-record.js +22 -0
  33. package/.medusa/server/src/modules/faire-sync/models/faire-sync-settings.js +26 -0
  34. package/.medusa/server/src/modules/faire-sync/models/faire-webhook-event.js +21 -0
  35. package/.medusa/server/src/modules/faire-sync/service.js +222 -0
  36. package/.medusa/server/src/subscribers/faire-inventory-sync.js +87 -0
  37. package/.medusa/server/src/subscribers/faire-listing-events.js +46 -0
  38. package/.medusa/server/src/subscribers/faire-order-ingest.js +54 -0
  39. package/.medusa/server/src/subscribers/faire-order-lifecycle.js +71 -0
  40. package/.medusa/server/src/subscribers/faire-product-status-sync.js +71 -0
  41. package/.medusa/server/src/workflows/index.js +21 -0
  42. package/.medusa/server/src/workflows/ingest-faire-order-support.js +76 -0
  43. package/.medusa/server/src/workflows/ingest-faire-order.js +148 -0
  44. package/.medusa/server/src/workflows/ingest-faire-orders-bulk.js +124 -0
  45. package/.medusa/server/src/workflows/refresh-faire-token.js +20 -0
  46. package/.medusa/server/src/workflows/sync-product-to-faire.js +262 -0
  47. package/.medusa/server/src/workflows/sync-products-to-faire.js +90 -0
  48. package/LICENSE +21 -0
  49. package/README.md +180 -0
  50. package/package.json +84 -0
  51. package/scripts/postbuild.js +36 -0
  52. package/src/__tests__/ingest-faire-order-support.spec.ts +77 -0
  53. package/src/__tests__/webhook.spec.ts +81 -0
  54. package/src/admin/lib/api.ts +88 -0
  55. package/src/admin/routes/settings/faire/[id]/page.tsx +184 -0
  56. package/src/admin/routes/settings/faire/hooks/use-faire-sync-columns.tsx +80 -0
  57. package/src/admin/routes/settings/faire/page.tsx +445 -0
  58. package/src/admin/routes/settings/faire/settings/page.tsx +286 -0
  59. package/src/admin/routes/settings/oauth/faire/callback/page.tsx +67 -0
  60. package/src/admin/widgets/faire-product-widget.tsx +180 -0
  61. package/src/api/admin/faire/auth/authorize/route.ts +10 -0
  62. package/src/api/admin/faire/auth/callback/route.ts +29 -0
  63. package/src/api/admin/faire/auth/disconnect/route.ts +10 -0
  64. package/src/api/admin/faire/brand/route.ts +12 -0
  65. package/src/api/admin/faire/ingest/orders/[batchId]/route.ts +31 -0
  66. package/src/api/admin/faire/ingest/orders/route.ts +29 -0
  67. package/src/api/admin/faire/products/route.ts +14 -0
  68. package/src/api/admin/faire/settings/route.ts +17 -0
  69. package/src/api/admin/faire/status/product/[id]/route.ts +30 -0
  70. package/src/api/admin/faire/status/route.ts +47 -0
  71. package/src/api/admin/faire/sync/bulk/[batchId]/route.ts +33 -0
  72. package/src/api/admin/faire/sync/bulk/route.ts +27 -0
  73. package/src/api/admin/faire/sync/product/[id]/route.ts +13 -0
  74. package/src/api/admin/faire/syncs/[id]/route.ts +32 -0
  75. package/src/api/admin/faire/syncs/route.ts +17 -0
  76. package/src/api/webhooks/faire/route.ts +133 -0
  77. package/src/jobs/pull-faire-orders.ts +30 -0
  78. package/src/jobs/refresh-faire-token.ts +23 -0
  79. package/src/lib/faire-client.ts +436 -0
  80. package/src/lib/types.ts +117 -0
  81. package/src/lib/webhook.ts +85 -0
  82. package/src/links/product-faire-sync-link.ts +56 -0
  83. package/src/modules/faire-sync/index.ts +11 -0
  84. package/src/modules/faire-sync/migrations/.snapshot-medusa-faire-sync.json +1486 -0
  85. package/src/modules/faire-sync/migrations/Migration20260707211346.ts +43 -0
  86. package/src/modules/faire-sync/models/faire-order.ts +20 -0
  87. package/src/modules/faire-sync/models/faire-sync-account.ts +16 -0
  88. package/src/modules/faire-sync/models/faire-sync-batch.ts +17 -0
  89. package/src/modules/faire-sync/models/faire-sync-record.ts +21 -0
  90. package/src/modules/faire-sync/models/faire-sync-settings.ts +25 -0
  91. package/src/modules/faire-sync/models/faire-webhook-event.ts +20 -0
  92. package/src/modules/faire-sync/service.ts +252 -0
  93. package/src/subscribers/faire-inventory-sync.ts +97 -0
  94. package/src/subscribers/faire-listing-events.ts +48 -0
  95. package/src/subscribers/faire-order-ingest.ts +61 -0
  96. package/src/subscribers/faire-order-lifecycle.ts +81 -0
  97. package/src/subscribers/faire-product-status-sync.ts +76 -0
  98. package/src/workflows/index.ts +4 -0
  99. package/src/workflows/ingest-faire-order-support.ts +96 -0
  100. package/src/workflows/ingest-faire-order.ts +212 -0
  101. package/src/workflows/ingest-faire-orders-bulk.ts +168 -0
  102. package/src/workflows/refresh-faire-token.ts +28 -0
  103. package/src/workflows/sync-product-to-faire.ts +375 -0
  104. package/src/workflows/sync-products-to-faire.ts +133 -0
@@ -0,0 +1,17 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { FAIRE_SYNC_MODULE } from "../../../../modules/faire-sync"
3
+ import FaireSyncService from "../../../../modules/faire-sync/service"
4
+
5
+ // GET /admin/faire/settings
6
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
7
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
8
+ const settings = await service.getSettings()
9
+ res.json({ settings })
10
+ }
11
+
12
+ // POST /admin/faire/settings
13
+ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
14
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
15
+ const settings = await service.updateSettings(req.body || {})
16
+ res.json({ settings })
17
+ }
@@ -0,0 +1,30 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { FAIRE_SYNC_MODULE } from "../../../../../../modules/faire-sync"
3
+ import FaireSyncService from "../../../../../../modules/faire-sync/service"
4
+
5
+ // GET /admin/faire/status/product/:id — latest sync state for a single product.
6
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
7
+ const { id } = req.params
8
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
9
+
10
+ const connected = !!(await service.getActiveAccount())
11
+ const [records] = await service.listSyncRecords({ product_id: id }, 1, 0)
12
+ const latest: any = records?.[0] ?? null
13
+
14
+ res.json({
15
+ connected,
16
+ synced: !!latest,
17
+ latest: latest
18
+ ? {
19
+ id: latest.id,
20
+ status: latest.status,
21
+ product_token: latest.product_token,
22
+ product_url: latest.product_url,
23
+ product_state: latest.product_state,
24
+ published: latest.published,
25
+ error_message: latest.error_message,
26
+ synced_at: latest.synced_at,
27
+ }
28
+ : null,
29
+ })
30
+ }
@@ -0,0 +1,47 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { FAIRE_SYNC_MODULE } from "../../../../modules/faire-sync"
3
+ import FaireSyncService from "../../../../modules/faire-sync/service"
4
+
5
+ // GET /admin/faire/status — connection status + readiness checklist
6
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
7
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
8
+ const account = await service.getActiveAccount()
9
+ const settings = await service.getSettings()
10
+
11
+ const connected = !!account && account.is_active
12
+ const hasBrand = !!settings.default_brand_id
13
+ const hasMarkup =
14
+ settings.default_wholesale_markup_percent != null &&
15
+ settings.default_wholesale_markup_percent > 0
16
+ const hasShipping = !!settings.default_shipping_policy_id
17
+
18
+ res.json({
19
+ connected,
20
+ account: account
21
+ ? {
22
+ id: account.id,
23
+ brand_id: account.brand_id,
24
+ brand_name: account.brand_name,
25
+ currency: account.currency,
26
+ token_expires_at: account.token_expires_at,
27
+ }
28
+ : null,
29
+ settings: {
30
+ default_brand_id: settings.default_brand_id,
31
+ default_wholesale_markup_percent: settings.default_wholesale_markup_percent,
32
+ default_min_order_quantity: settings.default_min_order_quantity,
33
+ default_lead_time_days: settings.default_lead_time_days,
34
+ default_shipping_policy_id: settings.default_shipping_policy_id,
35
+ default_category: settings.default_category,
36
+ auto_publish: settings.auto_publish,
37
+ follow_product_status: settings.follow_product_status,
38
+ },
39
+ readiness: {
40
+ connected,
41
+ brand: hasBrand,
42
+ wholesale_pricing: hasMarkup,
43
+ shipping_policy: hasShipping,
44
+ ready_to_publish: connected && hasBrand,
45
+ },
46
+ })
47
+ }
@@ -0,0 +1,33 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { MedusaError } from "@medusajs/framework/utils"
3
+ import { FAIRE_SYNC_MODULE } from "../../../../../../modules/faire-sync"
4
+ import FaireSyncService from "../../../../../../modules/faire-sync/service"
5
+
6
+ // GET /admin/faire/sync/bulk/:batchId — poll the progress of a background batch
7
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
8
+ const { batchId } = req.params
9
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
10
+
11
+ const batch = await service
12
+ .retrieveFaireSyncBatch(batchId)
13
+ .catch(() => null)
14
+
15
+ if (!batch) {
16
+ throw new MedusaError(MedusaError.Types.NOT_FOUND, "Batch not found")
17
+ }
18
+
19
+ const total = (batch as any).total_products || 0
20
+ const done =
21
+ ((batch as any).synced_count || 0) + ((batch as any).failed_count || 0)
22
+
23
+ res.json({
24
+ batch,
25
+ progress: {
26
+ total,
27
+ done,
28
+ pct: total ? Math.round((done / total) * 100) : 0,
29
+ finished:
30
+ (batch as any).status === "completed" || (batch as any).status === "failed",
31
+ },
32
+ })
33
+ }
@@ -0,0 +1,27 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { MedusaError } from "@medusajs/framework/utils"
3
+ import { syncProductsToFaireWorkflow } from "../../../../../workflows/sync-products-to-faire"
4
+
5
+ type BulkSyncBody = {
6
+ product_ids?: string[]
7
+ }
8
+
9
+ // POST /admin/faire/sync/bulk — sync many products (long-running background workflow)
10
+ export const POST = async (req: MedusaRequest<BulkSyncBody>, res: MedusaResponse) => {
11
+ const { product_ids } = req.body || {}
12
+
13
+ if (!product_ids?.length) {
14
+ throw new MedusaError(
15
+ MedusaError.Types.INVALID_DATA,
16
+ "product_ids array is required"
17
+ )
18
+ }
19
+
20
+ const { result } = await syncProductsToFaireWorkflow(req.scope).run({
21
+ input: { product_ids },
22
+ })
23
+
24
+ // The workflow runs the per-product sync in the background; we return the
25
+ // batch id immediately so the client can poll GET /admin/faire/sync/bulk/:id.
26
+ res.status(202).json({ batch_id: (result as any).batch_id, status: "processing" })
27
+ }
@@ -0,0 +1,13 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { syncProductToFaireWorkflow } from "../../../../../../workflows/sync-product-to-faire"
3
+
4
+ // POST /admin/faire/sync/product/:id — sync a single product to Faire
5
+ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
6
+ const { id } = req.params
7
+
8
+ const { result } = await syncProductToFaireWorkflow(req.scope).run({
9
+ input: { product_id: id },
10
+ })
11
+
12
+ res.json({ result })
13
+ }
@@ -0,0 +1,32 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { MedusaError } from "@medusajs/framework/utils"
3
+ import { FAIRE_SYNC_MODULE } from "../../../../../modules/faire-sync"
4
+ import FaireSyncService from "../../../../../modules/faire-sync/service"
5
+ import { syncProductToFaireWorkflow } from "../../../../../workflows/sync-product-to-faire"
6
+
7
+ // GET /admin/faire/syncs/:id — fetch a single sync record
8
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
9
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
10
+ const { id } = req.params
11
+ const [record] = await service.listFaireSyncRecords({ id } as any, { take: 1 } as any)
12
+ if (!record) {
13
+ throw new MedusaError(MedusaError.Types.NOT_FOUND, "Sync record not found")
14
+ }
15
+ res.json({ sync: record })
16
+ }
17
+
18
+ // POST /admin/faire/syncs/:id/retry — re-sync the product referenced by this record
19
+ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
20
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
21
+ const { id } = req.params
22
+ const [record] = await service.listFaireSyncRecords({ id } as any, { take: 1 } as any)
23
+ if (!record) {
24
+ throw new MedusaError(MedusaError.Types.NOT_FOUND, "Sync record not found")
25
+ }
26
+
27
+ const { result } = await syncProductToFaireWorkflow(req.scope).run({
28
+ input: { product_id: (record as any).product_id },
29
+ })
30
+
31
+ res.json({ result })
32
+ }
@@ -0,0 +1,17 @@
1
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import { FAIRE_SYNC_MODULE } from "../../../../modules/faire-sync"
3
+ import FaireSyncService from "../../../../modules/faire-sync/service"
4
+
5
+ // GET /admin/faire/syncs — list recent sync records (paginated)
6
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
7
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
8
+ const take = Number(req.query.take) || 20
9
+ const skip = Number(req.query.skip) || 0
10
+ const status = req.query.status as string | undefined
11
+
12
+ const filters: any = {}
13
+ if (status) filters.status = status
14
+
15
+ const [records, count] = await service.listSyncRecords(filters, take, skip)
16
+ res.json({ syncs: records, count, take, skip })
17
+ }
@@ -0,0 +1,133 @@
1
+ import crypto from "crypto"
2
+ import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
3
+ import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
4
+ import { FAIRE_SYNC_MODULE } from "../../../modules/faire-sync"
5
+ import FaireSyncService from "../../../modules/faire-sync/service"
6
+ import { verifyFaireWebhook } from "../../../lib/webhook"
7
+
8
+ /**
9
+ * POST /webhooks/faire — inbound Faire webhook receiver.
10
+ *
11
+ * Faire signs each delivery with an HMAC-SHA256 of the raw body using the
12
+ * webhook secret shared at registration time, sent in the
13
+ * `X-Faire-Webhook-Signature` header. This route:
14
+ * 1. verifies the signature over the raw body,
15
+ * 2. records the delivery idempotently (unique delivery id),
16
+ * 3. best-effort fetches the resource referenced and re-emits a Medusa event
17
+ * `faire.<event_type>` so subscribers / visual flows can act on it,
18
+ * 4. always returns 200 once verified+recorded (Faire retries on non-2xx).
19
+ *
20
+ * Raw body is available because the host's middlewares.ts sets preserveRawBody
21
+ * for this path. Register the endpoint URL + copy the signing secret
22
+ * (FAIRE_WEBHOOK_SECRET) when you create the webhook via the Faire API.
23
+ *
24
+ * Faire emits events such as ORDER_PLACED, ORDER_CANCELED, ORDER_SHIPPED,
25
+ * INVENTORY_UPDATED, PRODUCT_UPDATED (verify against your Faire app's
26
+ * configuration). We normalize the event_type to lowercase for the emitted
27
+ * Medusa event name.
28
+ */
29
+ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
30
+ const logger = req.scope.resolve(ContainerRegistrationKeys.LOGGER) as any
31
+ const service: FaireSyncService = req.scope.resolve(FAIRE_SYNC_MODULE)
32
+ const secret = (service.getOptions() as any).webhookSecret as string
33
+
34
+ const rawBody = (req as any).rawBody as Buffer | undefined
35
+ const rawStr = rawBody?.length ? rawBody.toString("utf8") : JSON.stringify(req.body ?? {})
36
+
37
+ const headers = {
38
+ signature: req.headers["x-faire-webhook-signature"] as string | undefined,
39
+ timestamp: req.headers["x-faire-webhook-timestamp"] as string | undefined,
40
+ }
41
+
42
+ const { valid, reason } = verifyFaireWebhook({ secret, headers, rawBody: rawStr })
43
+ if (!valid) {
44
+ logger?.warn?.(`[faire-webhook] rejected: ${reason}`)
45
+ return res.status(401).json({ error: "invalid signature" })
46
+ }
47
+
48
+ const payload: any = req.body ?? safeParse(rawStr)
49
+ const eventType: string = String(
50
+ payload?.event_type || payload?.type || "unknown"
51
+ ).toLowerCase()
52
+
53
+ // Delivery id — Faire sends a unique id per delivery; fall back to a hash of
54
+ // the body + type so we still dedupe if absent.
55
+ const webhookId =
56
+ (req.headers["x-faire-webhook-id"] as string | undefined) ||
57
+ (payload?.webhook_id as string | undefined) ||
58
+ `${eventType}:${crypto.createHash("sha256").update(rawStr).digest("hex")}`
59
+
60
+ // Idempotency — Faire retries with backoff; a verified duplicate is a no-op.
61
+ const existing = await service
62
+ .listFaireWebhookEvents({ webhook_id: webhookId } as any)
63
+ .catch(() => [])
64
+ if ((existing as any[])?.length) {
65
+ return res.status(200).json({ ok: true, duplicate: true })
66
+ }
67
+
68
+ let record: any
69
+ try {
70
+ record = await service.createFaireWebhookEvents({
71
+ webhook_id: webhookId,
72
+ event_type: eventType,
73
+ brand_id: payload?.brand_id != null ? String(payload.brand_id) : null,
74
+ resource_url: payload?.resource_url ?? null,
75
+ payload,
76
+ received_at: new Date(),
77
+ } as any)
78
+ } catch {
79
+ // Unique-constraint race → treat as duplicate.
80
+ return res.status(200).json({ ok: true, duplicate: true })
81
+ }
82
+
83
+ // Best-effort processing — never fail the webhook once it's verified+recorded.
84
+ try {
85
+ let resource: any = null
86
+ if (payload?.resource_url) {
87
+ const account = await service.getActiveAccount()
88
+ if (account) {
89
+ resource = await service
90
+ .getClient()
91
+ .fetchResource(account.access_token, payload.resource_url)
92
+ .catch(() => null)
93
+ }
94
+ } else if (payload?.data) {
95
+ resource = payload.data
96
+ }
97
+
98
+ const eventBus: any = req.scope.resolve(Modules.EVENT_BUS)
99
+ await eventBus.emit({
100
+ name: `faire.${eventType}`,
101
+ data: {
102
+ webhook_id: webhookId,
103
+ event_type: eventType,
104
+ brand_id: payload?.brand_id ?? null,
105
+ resource_url: payload?.resource_url ?? null,
106
+ resource,
107
+ },
108
+ })
109
+
110
+ await service.updateFaireWebhookEvents({
111
+ id: record.id,
112
+ resource,
113
+ processed: true,
114
+ } as any)
115
+ } catch (err: any) {
116
+ logger?.warn?.(
117
+ `[faire-webhook] processing failed for ${webhookId}: ${err?.message || err}`
118
+ )
119
+ await service
120
+ .updateFaireWebhookEvents({ id: record.id, error: String(err?.message || err) } as any)
121
+ .catch(() => {})
122
+ }
123
+
124
+ return res.status(200).json({ ok: true })
125
+ }
126
+
127
+ function safeParse(s: string): any {
128
+ try {
129
+ return JSON.parse(s)
130
+ } catch {
131
+ return {}
132
+ }
133
+ }
@@ -0,0 +1,30 @@
1
+ import { MedusaContainer } from "@medusajs/framework/types"
2
+ import { FAIRE_SYNC_MODULE } from "../modules/faire-sync"
3
+ import FaireSyncService from "../modules/faire-sync/service"
4
+ import { ingestFaireOrdersBulkWorkflow } from "../workflows/ingest-faire-orders-bulk"
5
+
6
+ /**
7
+ * Scheduled pull of new Faire orders. Runs the bulk order-ingestion workflow
8
+ * (a long-running background workflow) so new wholesale orders placed on Faire
9
+ * are mirrored into Medusa without a seller having to trigger it manually.
10
+ *
11
+ * Disable by setting FAIRE_AUTO_INGEST_ORDERS=false.
12
+ */
13
+ export default async function pullFaireOrdersJob(container: MedusaContainer) {
14
+ if (process.env.FAIRE_AUTO_INGEST_ORDERS === "false") return
15
+
16
+ const service: FaireSyncService = container.resolve(FAIRE_SYNC_MODULE)
17
+ const account = await service.getActiveAccount()
18
+ if (!account) return
19
+
20
+ try {
21
+ await ingestFaireOrdersBulkWorkflow(container).run({ input: {} })
22
+ } catch (err: any) {
23
+ console.error("[faire-sync] scheduled order pull failed:", err.message)
24
+ }
25
+ }
26
+
27
+ export const config = {
28
+ name: "faire-pull-orders",
29
+ schedule: "*/30 * * * *", // every 30 minutes
30
+ }
@@ -0,0 +1,23 @@
1
+ import { MedusaContainer } from "@medusajs/framework/types"
2
+ import { FAIRE_SYNC_MODULE } from "../modules/faire-sync"
3
+ import FaireSyncService from "../modules/faire-sync/service"
4
+
5
+ // Refreshes the Faire access token if it is set to expire (Faire tokens
6
+ // historically do not expire, but this keeps the OAuth2 path correct if yours do).
7
+ export default async function refreshFaireTokenJob(container: MedusaContainer) {
8
+ const service: FaireSyncService = container.resolve(FAIRE_SYNC_MODULE)
9
+ const account = await service.getActiveAccount()
10
+ if (!account) {
11
+ return
12
+ }
13
+ try {
14
+ await service.ensureFreshToken(account)
15
+ } catch (err: any) {
16
+ console.error("[faire-sync] token refresh failed:", err.message)
17
+ }
18
+ }
19
+
20
+ export const config = {
21
+ name: "faire-refresh-token",
22
+ schedule: "*/10 * * * *",
23
+ }