medusa-plugin-printify 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 (39) hide show
  1. package/.medusa/server/jest.config.js +20 -0
  2. package/.medusa/server/jest.setup.js +4 -0
  3. package/.medusa/server/src/admin/index.js +472 -0
  4. package/.medusa/server/src/admin/index.mjs +473 -0
  5. package/.medusa/server/src/api/admin/plugin/route.js +7 -0
  6. package/.medusa/server/src/api/admin/printify/orders/[id]/submit/route.js +19 -0
  7. package/.medusa/server/src/api/admin/printify/orders/route.js +18 -0
  8. package/.medusa/server/src/api/admin/printify/products/[id]/route.js +113 -0
  9. package/.medusa/server/src/api/admin/printify/products/route.js +35 -0
  10. package/.medusa/server/src/api/admin/printify/shops/route.js +18 -0
  11. package/.medusa/server/src/api/middlewares.js +12 -0
  12. package/.medusa/server/src/api/store/plugin/route.js +7 -0
  13. package/.medusa/server/src/api/webhooks/printify/route.js +102 -0
  14. package/.medusa/server/src/jobs/auto-submit-orders-job.js +31 -0
  15. package/.medusa/server/src/jobs/sync-products-job.js +23 -0
  16. package/.medusa/server/src/lib/webhook-utils.js +13 -0
  17. package/.medusa/server/src/links/printify-order-medusa-order.js +10 -0
  18. package/.medusa/server/src/links/printify-product-medusa-product.js +10 -0
  19. package/.medusa/server/src/modules/printify/api-client.js +67 -0
  20. package/.medusa/server/src/modules/printify/index.js +13 -0
  21. package/.medusa/server/src/modules/printify/migrations/Migration20260305115907.js +21 -0
  22. package/.medusa/server/src/modules/printify/migrations/Migration20260313185144.js +14 -0
  23. package/.medusa/server/src/modules/printify/models/order.js +18 -0
  24. package/.medusa/server/src/modules/printify/models/product.js +22 -0
  25. package/.medusa/server/src/modules/printify/models/shop.js +11 -0
  26. package/.medusa/server/src/modules/printify/service.js +38 -0
  27. package/.medusa/server/src/modules/printify/types.js +3 -0
  28. package/.medusa/server/src/subscribers/order-placed.js +55 -0
  29. package/.medusa/server/src/subscribers/plugin-registered.js +37 -0
  30. package/.medusa/server/src/utils/build-variant-prices.js +17 -0
  31. package/.medusa/server/src/utils/currency-converter.js +46 -0
  32. package/.medusa/server/src/workflows/create-printify-order.js +86 -0
  33. package/.medusa/server/src/workflows/index.js +12 -0
  34. package/.medusa/server/src/workflows/option-mapper.js +64 -0
  35. package/.medusa/server/src/workflows/submit-printify-order.js +29 -0
  36. package/.medusa/server/src/workflows/sync-products.js +313 -0
  37. package/.medusa/server/src/workflows/sync-shops.js +30 -0
  38. package/README.md +253 -0
  39. package/package.json +78 -0
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.syncShopsWorkflow = void 0;
4
+ const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
5
+ const printify_1 = require("../modules/printify");
6
+ const fetchShopsStep = (0, workflows_sdk_1.createStep)("printify-fetch-shops-step", async (_input, { container }) => {
7
+ const service = container.resolve(printify_1.PRINTIFY_MODULE);
8
+ const shops = await service.getApiClient().getShops();
9
+ return new workflows_sdk_1.StepResponse(shops);
10
+ });
11
+ const upsertShopsStep = (0, workflows_sdk_1.createStep)("printify-upsert-shops-step", async (shops, { container }) => {
12
+ const service = container.resolve(printify_1.PRINTIFY_MODULE);
13
+ for (const shop of shops) {
14
+ const printifyId = String(shop.id);
15
+ const existing = await service.listPrintifyShops({ printify_id: printifyId });
16
+ if (existing.length > 0) {
17
+ await service.updatePrintifyShops({ printify_id: printifyId }, { title: shop.title });
18
+ }
19
+ else {
20
+ await service.createPrintifyShops([{ printify_id: printifyId, title: shop.title }]);
21
+ }
22
+ }
23
+ return new workflows_sdk_1.StepResponse({ synced: shops.length });
24
+ });
25
+ exports.syncShopsWorkflow = (0, workflows_sdk_1.createWorkflow)("sync-printify-shops", (input) => {
26
+ const shops = fetchShopsStep(input);
27
+ const result = upsertShopsStep(shops);
28
+ return new workflows_sdk_1.WorkflowResponse(result);
29
+ });
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1zaG9wcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3Mvc3luYy1zaG9wcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFLMEM7QUFDMUMsa0RBQXFEO0FBVXJELE1BQU0sY0FBYyxHQUFHLElBQUEsMEJBQVUsRUFDL0IsMkJBQTJCLEVBQzNCLEtBQUssRUFBRSxNQUFzQixFQUFFLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUM5QyxNQUFNLE9BQU8sR0FBMEIsU0FBUyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDekUsTUFBTSxLQUFLLEdBQUcsTUFBTSxPQUFPLENBQUMsWUFBWSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUE7SUFDckQsT0FBTyxJQUFJLDRCQUFZLENBQUMsS0FBSyxDQUFDLENBQUE7QUFDaEMsQ0FBQyxDQUNGLENBQUE7QUFFRCxNQUFNLGVBQWUsR0FBRyxJQUFBLDBCQUFVLEVBQ2hDLDRCQUE0QixFQUM1QixLQUFLLEVBQUUsS0FBcUIsRUFBRSxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUU7SUFDN0MsTUFBTSxPQUFPLEdBQTBCLFNBQVMsQ0FBQyxPQUFPLENBQUMsMEJBQWUsQ0FBQyxDQUFBO0lBRXpFLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7UUFDekIsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQTtRQUNsQyxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFBO1FBQzdFLElBQUksUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUN4QixNQUFNLE9BQU8sQ0FBQyxtQkFBbUIsQ0FDL0IsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLEVBQzNCLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FDdEIsQ0FBQTtRQUNILENBQUM7YUFBTSxDQUFDO1lBQ04sTUFBTSxPQUFPLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUE7UUFDckYsQ0FBQztJQUNILENBQUM7SUFFRCxPQUFPLElBQUksNEJBQVksQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQTtBQUNuRCxDQUFDLENBQ0YsQ0FBQTtBQUVZLFFBQUEsaUJBQWlCLEdBQUcsSUFBQSw4QkFBYyxFQUM3QyxxQkFBcUIsRUFDckIsQ0FBQyxLQUFxQixFQUFFLEVBQUU7SUFDeEIsTUFBTSxLQUFLLEdBQUcsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ25DLE1BQU0sTUFBTSxHQUFHLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUNyQyxPQUFPLElBQUksZ0NBQWdCLENBQUMsTUFBTSxDQUFDLENBQUE7QUFDckMsQ0FBQyxDQUNGLENBQUEifQ==
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # medusa-plugin-printify
2
+
3
+ A [Medusa v2](https://medusajs.com) plugin that integrates [Printify](https://printify.com) print-on-demand fulfillment into your store. It syncs Printify products and shops into Medusa, automatically submits orders to Printify for production, handles real-time webhook status updates, and adds a Printify management section to the Medusa Admin dashboard.
4
+
5
+ ## Features
6
+
7
+ - **Product sync** — pull your Printify catalog into Medusa on a schedule or on-demand
8
+ - **Order fulfillment** — automatically submit Medusa orders to Printify when they are placed
9
+ - **Webhook handling** — receive real-time status updates (shipped, in-production, delivered, etc.)
10
+ - **Admin UI** — manage shops, browse synced products, and monitor order fulfillment status from the Medusa Admin dashboard
11
+ - **Webhook auto-registration** — registers all required Printify webhook topics on startup
12
+
13
+ ## Requirements
14
+
15
+ - Node.js >= 20
16
+ - Medusa >= 2.12.4
17
+ - A [Printify account](https://printify.com) with a personal access token
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ In your Medusa application directory:
24
+
25
+ ```bash
26
+ pnpm add medusa-plugin-printify
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Configuration
32
+
33
+ ### 1. Add environment variables
34
+
35
+ Add the following to your Medusa application's `.env`:
36
+
37
+ ```bash
38
+ PRINTIFY_API_KEY=your_printify_personal_access_token
39
+ PRINTIFY_WEBHOOK_SECRET=your_webhook_secret
40
+ PRINTIFY_SHOP_ID=your_printify_shop_id # optional, but required for order submission
41
+ PRINTIFY_WEBHOOK_URL=https://your-domain.com # base URL for webhook registration (no trailing slash)
42
+ ```
43
+
44
+ To find your **API key**: go to [printify.com/app/account/api](https://printify.com/app/account/api) and generate a personal access token.
45
+
46
+ To find your **shop ID**: call `GET https://api.printify.com/v1/shops.json` with your API key.
47
+
48
+ The **webhook secret** is a string you choose — Printify will use it to sign webhook payloads so you can verify they are genuine.
49
+
50
+ ### 2. Register the plugin in `medusa-config.ts`
51
+
52
+ ```ts
53
+ import { defineConfig } from "@medusajs/framework/utils"
54
+
55
+ export default defineConfig({
56
+ // ... other config
57
+ plugins: [
58
+ {
59
+ resolve: "medusa-plugin-printify",
60
+ options: {
61
+ apiKey: process.env.PRINTIFY_API_KEY,
62
+ webhookSecret: process.env.PRINTIFY_WEBHOOK_SECRET,
63
+ shopId: process.env.PRINTIFY_SHOP_ID,
64
+ webhookBaseUrl: process.env.PRINTIFY_WEBHOOK_URL,
65
+ },
66
+ },
67
+ ],
68
+ })
69
+ ```
70
+
71
+ All available options:
72
+
73
+ | Option | Type | Required | Description |
74
+ |---|---|---|---|
75
+ | `apiKey` | `string` | Yes | Printify personal access token |
76
+ | `webhookSecret` | `string` | Yes | Secret used to verify webhook signatures |
77
+ | `shopId` | `string` | No | Printify shop ID — required for order submission and webhook registration |
78
+ | `webhookBaseUrl` | `string` | No | Base URL of your Medusa server (e.g. `https://api.mystore.com`) — required for webhook registration |
79
+ | `enableNotifications` | `boolean` | No | Emit Medusa notification events for Printify order status changes |
80
+ | `notificationEmail` | `string` | No | Email address to receive Printify fulfillment notifications |
81
+
82
+ ### 3. Run database migrations
83
+
84
+ In your Medusa application directory:
85
+
86
+ ```bash
87
+ pnpm medusa db:migrate
88
+ ```
89
+
90
+ This creates the `printify_shop`, `printify_product`, and `printify_order` tables.
91
+
92
+ ---
93
+
94
+ ## Local Development
95
+
96
+ ### Prerequisites
97
+
98
+ - [Docker](https://www.docker.com) (for the local PostgreSQL instance)
99
+ - pnpm
100
+
101
+ ### Plugin setup
102
+
103
+ Clone this repository and install dependencies:
104
+
105
+ ```bash
106
+ git clone https://github.com/your-org/medusa-plugin-printify
107
+ cd medusa-plugin-printify
108
+ pnpm install
109
+ ```
110
+
111
+ Copy the environment template and fill in your values:
112
+
113
+ ```bash
114
+ cp .env.template .env
115
+ ```
116
+
117
+ Start the local PostgreSQL instance:
118
+
119
+ ```bash
120
+ docker compose up -d
121
+ ```
122
+
123
+ Generate the plugin's database migrations:
124
+
125
+ ```bash
126
+ pnpm medusa plugin:db:generate
127
+ ```
128
+
129
+ Publish the plugin to the local [Yalc](https://github.com/wclr/yalc) registry so your Medusa application can install it:
130
+
131
+ ```bash
132
+ pnpm medusa plugin:publish
133
+ ```
134
+
135
+ ### Link to a local Medusa application
136
+
137
+ In your Medusa application directory, install the locally published plugin:
138
+
139
+ ```bash
140
+ pnpm medusa plugin:add medusa-plugin-printify
141
+ ```
142
+
143
+ Run migrations in the Medusa application:
144
+
145
+ ```bash
146
+ pnpm medusa db:migrate
147
+ ```
148
+
149
+ ### Watch for changes
150
+
151
+ In the plugin directory, start the dev watcher — changes are automatically pushed to the local Yalc registry and picked up by your Medusa application:
152
+
153
+ ```bash
154
+ pnpm dev
155
+ ```
156
+
157
+ ### Run tests
158
+
159
+ ```bash
160
+ pnpm test # all tests
161
+ pnpm test -- --watch # watch mode
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Production Deployment
167
+
168
+ ### 1. Build the plugin
169
+
170
+ ```bash
171
+ pnpm build
172
+ ```
173
+
174
+ ### 2. Publish to npm (first time)
175
+
176
+ ```bash
177
+ npm publish
178
+ ```
179
+
180
+ ### 3. Install in your Medusa application
181
+
182
+ ```bash
183
+ pnpm add medusa-plugin-printify
184
+ ```
185
+
186
+ ### 4. Set environment variables on your server
187
+
188
+ Ensure the following are set in your production environment:
189
+
190
+ ```bash
191
+ PRINTIFY_API_KEY=...
192
+ PRINTIFY_WEBHOOK_SECRET=...
193
+ PRINTIFY_SHOP_ID=...
194
+ PRINTIFY_WEBHOOK_URL=https://api.your-production-domain.com
195
+ ```
196
+
197
+ ### 5. Run migrations
198
+
199
+ ```bash
200
+ pnpm medusa db:migrate
201
+ ```
202
+
203
+ ### 6. Configure your reverse proxy
204
+
205
+ The plugin exposes a webhook endpoint at `POST /webhooks/printify`. Ensure this path is reachable from the public internet so Printify can deliver webhook events. No authentication is needed on this route — the plugin verifies the `x-pfy-signature` HMAC header internally.
206
+
207
+ On startup, the plugin automatically registers all required webhook topics with Printify using the `webhookBaseUrl` you configured.
208
+
209
+ ---
210
+
211
+ ## API Reference
212
+
213
+ All admin routes require authentication. Storefront routes are public.
214
+
215
+ ### Admin routes
216
+
217
+ | Method | Path | Description |
218
+ |---|---|---|
219
+ | `GET` | `/admin/printify/shops` | List all synced Printify shops |
220
+ | `POST` | `/admin/printify/shops` | Trigger a shop sync from Printify |
221
+ | `GET` | `/admin/printify/products` | List synced products (supports `?shop_id=` filter) |
222
+ | `POST` | `/admin/printify/products` | Trigger a product sync from Printify |
223
+ | `GET` | `/admin/printify/orders` | List Printify orders (supports `?status=` and `?medusa_order_id=` filters) |
224
+ | `POST` | `/admin/printify/orders/:id/submit` | Manually submit a Printify order to production |
225
+
226
+ ### Webhook endpoint
227
+
228
+ | Method | Path | Description |
229
+ |---|---|---|
230
+ | `POST` | `/webhooks/printify` | Receives Printify webhook events |
231
+
232
+ **Handled events:** `order:status-changed`, `order:shipped`, `order:sent-to-production`, `order:shipment:delivered`, `product:updated`, `product:deleted`, `shop:disconnected`
233
+
234
+ ---
235
+
236
+ ## Order Fulfillment Flow
237
+
238
+ 1. A customer places an order in your Medusa storefront
239
+ 2. The `order.placed` subscriber detects any line items with `metadata.printify_product_id`
240
+ 3. A Printify order is created and submitted to production automatically
241
+ 4. Printify sends webhook events as the order moves through production and shipping
242
+ 5. The plugin updates the local Printify order record and emits Medusa events for each status change
243
+ 6. The Printify order widget on the Medusa Admin order detail page reflects the current status
244
+
245
+ ---
246
+
247
+ ## Admin Dashboard
248
+
249
+ The plugin adds two pages and one widget to the Medusa Admin:
250
+
251
+ - **Printify** (sidebar) — lists synced shops with a one-click sync button
252
+ - **Printify › Products** — browse all synced products with search and a sync button
253
+ - **Order detail widget** — appears below the order details on any order page, showing the linked Printify order status and a manual submit button for pending orders
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "medusa-plugin-printify",
3
+ "version": "0.1.0",
4
+ "description": "Printify print-on-demand integration plugin for Medusa v2",
5
+ "author": "TrendTri",
6
+ "license": "MIT",
7
+ "files": [
8
+ ".medusa/server"
9
+ ],
10
+ "exports": {
11
+ "./package.json": "./package.json",
12
+ "./workflows": "./.medusa/server/src/workflows/index.js",
13
+ "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
14
+ "./modules/*": "./.medusa/server/src/modules/*/index.js",
15
+ "./providers/*": "./.medusa/server/src/providers/*/index.js",
16
+ "./*": "./.medusa/server/src/*.js",
17
+ "./admin": {
18
+ "import": "./.medusa/server/src/admin/index.mjs",
19
+ "require": "./.medusa/server/src/admin/index.js",
20
+ "default": "./.medusa/server/src/admin/index.js"
21
+ }
22
+ },
23
+ "keywords": [
24
+ "medusa",
25
+ "plugin",
26
+ "medusa-plugin-other",
27
+ "medusa-plugin",
28
+ "medusa-v2"
29
+ ],
30
+ "scripts": {
31
+ "build": "medusa plugin:build",
32
+ "dev": "medusa plugin:develop",
33
+ "prepublishOnly": "medusa plugin:build",
34
+ "test": "jest",
35
+ "test:watch": "jest --watch",
36
+ "test:coverage": "jest --coverage"
37
+ },
38
+ "devDependencies": {
39
+ "@medusajs/admin-sdk": "2.13.1",
40
+ "@medusajs/cli": "2.13.1",
41
+ "@medusajs/framework": "2.13.1",
42
+ "@medusajs/icons": "2.13.1",
43
+ "@medusajs/medusa": "2.13.1",
44
+ "@medusajs/test-utils": "2.13.1",
45
+ "@medusajs/ui": "4.1.1",
46
+ "@swc/core": "^1.7.28",
47
+ "@types/jest": "^30.0.0",
48
+ "@types/node": "^20.0.0",
49
+ "@types/react": "^18.3.2",
50
+ "@types/react-dom": "^18.2.25",
51
+ "jest": "^30.2.0",
52
+ "jest-mock-extended": "^4.0.0",
53
+ "prop-types": "^15.8.1",
54
+ "react": "^18.2.0",
55
+ "react-dom": "^18.2.0",
56
+ "ts-jest": "^29.4.6",
57
+ "ts-node": "^10.9.2",
58
+ "typescript": "^5.6.2",
59
+ "vite": "^5.2.11",
60
+ "yalc": "^1.0.0-pre.53"
61
+ },
62
+ "peerDependencies": {
63
+ "@medusajs/admin-sdk": "^2.12.4",
64
+ "@medusajs/cli": "^2.12.4",
65
+ "@medusajs/framework": "^2.12.4",
66
+ "@medusajs/icons": "^2.12.4",
67
+ "@medusajs/medusa": "^2.12.4",
68
+ "@medusajs/test-utils": "^2.12.4",
69
+ "@medusajs/ui": "^4.0.25"
70
+ },
71
+ "engines": {
72
+ "node": ">=20"
73
+ },
74
+ "dependencies": {
75
+ "axios": "^1.13.6"
76
+ },
77
+ "packageManager": "pnpm@10.18.3+sha1.0202a20aaa3d7ba8bc29b50d95efe1a34dd95773"
78
+ }