@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.
- package/README.md +87 -0
- package/dist/util/src/index.d.ts +1 -0
- package/dist/util/src/index.js +17 -0
- package/dist/util/src/order-state-util.d.ts +19 -0
- package/dist/util/src/order-state-util.js +62 -0
- package/dist/util/src/raw-body.d.ts +6 -0
- package/dist/util/src/raw-body.js +26 -0
- package/dist/vendure-plugin-sendcloud/src/api/additional-parcel-input-items.d.ts +10 -0
- package/dist/vendure-plugin-sendcloud/src/api/additional-parcel-input-items.js +47 -0
- package/dist/vendure-plugin-sendcloud/src/api/constants.d.ts +2 -0
- package/dist/vendure-plugin-sendcloud/src/api/constants.js +5 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud-config.entity.d.ts +8 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud-config.entity.js +40 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.adapter.d.ts +11 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.adapter.js +62 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.client.d.ts +18 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.client.js +59 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.controller.d.ts +8 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.controller.js +65 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.handler.d.ts +7 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.handler.js +28 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.resolver.d.ts +16 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.resolver.js +75 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.service.d.ts +48 -0
- package/dist/vendure-plugin-sendcloud/src/api/sendcloud.service.js +245 -0
- package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud-api.types.d.ts +82 -0
- package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud-api.types.js +2 -0
- package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud.types.d.ts +41 -0
- package/dist/vendure-plugin-sendcloud/src/api/types/sendcloud.types.js +157 -0
- package/dist/vendure-plugin-sendcloud/src/index.d.ts +5 -0
- package/dist/vendure-plugin-sendcloud/src/index.js +21 -0
- package/dist/vendure-plugin-sendcloud/src/sendcloud.plugin.d.ts +7 -0
- package/dist/vendure-plugin-sendcloud/src/sendcloud.plugin.js +94 -0
- package/dist/vendure-plugin-sendcloud/src/ui/history-entry.component.ts +69 -0
- package/dist/vendure-plugin-sendcloud/src/ui/queries.ts +23 -0
- package/dist/vendure-plugin-sendcloud/src/ui/sendcloud-nav.module.ts +29 -0
- package/dist/vendure-plugin-sendcloud/src/ui/sendcloud.component.ts +112 -0
- package/dist/vendure-plugin-sendcloud/src/ui/sendcloud.module.ts +21 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Vendure SendCloud plugin
|
|
2
|
+
|
|
3
|
+

|
|
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,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,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
|
+
}
|