@pinelab/vendure-plugin-sendcloud 0.0.3

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/README.md +87 -0
  2. package/dist/util/src/index.d.ts +1 -0
  3. package/dist/util/src/index.js +17 -0
  4. package/dist/util/src/order-state-util.d.ts +19 -0
  5. package/dist/util/src/order-state-util.js +62 -0
  6. package/dist/util/src/raw-body.d.ts +6 -0
  7. package/dist/util/src/raw-body.js +26 -0
  8. package/dist/vendure-plugin-sendcloud/src/api/additional-parcel-input-items.d.ts +10 -0
  9. package/dist/vendure-plugin-sendcloud/src/api/additional-parcel-input-items.js +47 -0
  10. package/dist/vendure-plugin-sendcloud/src/api/constants.d.ts +2 -0
  11. package/dist/vendure-plugin-sendcloud/src/api/constants.js +5 -0
  12. package/dist/vendure-plugin-sendcloud/src/api/sendcloud-config.entity.d.ts +8 -0
  13. package/dist/vendure-plugin-sendcloud/src/api/sendcloud-config.entity.js +40 -0
  14. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.adapter.d.ts +11 -0
  15. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.adapter.js +62 -0
  16. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.client.d.ts +18 -0
  17. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.client.js +59 -0
  18. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.controller.d.ts +8 -0
  19. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.controller.js +65 -0
  20. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.handler.d.ts +7 -0
  21. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.handler.js +28 -0
  22. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.resolver.d.ts +16 -0
  23. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.resolver.js +75 -0
  24. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.service.d.ts +48 -0
  25. package/dist/vendure-plugin-sendcloud/src/api/sendcloud.service.js +245 -0
  26. package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud-api.types.d.ts +82 -0
  27. package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud-api.types.js +2 -0
  28. package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud.types.d.ts +41 -0
  29. package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud.types.js +157 -0
  30. package/dist/vendure-plugin-sendcloud/src/index.d.ts +5 -0
  31. package/dist/vendure-plugin-sendcloud/src/index.js +21 -0
  32. package/dist/vendure-plugin-sendcloud/src/sendcloud.plugin.d.ts +7 -0
  33. package/dist/vendure-plugin-sendcloud/src/sendcloud.plugin.js +94 -0
  34. package/dist/vendure-plugin-sendcloud/src/ui/history-entry.component.ts +69 -0
  35. package/dist/vendure-plugin-sendcloud/src/ui/queries.ts +23 -0
  36. package/dist/vendure-plugin-sendcloud/src/ui/sendcloud-nav.module.ts +29 -0
  37. package/dist/vendure-plugin-sendcloud/src/ui/sendcloud.component.ts +112 -0
  38. package/dist/vendure-plugin-sendcloud/src/ui/sendcloud.module.ts +21 -0
  39. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Vendure SendCloud plugin
2
+
3
+ ![Vendure version](https://img.shields.io/npm/dependency-version/vendure-plugin-sendcloud/dev/@vendure/core)
4
+
5
+ ### Visit [pinelab-plugins.com](https://pinelab-plugins.com/plugin/vendure-plugin-sendcloud) for more documentation and examples.
6
+
7
+ This plugin syncs orders to the SendCloud fulfillment platform.
8
+
9
+ ## Getting started
10
+
11
+ 1. Add the plugin to your `vendure-config.ts`:
12
+
13
+ ```ts
14
+ plugins: [
15
+ SendcloudPlugin.init({}),
16
+ ...
17
+ ]
18
+ ```
19
+
20
+ 2. Add the SendCloud Ui to the Vendure admin:
21
+
22
+ ```ts
23
+ AdminUiPlugin.init({
24
+ port: 3002,
25
+ route: 'admin',
26
+ app: compileUiExtensions({
27
+ outputPath: path.join(__dirname, '__admin-ui'),
28
+ extensions: [SendcloudPlugin.ui],
29
+ }),
30
+ }),
31
+ ```
32
+
33
+ 3. Run a DB [migration](https://www.vendure.io/docs/developer-guide/migrations/) to add the new SendCloudConfigEntity to
34
+ the database.
35
+ 4. Go to your SendCloud account and go to `Settings > Integrations` and create an integration.
36
+ 5. Write down the `secret` and `publicKey` of the created integration
37
+ 6. For the same integration, add the webhook `https://your-vendure-domain.io/sendcloud/webhook/your-channel-token`. This
38
+ will update orders when the status changes in SendCloud.
39
+ 7. Start Vendure and login as admin
40
+ 8. Make sure you have the permission `SetSendCloudConfig`
41
+ 9. Go to `Settings > SendCloud`
42
+ 10. You can fill in your SendCloud `secret` and `public key` here and click save.
43
+ 11. Additionally, you can set a fallback phone number, for when a customer hasn't filled out one. A phone number is
44
+ required by Sendcloud in some cases.
45
+
46
+ Now, when an order is placed, it will be automatically fulfilled and send to SendCloud.
47
+
48
+ ## Additional configuration
49
+
50
+ You can choose to send additional info to SendCloud: `weight`, `hsCode`, `origin_country` and additional parcel items.
51
+ Parcel items will show up as rows on your SendCloud packaging slips.
52
+
53
+ ```ts
54
+ import 'SendCloudPlugin, getNrOfOrders';
55
+
56
+ from;
57
+ ('vendure-plugin-sendcloud');
58
+ plugins: [
59
+ SendcloudPlugin.init({
60
+ /**
61
+ * Implement the weightFn to determine the weight of a parcel item,
62
+ * or set a default value
63
+ */
64
+ weightFn: (line) =>
65
+ (line.productVariant.product?.customFields as any)?.weight || 5,
66
+ /**
67
+ * Implement the hsCodeFn to set the hsCode of a parcel item,
68
+ * or set a default value
69
+ */
70
+ hsCodeFn: (line) =>
71
+ (line.productVariant.product?.customFields as any)?.hsCode || 'test hs',
72
+ /**
73
+ * Implement the originCountryFn to set the origin_country of a parcel item,
74
+ * or set a default value
75
+ */
76
+ originCountryFn: (line) => 'NL',
77
+ /**
78
+ * Implement the additionalParcelItemsFn to add additional rows to the SendCloud order.
79
+ * This example adds the nr of previous orders of the current customer to SendCloud
80
+ */
81
+ additionalParcelItemsFn: async (ctx, injector, order) => {
82
+ additionalInputs.push(await getNrOfOrders(ctx, injector, order));
83
+ return additionalInputs;
84
+ },
85
+ }),
86
+ ];
87
+ ```
@@ -0,0 +1 @@
1
+ export * from './order-state-util';
@@ -0,0 +1,17 @@
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);
@@ -0,0 +1,19 @@
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
+ /**
5
+ * Create a fulfillment for all orderlines. Returns fulfillments[0] if already fulfilled
6
+ */
7
+ export declare function fulfillAll(ctx: RequestContext, orderService: OrderService, order: Order, handler: ConfigurableOperationInput): Promise<Fulfillment>;
8
+ /**
9
+ * Fulfills all items to shipped using transitionFulfillmentToState
10
+ */
11
+ export declare function transitionToShipped(orderService: OrderService, ctx: RequestContext, order: Order, handler: ConfigurableOperationInput): Promise<Fulfillment>;
12
+ /**
13
+ * Fulfills all items to shipped, then to delivered using transitionFulfillmentToState
14
+ */
15
+ export declare function transitionToDelivered(orderService: OrderService, ctx: RequestContext, order: Order, handler: ConfigurableOperationInput): Promise<Fulfillment>;
16
+ /**
17
+ * Throw an error if the transition failed
18
+ */
19
+ export declare function throwIfTransitionFailed(result: FulfillmentStateTransitionError | Fulfillment | AddFulfillmentToOrderResult): void;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.throwIfTransitionFailed = exports.transitionToDelivered = exports.transitionToShipped = exports.fulfillAll = void 0;
4
+ /**
5
+ * Create a fulfillment for all orderlines. Returns fulfillments[0] if already fulfilled
6
+ */
7
+ async function fulfillAll(ctx, orderService, order, handler) {
8
+ const lines = order.lines.map((line) => ({
9
+ orderLineId: line.id,
10
+ quantity: line.quantity,
11
+ }));
12
+ const fulfillment = await orderService.createFulfillment(ctx, {
13
+ handler,
14
+ lines,
15
+ });
16
+ if (fulfillment.errorCode ===
17
+ 'ITEMS_ALREADY_FULFILLED_ERROR') {
18
+ const fulfillments = await orderService.getOrderFulfillments(ctx, order);
19
+ return fulfillments[0];
20
+ }
21
+ throwIfTransitionFailed(fulfillment);
22
+ return fulfillment;
23
+ }
24
+ exports.fulfillAll = fulfillAll;
25
+ /**
26
+ * Fulfills all items to shipped using transitionFulfillmentToState
27
+ */
28
+ async function transitionToShipped(orderService, ctx, order, handler) {
29
+ const fulfillment = await fulfillAll(ctx, orderService, order, handler);
30
+ const result = await orderService.transitionFulfillmentToState(ctx, fulfillment.id, 'Shipped');
31
+ throwIfTransitionFailed(result);
32
+ return result;
33
+ }
34
+ exports.transitionToShipped = transitionToShipped;
35
+ /**
36
+ * Fulfills all items to shipped, then to delivered using transitionFulfillmentToState
37
+ */
38
+ async function transitionToDelivered(orderService, ctx, order, handler) {
39
+ const fulfillment = await transitionToShipped(orderService, ctx, order, handler);
40
+ const result = await orderService.transitionFulfillmentToState(ctx, fulfillment.id, 'Delivered');
41
+ throwIfTransitionFailed(result);
42
+ return result;
43
+ }
44
+ exports.transitionToDelivered = transitionToDelivered;
45
+ /**
46
+ * Throw an error if the transition failed
47
+ */
48
+ function throwIfTransitionFailed(result) {
49
+ const stateError = result;
50
+ if (stateError.transitionError) {
51
+ if (stateError.fromState === stateError.toState) {
52
+ return; // If already 'Shipped', don't count this as an error
53
+ }
54
+ throw Error(`${stateError.message} - from ${stateError.fromState} to ${stateError.toState} - ${stateError.transitionError}`);
55
+ }
56
+ // It's not a stateTransition error
57
+ const error = result;
58
+ if (error.errorCode) {
59
+ throw Error(`${error.errorCode}: ${error.message}`);
60
+ }
61
+ }
62
+ exports.throwIfTransitionFailed = throwIfTransitionFailed;
@@ -0,0 +1,6 @@
1
+ import { Middleware } from '@vendure/core';
2
+ /**
3
+ * Set req.rawBody before any other middleware changes the body. Used to verify incoming request signatures for example.
4
+ * @param route
5
+ */
6
+ export declare function createRawBodyMiddleWare(route: string): Middleware;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createRawBodyMiddleWare = void 0;
7
+ const body_parser_1 = __importDefault(require("body-parser"));
8
+ /**
9
+ * Set req.rawBody before any other middleware changes the body. Used to verify incoming request signatures for example.
10
+ * @param route
11
+ */
12
+ function createRawBodyMiddleWare(route) {
13
+ return {
14
+ route,
15
+ beforeListen: true,
16
+ handler: body_parser_1.default.json({
17
+ verify(req, _, buf) {
18
+ if (Buffer.isBuffer(buf)) {
19
+ req.rawBody = Buffer.from(buf);
20
+ }
21
+ return true;
22
+ },
23
+ }),
24
+ };
25
+ }
26
+ exports.createRawBodyMiddleWare = createRawBodyMiddleWare;
@@ -0,0 +1,10 @@
1
+ import { Injector, Order, RequestContext } from '@vendure/core';
2
+ import { ParcelInputItem } from './types/sendcloud-api.types';
3
+ /**
4
+ * Add nr of previously placed orders for this customer as parcelItem
5
+ */
6
+ export declare function getNrOfOrders(ctx: RequestContext, injector: Injector, order: Order): Promise<ParcelInputItem>;
7
+ /**
8
+ * Return couponCodes as Sendcloud parcelInputItem
9
+ */
10
+ export declare function getCouponCodes(order: Order): ParcelInputItem | undefined;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCouponCodes = exports.getNrOfOrders = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ /**
6
+ * Add nr of previously placed orders for this customer as parcelItem
7
+ */
8
+ async function getNrOfOrders(ctx, injector, order) {
9
+ let nrOfOrders = 0;
10
+ if (order.customer?.id) {
11
+ const orders = await injector
12
+ .get(core_1.TransactionalConnection)
13
+ .getRepository(ctx, core_1.Order)
14
+ .find({
15
+ where: {
16
+ customer: { id: order.customer.id },
17
+ state: 'Delivered',
18
+ },
19
+ });
20
+ nrOfOrders = orders.length;
21
+ }
22
+ return {
23
+ description: String(nrOfOrders),
24
+ quantity: 1,
25
+ weight: '0.1',
26
+ sku: `Nr of orders`,
27
+ value: '0',
28
+ };
29
+ }
30
+ exports.getNrOfOrders = getNrOfOrders;
31
+ /**
32
+ * Return couponCodes as Sendcloud parcelInputItem
33
+ */
34
+ function getCouponCodes(order) {
35
+ if (!order.couponCodes || order.couponCodes.length === 0) {
36
+ return;
37
+ }
38
+ const couponCodesString = order.couponCodes.join(',');
39
+ return {
40
+ description: couponCodesString,
41
+ quantity: 1,
42
+ weight: '0.1',
43
+ sku: `Couponcodes`,
44
+ value: '0',
45
+ };
46
+ }
47
+ exports.getCouponCodes = getCouponCodes;
@@ -0,0 +1,2 @@
1
+ export declare const loggerCtx = "SendcloudPlugin";
2
+ export declare const PLUGIN_OPTIONS: unique symbol;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLUGIN_OPTIONS = exports.loggerCtx = void 0;
4
+ exports.loggerCtx = 'SendcloudPlugin';
5
+ exports.PLUGIN_OPTIONS = Symbol('SENDCLOUD_OPTIONS');
@@ -0,0 +1,8 @@
1
+ import { DeepPartial, VendureEntity } from '@vendure/core';
2
+ export declare class SendcloudConfigEntity extends VendureEntity {
3
+ constructor(input?: DeepPartial<SendcloudConfigEntity>);
4
+ channelId: string;
5
+ secret?: string;
6
+ publicKey?: string;
7
+ defaultPhoneNr?: string;
8
+ }
@@ -0,0 +1,40 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SendcloudConfigEntity = void 0;
13
+ const core_1 = require("@vendure/core");
14
+ const typeorm_1 = require("typeorm");
15
+ let SendcloudConfigEntity = class SendcloudConfigEntity extends core_1.VendureEntity {
16
+ constructor(input) {
17
+ super(input);
18
+ }
19
+ };
20
+ __decorate([
21
+ (0, typeorm_1.Column)({ unique: true }),
22
+ __metadata("design:type", String)
23
+ ], SendcloudConfigEntity.prototype, "channelId", void 0);
24
+ __decorate([
25
+ (0, typeorm_1.Column)({ nullable: true }),
26
+ __metadata("design:type", String)
27
+ ], SendcloudConfigEntity.prototype, "secret", void 0);
28
+ __decorate([
29
+ (0, typeorm_1.Column)({ nullable: true }),
30
+ __metadata("design:type", String)
31
+ ], SendcloudConfigEntity.prototype, "publicKey", void 0);
32
+ __decorate([
33
+ (0, typeorm_1.Column)({ nullable: true }),
34
+ __metadata("design:type", String)
35
+ ], SendcloudConfigEntity.prototype, "defaultPhoneNr", void 0);
36
+ SendcloudConfigEntity = __decorate([
37
+ (0, typeorm_1.Entity)(),
38
+ __metadata("design:paramtypes", [Object])
39
+ ], SendcloudConfigEntity);
40
+ exports.SendcloudConfigEntity = SendcloudConfigEntity;
@@ -0,0 +1,11 @@
1
+ import { Order, OrderLine } from '@vendure/core';
2
+ import { ParcelInput, ParcelInputItem } from './types/sendcloud-api.types';
3
+ import { SendcloudPluginOptions } from './types/sendcloud.types';
4
+ /**
5
+ * Transforms order and variants to ParcelInput
6
+ * @param order including lines, shippingaddress and customer
7
+ * @param options
8
+ */
9
+ export declare function toParcelInput(order: Order, options: SendcloudPluginOptions, defaultPhoneNr?: string): ParcelInput;
10
+ export declare function toParcelInputItem(line: OrderLine, options: SendcloudPluginOptions): ParcelInputItem;
11
+ export declare function getTotalWeight(items: ParcelInputItem[]): string;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTotalWeight = exports.toParcelInputItem = exports.toParcelInput = void 0;
4
+ /**
5
+ * Transforms order and variants to ParcelInput
6
+ * @param order including lines, shippingaddress and customer
7
+ * @param options
8
+ */
9
+ function toParcelInput(order, options, defaultPhoneNr) {
10
+ const items = order.lines
11
+ .filter((line) => line.quantity >= 1)
12
+ .map((line) => toParcelInputItem(line, options));
13
+ return {
14
+ name: order.shippingAddress.fullName || '-',
15
+ company_name: order.shippingAddress.company,
16
+ address: order.shippingAddress.streetLine1,
17
+ house_number: order.shippingAddress.streetLine2,
18
+ city: order.shippingAddress.city,
19
+ postal_code: order.shippingAddress.postalCode,
20
+ country: order.shippingAddress.countryCode.toUpperCase(),
21
+ telephone: order.customer?.phoneNumber || defaultPhoneNr,
22
+ request_label: false,
23
+ email: order.customer?.emailAddress,
24
+ order_number: order.code,
25
+ parcel_items: items,
26
+ weight: getTotalWeight(items),
27
+ shipping_method_checkout_name: order.shippingLines?.[0].shippingMethod?.code,
28
+ };
29
+ }
30
+ exports.toParcelInput = toParcelInput;
31
+ function toParcelInputItem(line, options) {
32
+ const variant = line.productVariant;
33
+ let weightPerUnit = options.weightFn?.(line) || 0;
34
+ let hsCode = options.hsCodeFn?.(line);
35
+ if (weightPerUnit < 0.001) {
36
+ weightPerUnit = 0.001;
37
+ }
38
+ const parcelInput = {
39
+ description: variant.name,
40
+ quantity: line.quantity,
41
+ weight: weightPerUnit.toFixed(3),
42
+ sku: variant.sku,
43
+ value: (line.unitPriceWithTax / 100).toFixed(2),
44
+ };
45
+ const originCountry = options.originCountryFn?.(line);
46
+ if (originCountry) {
47
+ parcelInput.origin_country = originCountry;
48
+ }
49
+ if (hsCode && hsCode.length > 1) {
50
+ parcelInput.hs_code = hsCode;
51
+ }
52
+ return parcelInput;
53
+ }
54
+ exports.toParcelInputItem = toParcelInputItem;
55
+ function getTotalWeight(items) {
56
+ let totalWeight = 0;
57
+ items.forEach((item) => {
58
+ totalWeight += parseFloat(item.weight) * item.quantity;
59
+ });
60
+ return totalWeight.toFixed(3);
61
+ }
62
+ exports.getTotalWeight = getTotalWeight;
@@ -0,0 +1,18 @@
1
+ import { Response } from 'node-fetch';
2
+ import { Parcel, ParcelInput } from './types/sendcloud-api.types';
3
+ export declare class SendcloudClient {
4
+ private publicKey;
5
+ private secret;
6
+ static signatureHeader: string;
7
+ endpoint: string;
8
+ headers: {
9
+ [key: string]: string;
10
+ };
11
+ constructor(publicKey: string, secret: string);
12
+ 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
+ fetch(path: string, body: any): Promise<Response>;
18
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SendcloudClient = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const core_1 = require("@vendure/core");
10
+ const constants_1 = require("./constants");
11
+ class SendcloudClient {
12
+ constructor(publicKey, secret) {
13
+ this.publicKey = publicKey;
14
+ this.secret = secret;
15
+ this.endpoint = 'https://panel.sendcloud.sc/api/v2';
16
+ this.headers = {
17
+ 'Content-Type': 'application/json',
18
+ Authorization: 'Basic ' +
19
+ Buffer.from(`${this.publicKey}:${this.secret}`).toString('base64'),
20
+ };
21
+ }
22
+ async createParcel(parcelInput) {
23
+ const body = { parcel: parcelInput };
24
+ const res = await this.fetch('parcels', body);
25
+ if (!res.ok) {
26
+ throw Error(res.statusText);
27
+ }
28
+ const json = (await res.json());
29
+ core_1.Logger.info(`Created parcel in SendCloud with for order ${parcelInput.order_number} with id ${json.parcel?.id}`, constants_1.loggerCtx);
30
+ return json.parcel;
31
+ }
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
+ async fetch(path, body) {
46
+ const res = await (0, node_fetch_1.default)(`${this.endpoint}/${path}`, {
47
+ method: 'POST',
48
+ headers: this.headers,
49
+ body: JSON.stringify(body),
50
+ });
51
+ if (!res.ok) {
52
+ const json = (await res.json());
53
+ throw Error(`${res.statusText}: ${json.error?.message}`);
54
+ }
55
+ return res;
56
+ }
57
+ }
58
+ exports.SendcloudClient = SendcloudClient;
59
+ SendcloudClient.signatureHeader = 'sendcloud-signature';
@@ -0,0 +1,8 @@
1
+ import { Request } from 'express';
2
+ import { SendcloudService } from './sendcloud.service';
3
+ import { IncomingWebhookBody } from './types/sendcloud-api.types';
4
+ export declare class SendcloudController {
5
+ private sendcloudService;
6
+ constructor(sendcloudService: SendcloudService);
7
+ webhook(req: Request, body: IncomingWebhookBody, signature: string, channelToken: string): Promise<unknown>;
8
+ }
@@ -0,0 +1,65 @@
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
+ let SendcloudController = class SendcloudController {
23
+ constructor(sendcloudService) {
24
+ this.sendcloudService = sendcloudService;
25
+ }
26
+ async webhook(req, body, signature, channelToken) {
27
+ const rawBody = req.rawBody || JSON.stringify(body); // TestEnvironment doesnt have middleware applied, so no rawBody available
28
+ const ctx = await this.sendcloudService.createContext(channelToken);
29
+ const { client } = await this.sendcloudService.getClient(ctx);
30
+ if (!client.isValidWebhook(rawBody, signature)) {
31
+ core_1.Logger.warn(`Ignoring incoming webhook for channel ${channelToken}, because it has an invalid signature`, constants_1.loggerCtx);
32
+ return;
33
+ }
34
+ if (body.action !== 'parcel_status_changed') {
35
+ return core_1.Logger.info(`Incoming webhook: ${body.action}. skipping...`, constants_1.loggerCtx);
36
+ }
37
+ 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);
38
+ const status = sendcloud_types_1.sendcloudStates.find((s) => s.id === body.parcel?.status?.id);
39
+ if (!status) {
40
+ return core_1.Logger.warn(`Unknown SendCloud status "${body.parcel?.status?.message}", not handling this webhook.`, constants_1.loggerCtx);
41
+ }
42
+ if (!status.orderState) {
43
+ 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);
44
+ }
45
+ if (!body.parcel?.order_number) {
46
+ return core_1.Logger.warn(`No order_number in incoming Sendcloud webhook: ${JSON.stringify(body.parcel)}`, constants_1.loggerCtx);
47
+ }
48
+ await this.sendcloudService.updateOrderStatus(ctx, status, body.parcel.order_number, body.parcel.tracking_number);
49
+ }
50
+ };
51
+ __decorate([
52
+ (0, common_1.Post)('webhook/:channelToken'),
53
+ __param(0, (0, common_1.Req)()),
54
+ __param(1, (0, common_1.Body)()),
55
+ __param(2, (0, common_1.Headers)(sendcloud_client_1.SendcloudClient.signatureHeader)),
56
+ __param(3, (0, common_1.Param)('channelToken')),
57
+ __metadata("design:type", Function),
58
+ __metadata("design:paramtypes", [Object, Object, String, String]),
59
+ __metadata("design:returntype", Promise)
60
+ ], SendcloudController.prototype, "webhook", null);
61
+ SendcloudController = __decorate([
62
+ (0, common_1.Controller)('sendcloud'),
63
+ __metadata("design:paramtypes", [sendcloud_service_1.SendcloudService])
64
+ ], SendcloudController);
65
+ exports.SendcloudController = SendcloudController;
@@ -0,0 +1,7 @@
1
+ import { FulfillmentHandler } from '@vendure/core';
2
+ export declare const sendcloudHandler: FulfillmentHandler<{
3
+ trackingNumber: {
4
+ type: "string";
5
+ required: false;
6
+ };
7
+ }>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendcloudHandler = void 0;
4
+ const core_1 = require("@vendure/core");
5
+ const constants_1 = require("./constants");
6
+ exports.sendcloudHandler = new core_1.FulfillmentHandler({
7
+ code: 'sendcloud',
8
+ description: [
9
+ {
10
+ languageCode: core_1.LanguageCode.en,
11
+ value: 'Send order to SendCloud',
12
+ },
13
+ ],
14
+ args: {
15
+ trackingNumber: {
16
+ type: 'string',
17
+ required: false,
18
+ },
19
+ },
20
+ createFulfillment: async (ctx, orders, orderItems, args) => {
21
+ const orderCodes = orders.map((o) => o.code);
22
+ core_1.Logger.info(`Fulfilled orders ${orderCodes.join(',')}`, constants_1.loggerCtx);
23
+ return {
24
+ method: `SendCloud - ${args.trackingNumber || orderCodes.join(',')} `,
25
+ trackingCode: args.trackingNumber,
26
+ };
27
+ },
28
+ });
@@ -0,0 +1,16 @@
1
+ import { OrderService, RequestContext, PermissionDefinition } from '@vendure/core';
2
+ import { SendcloudService } from './sendcloud.service';
3
+ import { SendcloudConfigEntity } from './sendcloud-config.entity';
4
+ export declare const sendcloudPermission: PermissionDefinition;
5
+ export declare class SendcloudResolver {
6
+ private service;
7
+ private orderService;
8
+ constructor(service: SendcloudService, orderService: OrderService);
9
+ sendToSendCloud(ctx: RequestContext, orderId: string): Promise<boolean>;
10
+ sendCloudConfig(ctx: RequestContext): Promise<SendcloudConfigEntity | null>;
11
+ updateSendCloudConfig(ctx: RequestContext, input: {
12
+ secret: string;
13
+ publicKey: string;
14
+ defaultPhoneNr: string;
15
+ }): Promise<SendcloudConfigEntity>;
16
+ }