@pinelab/vendure-plugin-sendcloud 1.6.0 → 2.0.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 (43) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +49 -12
  3. package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.client.d.ts +0 -5
  4. package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.client.js +0 -15
  5. package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.service.d.ts +30 -14
  6. package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.service.js +154 -62
  7. package/dist/{vendure-plugin-sendcloud/src/api → api}/types/sendcloud-api.types.d.ts +0 -5
  8. package/dist/{vendure-plugin-sendcloud/src/api → api}/types/sendcloud.types.d.ts +0 -9
  9. package/dist/api/types/sendcloud.types.js +2 -0
  10. package/dist/config/fulfill-settled-orders-task.d.ts +26 -0
  11. package/dist/config/fulfill-settled-orders-task.js +40 -0
  12. package/dist/{vendure-plugin-sendcloud/src/index.d.ts → index.d.ts} +1 -0
  13. package/dist/{vendure-plugin-sendcloud/src/index.js → index.js} +1 -0
  14. package/dist/{vendure-plugin-sendcloud/src/sendcloud.plugin.js → sendcloud.plugin.js} +1 -9
  15. package/package.json +5 -5
  16. package/dist/util/src/index.d.ts +0 -1
  17. package/dist/util/src/index.js +0 -17
  18. package/dist/util/src/order-state-util.d.ts +0 -22
  19. package/dist/util/src/order-state-util.js +0 -78
  20. package/dist/util/src/raw-body.middleware.d.ts +0 -9
  21. package/dist/util/src/raw-body.middleware.js +0 -17
  22. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.controller.d.ts +0 -7
  23. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.controller.js +0 -80
  24. package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud.types.js +0 -157
  25. /package/dist/{vendure-plugin-sendcloud/src/api → api}/additional-parcel-input-items.d.ts +0 -0
  26. /package/dist/{vendure-plugin-sendcloud/src/api → api}/additional-parcel-input-items.js +0 -0
  27. /package/dist/{vendure-plugin-sendcloud/src/api → api}/constants.d.ts +0 -0
  28. /package/dist/{vendure-plugin-sendcloud/src/api → api}/constants.js +0 -0
  29. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud-config.entity.d.ts +0 -0
  30. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud-config.entity.js +0 -0
  31. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.adapter.d.ts +0 -0
  32. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.adapter.js +0 -0
  33. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.handler.d.ts +0 -0
  34. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.handler.js +0 -0
  35. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.resolver.d.ts +0 -0
  36. /package/dist/{vendure-plugin-sendcloud/src/api → api}/sendcloud.resolver.js +0 -0
  37. /package/dist/{vendure-plugin-sendcloud/src/api → api}/types/sendcloud-api.types.js +0 -0
  38. /package/dist/{vendure-plugin-sendcloud/src/sendcloud.plugin.d.ts → sendcloud.plugin.d.ts} +0 -0
  39. /package/dist/{vendure-plugin-sendcloud/src/ui → ui}/history-entry.component.ts +0 -0
  40. /package/dist/{vendure-plugin-sendcloud/src/ui → ui}/queries.ts +0 -0
  41. /package/dist/{vendure-plugin-sendcloud/src/ui → ui}/sendcloud-nav.module.ts +0 -0
  42. /package/dist/{vendure-plugin-sendcloud/src/ui → ui}/sendcloud.component.ts +0 -0
  43. /package/dist/{vendure-plugin-sendcloud/src/ui → ui}/sendcloud.module.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 2.0.0 (2026-04-12)
2
+
3
+ - **Breaking**: Removed all webhook handling. Sendcloud is responsible for fulfillment.
4
+ - Added `fulfillSettledOrdersTask` scheduled task (opt-in) to fulfill settled orders to `Delivered` on a nightly basis.
5
+ - Orders are no longer automatically fulfilled on placement. They stay in `PaymentSettled` until the scheduled task runs to prevent misaligned stock in Vendure.
6
+
1
7
  # 1.6.0 (2026-02-05)
2
8
 
3
9
  - Upgraded to Vendure 3.5.3
package/README.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  This plugin syncs orders to the SendCloud fulfillment platform.
6
6
 
7
+ ## How it works
8
+
9
+ 1. When an order is placed, it is automatically sent to SendCloud as a parcel (for label creation).
10
+ 2. The order stays in `PaymentSettled` state in Vendure — **no fulfillment is created at this point**.
11
+ 3. Once an order has been sent to SendCloud, SendCloud is responsible for tracking status. The Vendure order will simply be transitioned to `Delivered` by the included scheduled task. This is because orders are sometimes cancelled and duplicated in Sendcloud, resulting in misaligned stock.
12
+
7
13
  ## Getting started
8
14
 
9
15
  1. Add the plugin to your `vendure-config.ts`:
@@ -31,17 +37,46 @@ AdminUiPlugin.init({
31
37
  3. Run a DB [migration](https://www.vendure.io/docs/developer-guide/migrations/) to add the new SendCloudConfigEntity to
32
38
  the database.
33
39
  4. Go to your SendCloud account and go to `Settings > Integrations` and create an integration.
34
- 5. Write down the `secret` and `publicKey` of the created integration
35
- 6. For the same integration, add the webhook `https://your-vendure-domain.io/sendcloud/webhook/your-channel-token`. This
36
- will update orders when the status changes in SendCloud.
37
- 7. Start Vendure and login as admin
38
- 8. Make sure you have the permission `SetSendCloudConfig`
39
- 9. Go to `Settings > SendCloud`
40
- 10. You can fill in your SendCloud `secret` and `public key` here and click save.
41
- 11. Additionally, you can set a fallback phone number, for when a customer hasn't filled out one. A phone number is
40
+ 5. Write down the `secret` and `publicKey` of the created integration.
41
+ 6. Start Vendure and login as admin.
42
+ 7. Make sure you have the permission `SetSendCloudConfig`.
43
+ 8. Go to `Settings > SendCloud`.
44
+ 9. Fill in your SendCloud `secret` and `public key` here and click save.
45
+ 10. Additionally, you can set a fallback phone number, for when a customer hasn't filled out one. A phone number is
42
46
  required by Sendcloud in some cases.
43
47
 
44
- Now, when an order is placed, it will be automatically fulfilled and send to SendCloud.
48
+ ## Scheduled task: fulfill settled orders
49
+
50
+ An optional scheduled task `fulfillSettledOrdersTask` is included to automatically transition settled orders to `Delivered`.
51
+ It is **not auto-registered** — you need to add it to your `schedulerOptions.tasks` yourself.
52
+
53
+ The task runs nightly (default 2:00 AM) and processes all `PaymentSettled` orders placed within the last N days
54
+ (default 7) that use the SendCloud fulfillment handler. Each qualifying order is fulfilled and transitioned to `Delivered`.
55
+
56
+ Do not manually change order statuses in Vendure if you use this scheduled task. The task will fulfill orders and transition to delivered every night.
57
+
58
+ ```ts
59
+ import { DefaultSchedulerPlugin } from '@vendure/core';
60
+ import {
61
+ SendcloudPlugin,
62
+ fulfillSettledOrdersTask,
63
+ } from '@pinelab/vendure-plugin-sendcloud';
64
+
65
+ const config: VendureConfig = {
66
+ plugins: [SendcloudPlugin.init({}), DefaultSchedulerPlugin.init()],
67
+ schedulerOptions: {
68
+ tasks: [
69
+ // Use defaults (every day at 2:00 AM, look back 7 days)
70
+ fulfillSettledOrdersTask,
71
+ // Or configure the task
72
+ fulfillSettledOrdersTask.configure({
73
+ schedule: (cron) => cron.everyDayAt(3, 0),
74
+ params: { settledSinceDays: 14 },
75
+ }),
76
+ ],
77
+ },
78
+ };
79
+ ```
45
80
 
46
81
  ## Additional configuration
47
82
 
@@ -49,10 +84,11 @@ You can choose to send additional info to SendCloud: `weight`, `hsCode`, `origin
49
84
  Parcel items will show up as rows on your SendCloud packaging slips.
50
85
 
51
86
  ```ts
52
- import 'SendCloudPlugin, getNrOfOrders';
87
+ import {
88
+ SendcloudPlugin,
89
+ getNrOfOrders,
90
+ } from '@pinelab/vendure-plugin-sendcloud';
53
91
 
54
- from;
55
- ('vendure-plugin-sendcloud');
56
92
  plugins: [
57
93
  SendcloudPlugin.init({
58
94
  /**
@@ -77,6 +113,7 @@ plugins: [
77
113
  * This example adds the nr of previous orders of the current customer to SendCloud
78
114
  */
79
115
  additionalParcelItemsFn: async (ctx, injector, order) => {
116
+ const additionalInputs = [];
80
117
  additionalInputs.push(await getNrOfOrders(ctx, injector, order));
81
118
  return additionalInputs;
82
119
  },
@@ -3,16 +3,11 @@ import { Parcel, ParcelInput } from './types/sendcloud-api.types';
3
3
  export declare class SendcloudClient {
4
4
  private publicKey;
5
5
  private secret;
6
- static signatureHeader: string;
7
6
  endpoint: string;
8
7
  headers: {
9
8
  [key: string]: string;
10
9
  };
11
10
  constructor(publicKey: string, secret: string);
12
11
  createParcel(parcelInput: ParcelInput): Promise<Parcel>;
13
- /**
14
- * Verifies if the incoming webhook is actually from SendCloud
15
- */
16
- isValidWebhook(body: string, signature: string): boolean;
17
12
  fetch(path: string, body: any): Promise<Response>;
18
13
  }
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SendcloudClient = void 0;
7
7
  const node_fetch_1 = __importDefault(require("node-fetch"));
8
- const crypto_1 = __importDefault(require("crypto"));
9
8
  const core_1 = require("@vendure/core");
10
9
  const constants_1 = require("./constants");
11
10
  class SendcloudClient {
@@ -29,19 +28,6 @@ class SendcloudClient {
29
28
  core_1.Logger.info(`Created parcel in SendCloud with for order ${parcelInput.order_number} with id ${json.parcel?.id}`, constants_1.loggerCtx);
30
29
  return json.parcel;
31
30
  }
32
- /**
33
- * Verifies if the incoming webhook is actually from SendCloud
34
- */
35
- isValidWebhook(body, signature) {
36
- if (!body || !signature) {
37
- return false;
38
- }
39
- const hash = crypto_1.default
40
- .createHmac('sha256', this.secret)
41
- .update(body)
42
- .digest('hex');
43
- return hash === signature;
44
- }
45
31
  async fetch(path, body) {
46
32
  const res = await (0, node_fetch_1.default)(`${this.endpoint}/${path}`, {
47
33
  method: 'POST',
@@ -56,4 +42,3 @@ class SendcloudClient {
56
42
  }
57
43
  }
58
44
  exports.SendcloudClient = SendcloudClient;
59
- SendcloudClient.signatureHeader = 'sendcloud-signature';
@@ -4,7 +4,7 @@ import { ChannelService, EntityHydrator, EventBus, HistoryService, ID, JobQueueS
4
4
  import { SendcloudConfigEntity } from './sendcloud-config.entity';
5
5
  import { SendcloudClient } from './sendcloud.client';
6
6
  import { Parcel } from './types/sendcloud-api.types';
7
- import { SendcloudParcelStatus, SendcloudPluginOptions } from './types/sendcloud.types';
7
+ import { SendcloudPluginOptions } from './types/sendcloud.types';
8
8
  export declare class SendcloudService implements OnApplicationBootstrap {
9
9
  private eventBus;
10
10
  private connection;
@@ -19,19 +19,6 @@ export declare class SendcloudService implements OnApplicationBootstrap {
19
19
  constructor(eventBus: EventBus, connection: TransactionalConnection, orderService: OrderService, channelService: ChannelService, jobQueueService: JobQueueService, moduleRef: ModuleRef, options: SendcloudPluginOptions, entityHydrator: EntityHydrator, historyService: HistoryService);
20
20
  onApplicationBootstrap(): Promise<void>;
21
21
  createOrderInSendcloud(userCtx: RequestContext, order: Order): Promise<Parcel | undefined>;
22
- /**
23
- * Update order by given orderCode, returns undefined if no action was taken
24
- * Returns order if transition was successful
25
- */
26
- updateOrderStatus(ctx: RequestContext, sendcloudStatus: SendcloudParcelStatus, orderCode: string): Promise<void>;
27
- /**
28
- * Ship all items
29
- */
30
- shipAll(ctx: RequestContext, order: Order): Promise<void>;
31
- /**
32
- * Fulfill without throwing errors. Logs an error if fulfilment fails
33
- */
34
- private safeFulfill;
35
22
  upsertConfig(ctx: RequestContext, config: {
36
23
  secret: string;
37
24
  publicKey: string;
@@ -48,4 +35,33 @@ export declare class SendcloudService implements OnApplicationBootstrap {
48
35
  */
49
36
  private syncOrder;
50
37
  logHistoryEntry(ctx: RequestContext, orderId: ID, error?: unknown): Promise<void>;
38
+ /**
39
+ * Find orders placed within the last N days that use the SendCloud fulfillment handler
40
+ * and process them based on their current state:
41
+ * - PaymentAuthorized: create fulfillment only (no shipping transitions)
42
+ * - PaymentSettled: fulfill + transition to Shipped + transition to Delivered
43
+ * - Shipped: transition existing fulfillment(s) to Delivered
44
+ *
45
+ * Intended to be called by the `fulfillSettledOrdersTask` scheduled task.
46
+ */
47
+ fulfillPlacedOrders(settledSinceDays: number): Promise<{
48
+ fulfilled: number;
49
+ skipped: number;
50
+ failed: number;
51
+ }>;
52
+ /**
53
+ * Create a fulfillment for all order lines without any state transitions.
54
+ * Used for PaymentAuthorized orders where we only want to register the fulfillment.
55
+ */
56
+ private createFulfillmentOnly;
57
+ /**
58
+ * Fulfill all order lines and transition the fulfillment to Shipped then Delivered.
59
+ * Also works for orders that already have a fulfillment and/or are already Shipped
60
+ */
61
+ private fulfillOrderToDelivered;
62
+ /**
63
+ * Throws if a fulfillment state transition failed.
64
+ * Ignores errors where fromState === toState (already in the desired state).
65
+ */
66
+ private throwIfTransitionError;
51
67
  }
@@ -11,9 +11,6 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
- var __importDefault = (this && this.__importDefault) || function (mod) {
15
- return (mod && mod.__esModule) ? mod : { "default": mod };
16
- };
17
14
  Object.defineProperty(exports, "__esModule", { value: true });
18
15
  exports.SendcloudService = void 0;
19
16
  const common_1 = require("@nestjs/common");
@@ -24,8 +21,6 @@ const constants_1 = require("./constants");
24
21
  const sendcloud_config_entity_1 = require("./sendcloud-config.entity");
25
22
  const sendcloud_client_1 = require("./sendcloud.client");
26
23
  const sendcloud_handler_1 = require("./sendcloud.handler");
27
- const src_1 = require("../../../util/src");
28
- const util_1 = __importDefault(require("util"));
29
24
  let SendcloudService = class SendcloudService {
30
25
  constructor(eventBus, connection,
31
26
  // private rawConnection: Connection,
@@ -93,62 +88,6 @@ let SendcloudService = class SendcloudService {
93
88
  throw err;
94
89
  }
95
90
  }
96
- /**
97
- * Update order by given orderCode, returns undefined if no action was taken
98
- * Returns order if transition was successful
99
- */
100
- async updateOrderStatus(ctx, sendcloudStatus, orderCode) {
101
- let order = await this.connection
102
- .getRepository(ctx, core_2.Order)
103
- .findOne({ where: { code: orderCode }, relations: ['lines'] });
104
- if (!order) {
105
- core_2.Logger.warn(`Cannot update status from SendCloud: No order with code ${orderCode} found`, constants_1.loggerCtx);
106
- throw Error(`Cannot update status from SendCloud: No order with code ${orderCode} found`);
107
- }
108
- if (order.state === sendcloudStatus.orderState) {
109
- return core_2.Logger.info(`Not updating order with code ${orderCode}: Order already has state ${order.state}`, constants_1.loggerCtx);
110
- }
111
- if (sendcloudStatus.orderState === 'Shipped') {
112
- await this.shipAll(ctx, order);
113
- return core_2.Logger.info(`Successfully updated order ${orderCode} to Shipped`, constants_1.loggerCtx);
114
- }
115
- order = await this.connection
116
- .getRepository(ctx, core_2.Order)
117
- .findOneOrFail({ where: { code: orderCode }, relations: ['lines'] }); // Refetch in case state was updated
118
- if (sendcloudStatus.orderState === 'Delivered') {
119
- await (0, src_1.transitionToDelivered)(this.orderService, ctx, order, {
120
- code: sendcloud_handler_1.sendcloudHandler.code,
121
- arguments: [],
122
- });
123
- return core_2.Logger.info(`Successfully updated order ${orderCode} to Delivered`, constants_1.loggerCtx);
124
- }
125
- // Fall through, means unhandled state
126
- core_2.Logger.info(`Not handling state ${sendcloudStatus.orderState}`, constants_1.loggerCtx);
127
- }
128
- /**
129
- * Ship all items
130
- */
131
- async shipAll(ctx, order) {
132
- await (0, src_1.transitionToShipped)(this.orderService, ctx, order, {
133
- code: sendcloud_handler_1.sendcloudHandler.code,
134
- arguments: [],
135
- });
136
- }
137
- /**
138
- * Fulfill without throwing errors. Logs an error if fulfilment fails
139
- */
140
- async safeFulfill(ctx, order) {
141
- try {
142
- const fulfillment = await (0, src_1.fulfillAll)(ctx, this.orderService, order, {
143
- code: sendcloud_handler_1.sendcloudHandler.code,
144
- arguments: [],
145
- });
146
- core_2.Logger.info(`Created fulfillment (${fulfillment.id}) for order ${order.code}`, constants_1.loggerCtx);
147
- }
148
- catch (e) {
149
- core_2.Logger.error(`Failed to fulfill order ${order.code}: ${e?.message}. Transition this order manually to 'Delivered' after checking that it exists in Sendcloud.`, constants_1.loggerCtx, util_1.default.inspect(e));
150
- }
151
- }
152
91
  async upsertConfig(ctx, config) {
153
92
  const repo = this.connection.getRepository(ctx, sendcloud_config_entity_1.SendcloudConfigEntity);
154
93
  const existing = await repo.findOne({
@@ -216,7 +155,6 @@ let SendcloudService = class SendcloudService {
216
155
  if (!hasSendcloudHandler) {
217
156
  return core_2.Logger.info(`Order ${order.code} does not have SendCloud set as handler. Not syncing this order.`, constants_1.loggerCtx);
218
157
  }
219
- await this.safeFulfill(ctx, order);
220
158
  core_2.Logger.info(`Syncing order ${orderCode} for channel ${ctx.channel.token}`, constants_1.loggerCtx);
221
159
  const result = await this.createOrderInSendcloud(ctx, order);
222
160
  if (result) {
@@ -238,6 +176,160 @@ let SendcloudService = class SendcloudService {
238
176
  },
239
177
  }, false);
240
178
  }
179
+ /**
180
+ * Find orders placed within the last N days that use the SendCloud fulfillment handler
181
+ * and process them based on their current state:
182
+ * - PaymentAuthorized: create fulfillment only (no shipping transitions)
183
+ * - PaymentSettled: fulfill + transition to Shipped + transition to Delivered
184
+ * - Shipped: transition existing fulfillment(s) to Delivered
185
+ *
186
+ * Intended to be called by the `fulfillSettledOrdersTask` scheduled task.
187
+ */
188
+ async fulfillPlacedOrders(settledSinceDays) {
189
+ const ctx = await this.createContext((await this.channelService.getDefaultChannel()).token);
190
+ const channels = await this.channelService.findAll(ctx);
191
+ const summary = { fulfilled: 0, skipped: 0, failed: 0 };
192
+ const sinceDate = new Date();
193
+ sinceDate.setDate(sinceDate.getDate() - settledSinceDays);
194
+ for (const channel of channels.items) {
195
+ const channelCtx = new core_2.RequestContext({
196
+ apiType: 'admin',
197
+ channel,
198
+ isAuthorized: true,
199
+ authorizedAsOwnerOnly: false,
200
+ });
201
+ let skip = 0;
202
+ const take = 100;
203
+ while (true) {
204
+ const orderList = await this.orderService.findAll(channelCtx, {
205
+ filter: {
206
+ state: {
207
+ in: ['PaymentAuthorized', 'PaymentSettled', 'Shipped'],
208
+ },
209
+ orderPlacedAt: { after: sinceDate.toISOString() },
210
+ },
211
+ skip,
212
+ take,
213
+ }, ['lines', 'shippingLines', 'shippingLines.shippingMethod']);
214
+ for (const order of orderList.items) {
215
+ const hasSendcloudHandler = order.shippingLines?.find((line) => line.shippingMethod?.fulfillmentHandlerCode ===
216
+ sendcloud_handler_1.sendcloudHandler.code);
217
+ if (!hasSendcloudHandler) {
218
+ summary.skipped++;
219
+ continue;
220
+ }
221
+ try {
222
+ if (order.state === 'PaymentAuthorized') {
223
+ await this.createFulfillmentOnly(channelCtx, order);
224
+ core_2.Logger.info(`Created fulfillment for authorized order ${order.code}`, constants_1.loggerCtx);
225
+ summary.fulfilled++;
226
+ continue;
227
+ }
228
+ // Handle Shipped and PaymentSettled orders the same: fulfill + transition to Delivered
229
+ await this.fulfillOrderToDelivered(channelCtx, order);
230
+ core_2.Logger.info(`Fulfilled order ${order.code} (state: ${order.state}) to Delivered`, constants_1.loggerCtx);
231
+ summary.fulfilled++;
232
+ }
233
+ catch (e) {
234
+ summary.failed++;
235
+ core_2.Logger.error(`Failed to process order ${order.code} (${order.state}): ${e?.message}`, constants_1.loggerCtx);
236
+ }
237
+ }
238
+ if (orderList.items.length < take) {
239
+ break;
240
+ }
241
+ skip += take;
242
+ }
243
+ }
244
+ core_2.Logger.info(`Finished: ${summary.fulfilled} fulfilled, ${summary.skipped} skipped, ${summary.failed} failed`, constants_1.loggerCtx);
245
+ return summary;
246
+ }
247
+ /**
248
+ * Create a fulfillment for all order lines without any state transitions.
249
+ * Used for PaymentAuthorized orders where we only want to register the fulfillment.
250
+ */
251
+ async createFulfillmentOnly(ctx, order) {
252
+ if (!order.lines?.length) {
253
+ const fullOrder = await this.orderService.findOne(ctx, order.id, [
254
+ 'lines',
255
+ ]);
256
+ if (!fullOrder) {
257
+ throw new Error(`Order ${order.code} not found`);
258
+ }
259
+ order = fullOrder;
260
+ }
261
+ const lines = order.lines.map((line) => ({
262
+ orderLineId: line.id,
263
+ quantity: line.quantity,
264
+ }));
265
+ const fulfillmentResult = await this.orderService.createFulfillment(ctx, {
266
+ handler: { code: sendcloud_handler_1.sendcloudHandler.code, arguments: [] },
267
+ lines,
268
+ });
269
+ if (fulfillmentResult.errorCode ===
270
+ 'ITEMS_ALREADY_FULFILLED_ERROR') {
271
+ return; // Already fulfilled, nothing to do
272
+ }
273
+ const error = fulfillmentResult;
274
+ if (error.errorCode) {
275
+ throw new Error(`${error.errorCode}: ${error.message}`);
276
+ }
277
+ }
278
+ /**
279
+ * Fulfill all order lines and transition the fulfillment to Shipped then Delivered.
280
+ * Also works for orders that already have a fulfillment and/or are already Shipped
281
+ */
282
+ async fulfillOrderToDelivered(ctx, order) {
283
+ if (!order.lines?.length) {
284
+ const fullOrder = await this.orderService.findOne(ctx, order.id, [
285
+ 'lines',
286
+ ]);
287
+ if (!fullOrder) {
288
+ throw new Error(`Order ${order.code} not found`);
289
+ }
290
+ order = fullOrder;
291
+ }
292
+ const lines = order.lines.map((line) => ({
293
+ orderLineId: line.id,
294
+ quantity: line.quantity,
295
+ }));
296
+ const fulfillmentResult = await this.orderService.createFulfillment(ctx, {
297
+ handler: { code: sendcloud_handler_1.sendcloudHandler.code, arguments: [] },
298
+ lines,
299
+ });
300
+ let fulfillment;
301
+ if (fulfillmentResult.errorCode ===
302
+ 'ITEMS_ALREADY_FULFILLED_ERROR') {
303
+ const fulfillments = await this.orderService.getOrderFulfillments(ctx, order);
304
+ fulfillment = fulfillments[0];
305
+ }
306
+ else {
307
+ const error = fulfillmentResult;
308
+ if (error.errorCode) {
309
+ throw new Error(`${error.errorCode}: ${error.message}`);
310
+ }
311
+ fulfillment = fulfillmentResult;
312
+ }
313
+ if (order.state !== 'Delivered') {
314
+ const shippedResult = await this.orderService.transitionFulfillmentToState(ctx, fulfillment.id, 'Shipped');
315
+ this.throwIfTransitionError(shippedResult);
316
+ }
317
+ const deliveredResult = await this.orderService.transitionFulfillmentToState(ctx, fulfillment.id, 'Delivered');
318
+ this.throwIfTransitionError(deliveredResult);
319
+ }
320
+ /**
321
+ * Throws if a fulfillment state transition failed.
322
+ * Ignores errors where fromState === toState (already in the desired state).
323
+ */
324
+ throwIfTransitionError(result) {
325
+ const stateError = result;
326
+ if (stateError.transitionError) {
327
+ if (stateError.fromState === stateError.toState) {
328
+ return;
329
+ }
330
+ throw new Error(`Transition error: ${stateError.fromState} -> ${stateError.toState}: ${stateError.transitionError}`);
331
+ }
332
+ }
241
333
  };
242
334
  exports.SendcloudService = SendcloudService;
243
335
  exports.SendcloudService = SendcloudService = __decorate([
@@ -75,8 +75,3 @@ export interface Status {
75
75
  id: number;
76
76
  message: string;
77
77
  }
78
- export interface IncomingWebhookBody {
79
- action: 'parcel_status_changed' | string;
80
- timestamp: number;
81
- parcel?: Parcel;
82
- }
@@ -1,14 +1,5 @@
1
1
  import { Injector, Order, OrderLine, RequestContext } from '@vendure/core';
2
2
  import { ParcelInputItem } from './sendcloud-api.types';
3
- export interface SendcloudParcelStatus {
4
- id: number;
5
- message: string;
6
- /**
7
- * Corresponding orderState for the sendcloud status
8
- */
9
- orderState?: 'Shipped' | 'Delivered' | 'Cancelled';
10
- }
11
- export declare const sendcloudStates: SendcloudParcelStatus[];
12
3
  export interface SendcloudPluginOptions {
13
4
  /**
14
5
  * Function to specify the weight of a parcelItem
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ import { ScheduledTask } from '@vendure/core';
2
+ /**
3
+ * Scheduled task that finds all PaymentSettled orders placed within the last N days
4
+ * that use the SendCloud fulfillment handler, and transitions them to Delivered.
5
+ *
6
+ * This task is opt-in: add it to `schedulerOptions.tasks` in your Vendure config.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { fulfillSettledOrdersTask } from '@pinelab/vendure-plugin-sendcloud';
11
+ *
12
+ * const config: VendureConfig = {
13
+ * schedulerOptions: {
14
+ * tasks: [
15
+ * fulfillSettledOrdersTask.configure({
16
+ * params: { settledSinceDays: 14 },
17
+ * }),
18
+ * ],
19
+ * },
20
+ * };
21
+ * ```
22
+ */
23
+ export declare const fulfillSettledOrdersTask: ScheduledTask<{
24
+ /** Number of days to look back for settled orders */
25
+ settledSinceDays: number;
26
+ }>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fulfillSettledOrdersTask = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ const sendcloud_service_1 = require("../api/sendcloud.service");
6
+ /**
7
+ * Scheduled task that finds all PaymentSettled orders placed within the last N days
8
+ * that use the SendCloud fulfillment handler, and transitions them to Delivered.
9
+ *
10
+ * This task is opt-in: add it to `schedulerOptions.tasks` in your Vendure config.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { fulfillSettledOrdersTask } from '@pinelab/vendure-plugin-sendcloud';
15
+ *
16
+ * const config: VendureConfig = {
17
+ * schedulerOptions: {
18
+ * tasks: [
19
+ * fulfillSettledOrdersTask.configure({
20
+ * params: { settledSinceDays: 14 },
21
+ * }),
22
+ * ],
23
+ * },
24
+ * };
25
+ * ```
26
+ */
27
+ exports.fulfillSettledOrdersTask = new core_1.ScheduledTask({
28
+ id: 'sendcloud-fulfill-settled-orders',
29
+ description: 'Fulfill settled SendCloud orders to Delivered. Only processes orders in PaymentSettled state with the SendCloud handler.',
30
+ params: {
31
+ /** Number of days to look back for settled orders */
32
+ settledSinceDays: 90,
33
+ },
34
+ schedule: (cron) => cron.everyDayAt(2, 0),
35
+ async execute({ injector, params }) {
36
+ return injector
37
+ .get(sendcloud_service_1.SendcloudService)
38
+ .fulfillPlacedOrders(params.settledSinceDays);
39
+ },
40
+ });
@@ -3,3 +3,4 @@ export * from './api/sendcloud.handler';
3
3
  export * from './api/additional-parcel-input-items';
4
4
  export * from './api/types/sendcloud.types';
5
5
  export * from './api/types/sendcloud-api.types';
6
+ export * from './config/fulfill-settled-orders-task';
@@ -19,3 +19,4 @@ __exportStar(require("./api/sendcloud.handler"), exports);
19
19
  __exportStar(require("./api/additional-parcel-input-items"), exports);
20
20
  __exportStar(require("./api/types/sendcloud.types"), exports);
21
21
  __exportStar(require("./api/types/sendcloud-api.types"), exports);
22
+ __exportStar(require("./config/fulfill-settled-orders-task"), exports);
@@ -17,10 +17,8 @@ const path_1 = __importDefault(require("path"));
17
17
  const sendcloud_resolver_1 = require("./api/sendcloud.resolver");
18
18
  const sendcloud_service_1 = require("./api/sendcloud.service");
19
19
  const constants_1 = require("./api/constants");
20
- const sendcloud_controller_1 = require("./api/sendcloud.controller");
21
20
  const sendcloud_config_entity_1 = require("./api/sendcloud-config.entity");
22
21
  const sendcloud_handler_1 = require("./api/sendcloud.handler");
23
- const raw_body_middleware_1 = require("../../util/src/raw-body.middleware");
24
22
  let SendcloudPlugin = SendcloudPlugin_1 = class SendcloudPlugin {
25
23
  static init(options) {
26
24
  this.options = options;
@@ -80,18 +78,12 @@ exports.SendcloudPlugin = SendcloudPlugin = SendcloudPlugin_1 = __decorate([
80
78
  },
81
79
  ],
82
80
  imports: [core_1.PluginCommonModule],
83
- controllers: [sendcloud_controller_1.SendcloudController],
84
81
  entities: [sendcloud_config_entity_1.SendcloudConfigEntity],
85
82
  configuration: (config) => {
86
- config.apiOptions.middleware.push({
87
- route: '/sendcloud/webhook*splat',
88
- handler: raw_body_middleware_1.rawBodyMiddleware,
89
- beforeListen: true,
90
- });
91
83
  config.shippingOptions.fulfillmentHandlers.push(sendcloud_handler_1.sendcloudHandler);
92
84
  config.authOptions.customPermissions.push(sendcloud_resolver_1.sendcloudPermission);
93
85
  return config;
94
86
  },
95
- compatibility: '>=3.2.0',
87
+ compatibility: '>=3.1.0',
96
88
  })
97
89
  ], SendcloudPlugin);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinelab/vendure-plugin-sendcloud",
3
- "version": "1.6.0",
3
+ "version": "2.0.0",
4
4
  "description": "Vendure plugin for syncing orders with SendCloud",
5
5
  "author": "Martijn van de Brug <martijn@pinelab.studio>",
6
6
  "homepage": "https://plugins.pinelab.studio/",
@@ -10,18 +10,18 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "main": "dist/vendure-plugin-sendcloud/src/index.js",
14
- "types": "dist/vendure-plugin-sendcloud/src/index.d.ts",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
17
  "README.md",
18
18
  "CHANGELOG.md"
19
19
  ],
20
20
  "scripts": {
21
- "build": "rimraf dist && tsc && copyfiles -u 1 'src/ui/**/*' dist/vendure-plugin-sendcloud/src/",
21
+ "build": "rimraf dist && tsc && copyfiles -u 1 'src/ui/**/*' dist/",
22
22
  "test": "vitest run",
23
23
  "start": "yarn ts-node test/dev-server.ts",
24
24
  "lint": "echo 'No linting configured'"
25
25
  },
26
- "gitHead": "349500e8984c828a399d21bd63bb8eb11aa790e8"
26
+ "gitHead": "476f36da3aafea41fbf21c70774a30306f1d238f"
27
27
  }
@@ -1 +0,0 @@
1
- export * from './order-state-util';
@@ -1,17 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./order-state-util"), exports);
@@ -1,22 +0,0 @@
1
- import { FulfillmentStateTransitionError, Order, OrderService, RequestContext } from '@vendure/core';
2
- import { AddFulfillmentToOrderResult, ConfigurableOperationInput } from '@vendure/common/lib/generated-types';
3
- import { Fulfillment } from '@vendure/core/dist/entity/fulfillment/fulfillment.entity';
4
- export declare const loggerCtx = "OrderStateUtil";
5
- /**
6
- * Create a fulfillment for all orderlines. Returns fulfillments[0] if already fulfilled
7
- */
8
- export declare function fulfillAll(ctx: RequestContext, orderService: OrderService, order: Order, handler: ConfigurableOperationInput): Promise<Fulfillment>;
9
- /**
10
- * Fulfills all items to shipped using transitionFulfillmentToState
11
- */
12
- export declare function transitionToShipped(orderService: OrderService, ctx: RequestContext, order: Order, handler: ConfigurableOperationInput): Promise<Fulfillment | FulfillmentStateTransitionError>;
13
- /**
14
- * Fulfills all items to shipped, then to delivered using transitionFulfillmentToState
15
- */
16
- export declare function transitionToDelivered(orderService: OrderService, ctx: RequestContext, order: Order, handler: ConfigurableOperationInput): Promise<(Fulfillment | FulfillmentStateTransitionError)[]>;
17
- /**
18
- * Throws the error result if the transition failed
19
- * Ignores transition errors where from and to state are the same,
20
- * because that still results in the situation we want
21
- */
22
- export declare function throwIfTransitionFailed(result: FulfillmentStateTransitionError | Fulfillment | AddFulfillmentToOrderResult): void;
@@ -1,78 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loggerCtx = void 0;
4
- exports.fulfillAll = fulfillAll;
5
- exports.transitionToShipped = transitionToShipped;
6
- exports.transitionToDelivered = transitionToDelivered;
7
- exports.throwIfTransitionFailed = throwIfTransitionFailed;
8
- const core_1 = require("@vendure/core");
9
- exports.loggerCtx = 'OrderStateUtil';
10
- /**
11
- * Create a fulfillment for all orderlines. Returns fulfillments[0] if already fulfilled
12
- */
13
- async function fulfillAll(ctx, orderService, order, handler) {
14
- const lines = order.lines.map((line) => ({
15
- orderLineId: line.id,
16
- quantity: line.quantity,
17
- }));
18
- const fulfillment = await orderService.createFulfillment(ctx, {
19
- handler,
20
- lines,
21
- });
22
- if (fulfillment.errorCode ===
23
- 'ITEMS_ALREADY_FULFILLED_ERROR') {
24
- const fulfillments = await orderService.getOrderFulfillments(ctx, order);
25
- const fulfillment = fulfillments[0];
26
- core_1.Logger.info(`Items already fulfilled for order ${order.code}. Returning fulfillment '${fulfillment.id}'. Existing fulfillments: ${fulfillments.map((f) => f.id).join(', ')}`, exports.loggerCtx);
27
- return fulfillment;
28
- }
29
- throwIfTransitionFailed(fulfillment);
30
- return fulfillment;
31
- }
32
- /**
33
- * Fulfills all items to shipped using transitionFulfillmentToState
34
- */
35
- async function transitionToShipped(orderService, ctx, order, handler) {
36
- const fulfillment = await fulfillAll(ctx, orderService, order, handler);
37
- const result = await orderService.transitionFulfillmentToState(ctx, fulfillment.id, 'Shipped');
38
- throwIfTransitionFailed(result);
39
- return result;
40
- }
41
- /**
42
- * Fulfills all items to shipped, then to delivered using transitionFulfillmentToState
43
- */
44
- async function transitionToDelivered(orderService, ctx, order, handler) {
45
- const shippedResult = await transitionToShipped(orderService, ctx, order, handler);
46
- let fulfillments = [];
47
- if (shippedResult.errorCode) {
48
- fulfillments = await orderService.getOrderFulfillments(ctx, order);
49
- }
50
- else {
51
- // if not an error, shippedResult is the only fulfillment
52
- fulfillments.push(shippedResult);
53
- }
54
- const results = [];
55
- for (const fulfillment of fulfillments) {
56
- const result = await orderService.transitionFulfillmentToState(ctx, fulfillment.id, 'Delivered');
57
- throwIfTransitionFailed(result);
58
- results.push(result);
59
- }
60
- return results;
61
- }
62
- /**
63
- * Throws the error result if the transition failed
64
- * Ignores transition errors where from and to state are the same,
65
- * because that still results in the situation we want
66
- */
67
- function throwIfTransitionFailed(result) {
68
- const stateError = result;
69
- if (stateError.transitionError &&
70
- stateError.fromState === stateError.toState) {
71
- return; // If already 'Shipped', don't count this as an error
72
- }
73
- // It's not a stateTransition error
74
- const error = result;
75
- if (error.errorCode) {
76
- throw error;
77
- }
78
- }
@@ -1,9 +0,0 @@
1
- import type { Request } from 'express';
2
- export interface RequestWithRawBody extends Request {
3
- rawBody: Buffer;
4
- }
5
- /**
6
- * Middleware which adds the raw request body to the incoming message object. This is needed by
7
- * Stripe to properly verify webhook events.
8
- */
9
- export declare const rawBodyMiddleware: import("connect").NextHandleFunction;
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rawBodyMiddleware = void 0;
4
- const body_parser_1 = require("body-parser");
5
- /**
6
- * Middleware which adds the raw request body to the incoming message object. This is needed by
7
- * Stripe to properly verify webhook events.
8
- */
9
- exports.rawBodyMiddleware = (0, body_parser_1.raw)({
10
- type: '*/*',
11
- verify(req, res, buf, encoding) {
12
- if (Buffer.isBuffer(buf)) {
13
- req.rawBody = Buffer.from(buf);
14
- }
15
- return true;
16
- },
17
- });
@@ -1,7 +0,0 @@
1
- import { Request } from 'express';
2
- import { SendcloudService } from './sendcloud.service';
3
- export declare class SendcloudController {
4
- private sendcloudService;
5
- constructor(sendcloudService: SendcloudService);
6
- webhook(req: Request, signature: string, channelToken: string): Promise<unknown>;
7
- }
@@ -1,80 +0,0 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.SendcloudController = void 0;
16
- const common_1 = require("@nestjs/common");
17
- const sendcloud_service_1 = require("./sendcloud.service");
18
- const sendcloud_client_1 = require("./sendcloud.client");
19
- const core_1 = require("@vendure/core");
20
- const constants_1 = require("./constants");
21
- const sendcloud_types_1 = require("./types/sendcloud.types");
22
- const util_1 = require("util");
23
- let SendcloudController = class SendcloudController {
24
- constructor(sendcloudService) {
25
- this.sendcloudService = sendcloudService;
26
- }
27
- async webhook(req, signature, channelToken) {
28
- let body;
29
- if (!Buffer.isBuffer(req.body)) {
30
- core_1.Logger.warn(`Incoming webhook body is not a Buffer. This means the body was already parsed by some other middleware. This might cause problems when validating the incoming webhook signature.`, constants_1.loggerCtx);
31
- body = req.body;
32
- }
33
- else {
34
- // Else we try and parse the body
35
- try {
36
- body = JSON.parse(req.body.toString());
37
- }
38
- catch (e) {
39
- core_1.Logger.error(`Error parsing incoming webhook body: ${e?.message ?? e}`, constants_1.loggerCtx, (0, util_1.inspect)(req.body));
40
- return;
41
- }
42
- }
43
- const rawBody = req.rawBody;
44
- const ctx = await this.sendcloudService.createContext(channelToken);
45
- const { client } = await this.sendcloudService.getClient(ctx);
46
- if (!client.isValidWebhook(rawBody, signature)) {
47
- core_1.Logger.warn(`Ignoring incoming webhook for channel ${channelToken}, because it has an invalid signature`, constants_1.loggerCtx);
48
- return;
49
- }
50
- if (body.action !== 'parcel_status_changed') {
51
- return core_1.Logger.info(`Incoming webhook: ${body.action}. skipping...`, constants_1.loggerCtx);
52
- }
53
- core_1.Logger.info(`Incoming Sendcloud webhook: ${body.action} - ${body.parcel?.id} - ${body.parcel?.order_number} - ${body.parcel?.status.id} (${body.parcel?.status.message})`, constants_1.loggerCtx);
54
- const status = sendcloud_types_1.sendcloudStates.find((s) => s.id === body.parcel?.status?.id);
55
- if (!status) {
56
- return core_1.Logger.warn(`Unknown SendCloud status "${body.parcel?.status?.message}", not handling this webhook.`, constants_1.loggerCtx);
57
- }
58
- if (!status.orderState) {
59
- return core_1.Logger.info(`Ignoring incoming webhook status "${body.parcel?.status?.message}", because we don't update Vendure order status for this sendcloud status.`, constants_1.loggerCtx);
60
- }
61
- if (!body.parcel?.order_number) {
62
- return core_1.Logger.warn(`No order_number in incoming Sendcloud webhook: ${JSON.stringify(body.parcel)}`, constants_1.loggerCtx);
63
- }
64
- await this.sendcloudService.updateOrderStatus(ctx, status, body.parcel.order_number);
65
- }
66
- };
67
- exports.SendcloudController = SendcloudController;
68
- __decorate([
69
- (0, common_1.Post)('webhook/:channelToken'),
70
- __param(0, (0, common_1.Req)()),
71
- __param(1, (0, common_1.Headers)(sendcloud_client_1.SendcloudClient.signatureHeader)),
72
- __param(2, (0, common_1.Param)('channelToken')),
73
- __metadata("design:type", Function),
74
- __metadata("design:paramtypes", [Object, String, String]),
75
- __metadata("design:returntype", Promise)
76
- ], SendcloudController.prototype, "webhook", null);
77
- exports.SendcloudController = SendcloudController = __decorate([
78
- (0, common_1.Controller)('sendcloud'),
79
- __metadata("design:paramtypes", [sendcloud_service_1.SendcloudService])
80
- ], SendcloudController);
@@ -1,157 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendcloudStates = void 0;
4
- exports.sendcloudStates = [
5
- {
6
- id: 62989,
7
- message: 'The package has been held at customs',
8
- },
9
- {
10
- id: 6,
11
- message: 'Not sorted',
12
- orderState: 'Shipped',
13
- },
14
- {
15
- id: 15,
16
- message: 'Error collecting',
17
- },
18
- {
19
- id: 62990,
20
- message: 'The package is in the sorting centre',
21
- orderState: 'Shipped',
22
- },
23
- {
24
- id: 62991,
25
- message: 'The package was refused by the recipient when delivered',
26
- },
27
- {
28
- id: 62992,
29
- message: 'The package was returned to the sender due to an issue',
30
- },
31
- {
32
- id: 62993,
33
- message: 'The delivery method was changed by the request of the recipient or due to other circumstances.',
34
- },
35
- {
36
- id: 1002,
37
- message: 'Announcement failed',
38
- },
39
- {
40
- id: 1999,
41
- message: 'Cancellation requested',
42
- },
43
- {
44
- id: 62994,
45
- message: 'The delivery date was changed by the request of the recipient or due to other.',
46
- },
47
- {
48
- id: 62995,
49
- message: 'The delivery address was changed by the request of the recipient or due to other circumstances.',
50
- },
51
- {
52
- id: 62996,
53
- message: 'For unusual cases: lost, damaged, destroyed, etc.',
54
- },
55
- {
56
- id: 1998,
57
- message: 'Cancelled upstream',
58
- orderState: 'Cancelled',
59
- },
60
- {
61
- id: 1000,
62
- message: 'Ready to send',
63
- orderState: 'Shipped',
64
- },
65
- {
66
- id: 62997,
67
- message: 'The address is incorrect and the carrier needs address correction from the sender or the recipient.',
68
- },
69
- {
70
- id: 12,
71
- message: 'Awaiting customer pickup',
72
- orderState: 'Shipped',
73
- },
74
- {
75
- id: 11,
76
- message: 'Delivered',
77
- orderState: 'Delivered',
78
- },
79
- {
80
- id: 93,
81
- message: 'Shipment collected by customer',
82
- orderState: 'Delivered',
83
- },
84
- {
85
- id: 91,
86
- message: 'Parcel en route',
87
- orderState: 'Shipped',
88
- },
89
- {
90
- id: 80,
91
- message: 'Unable to deliver',
92
- },
93
- {
94
- id: 22,
95
- message: 'Shipment picked up by driver',
96
- orderState: 'Shipped',
97
- },
98
- {
99
- id: 13,
100
- message: 'Announced: not collected',
101
- },
102
- {
103
- id: 8,
104
- message: 'Delivery attempt failed',
105
- },
106
- {
107
- id: 7,
108
- message: 'Being sorted',
109
- orderState: 'Shipped',
110
- },
111
- {
112
- id: 5,
113
- message: 'Sorted',
114
- orderState: 'Shipped',
115
- },
116
- {
117
- id: 4,
118
- message: 'Delivery delayed',
119
- },
120
- {
121
- id: 3,
122
- message: 'En route to sorting center',
123
- orderState: 'Shipped',
124
- },
125
- {
126
- id: 1,
127
- message: 'Announced',
128
- orderState: 'Shipped',
129
- },
130
- {
131
- id: 1337,
132
- message: 'Unknown status - check carrier track & trace page for more insights',
133
- },
134
- {
135
- id: 999,
136
- message: 'No label',
137
- },
138
- {
139
- id: 1001,
140
- message: 'Being announced',
141
- orderState: 'Shipped',
142
- },
143
- {
144
- id: 2000,
145
- message: 'Cancelled',
146
- orderState: 'Cancelled',
147
- },
148
- {
149
- id: 2001,
150
- message: 'Submitting cancellation request',
151
- },
152
- {
153
- id: 92,
154
- message: 'Driver en route',
155
- orderState: 'Shipped',
156
- },
157
- ];