fix merge conflicts

This commit is contained in:
olivermrbl
2021-02-17 17:24:34 +01:00
152 changed files with 5559 additions and 2541 deletions
+184 -175
View File
@@ -1,26 +1,32 @@
title: Carts
domain: store
routes:
- method: POST
path: /carts
- method: GET
path: /carts/:id
- method: POST
path: /carts
- method: POST
path: /carts/:id
- method: POST
path: /carts/:id/complete-cart
- method: POST
path: /carts/:id/line-items
- method: POST
path: /carts/:id/line-items/:line_id
- method: DELETE
path: /carts/:id/line-items/:line_id
- method: POST
path: /carts/:id/payment-sessions
- method: DELETE
path: /carts/:id/payment-sessions/:provider_id
- method: POST
path: /carts/:id/payment-method
path: /carts/:id/payment-session/update
- method: POST
path: /carts/:id/payment-sessions/:provider_id/refresh
- method: POST
path: /carts/:id/payment-session
- method: POST
path: /carts/:id/shipping-methods
- method: DELETE
path: /carts/:id/payment-sessions/:provider_id
- method: DELETE
path: /carts/:id/line-items/:line_id
- method: DELETE
path: /carts/:id/discounts/:code
route: /carts
@@ -40,7 +46,7 @@ endpoints:
- name: items
type: List
required: false
description: List of objects with `variantId` and `quantity`.
description: List of objects with `variant_id` and `quantity`.
description: >
Creates a Cart within the given region and with the initial items. If no
`region_id` is provided the cart will be associated with the first Region
@@ -49,19 +55,11 @@ endpoints:
- path: /:id
method: GET
title: Retrieve a Cart
params:
- name: id
type: String
description: Id of the cart.
description: >
Retrieves an existing cart.
- path: /:id
method: POST
title: Update a Cart
params:
- name: id
type: String
description: Id of the cart.
body:
- name: region_id
type: String
@@ -84,17 +82,28 @@ endpoints:
description: >
A list of objects with `code` that represents a discount code to be
applied to the cart.
- name: customer_id
type: String
description: >
A list of objects with `code` that represents a discount code to be
applied to the cart.
description: >
Updates an existing cart with the provided data. Updating the `region_id`
will change the prices of the items in the cart to match the prices
defined for the region. You can apply multiple discounts at once by
calling the endpoint with an array of discounts.
- path: /:id/complete-cart
method: POST
title: Complete cart
description: >
Completes a cart. The following steps will be performed. Payment authorization
is attempted and if more work is required, we simply return the cart for further updates.
If payment is authorized and order is not yet created, we make sure to do so.
The completion of a cart can be performed idempotently with a provided header `Idempotency-Key`.
If not provided, we will generate one for the request.
- path: /:id/line-items
method: POST
title: Add line item
params:
- name: id
type: String
body:
- name: variant_id
type: String
@@ -114,13 +123,6 @@ endpoints:
- path: /:id/line-items/:line_id
method: POST
title: Update line item
params:
- name: id
type: String
description: Id of the cart to update.
- name: line_id
type: String
description: Id of the line to update.
body:
- name: quantity
type: Integer
@@ -132,84 +134,61 @@ endpoints:
- path: /:id/line-items/:line_id
method: DELETE
title: Remove line item
params:
- name: id
type: String
description: Id of the cart.
- name: line_id
type: String
description: Id of the line item to remove.
description: >
Removes a the given line item from the cart.
- path: /:id/discounts/:code
method: DELETE
title: Remove discount code
params:
- name: id
type: String
description: Id of the cart.
- name: code
type: String
description: The discount code to remove
description: Removes a discount code from the cart.
- path: /:id/payment-sessions
method: POST
title: Create payment sessions
params:
- name: id
type: String
description: Id of the cart.
description: >
Initializes the payment sessions that can be used to pay for the items of
the cart. This is usually called when a customer proceeds to checkout.
- path: /:id/payment-sessions/:provider_id
method: DELETE
title: Delete payment session
params:
- name: id
type: String
description: Id of the cart.
- name: provider_id
type: String
description: >
Id of the provider that created the payment session to remove.
description: >
Deletes a payment session. Note that this will initialize a new payment
session with the provider. This is usually used if the payment session
reaches a state that cannot be recovered from.
- path: /:id/payment-method
- path: /:id/payment-session
method: POST
title: Add payment method
title: Set payment session on cart
params:
- name: id
type: String
description: Id of the cart.
body:
- name: provider_id
type: String
required: true
description: >
Id of the provider that offers the payment method.
- name: data
type: Dictionary
description: >
Used to hold any data that the payment method may need to process the
payment. The requirements of `data` will be different across payment
methods, and you should look at your installed payment providers to
find out what to send.
description: Id of the provider (e.g. stripe).
description: >
Adds or updates the payment method that will be used to pay for the items
in the cart. The payment is not processed until the payment provider's
authorization functionality is called. You should consult with your
installed payment plugins to find information about how to authorize a
payment.
- path: /:id/payment-session/update
method: POST
title: Update payment session on cart
params:
- name: session
type: Dictionary
required: true
description: >
The session object needs to have a `provider_id` (e.g. stripe) and `data`, that holds the update data for the session.
The data object can be anything that might be needed for the payment provider to process the
payment. The requirements of `data` will be different across payment
methods, and you should look at your installed payment providers to
find out what to send..
description: >
Updates the payment session that will be used to pay for the items
in the cart. The payment is not processed until the payment provider's
authorization functionality is called. You should consult with your
installed payment plugins to find information about how to authorize a
payment.
- path: /:id/shipping-methods
method: POST
title: Add shipping method
params:
- name: id
type: String
description: Id of the cart.
body:
- name: option_id
type: String
@@ -226,115 +205,145 @@ endpoints:
response: |
{
"cart": {
"_id": "5f68a234d694d000217a4d64",
"customer_id": "",
"region_id": "5f4cd57a5d1e3200214c0e4e",
"email": "",
"id": "cart_1234",
"email": null,
"billing_address_id": null,
"billing_address": null,
"shipping_address_id": "addr_1234",
"shipping_address": {
"_id": "5f68a234d694d000217a4d65",
"country_code": "US"
"id": "addr_1234",
"customer_id": null,
"company": null,
"first_name": null,
"last_name": null,
"address_1": null,
"address_2": null,
"city": null,
"country_code": "dk",
"province": null,
"postal_code": null,
"phone": null,
"created_at": "2021-02-04T15:40:54.691Z",
"updated_at": "2021-02-04T15:40:54.691Z",
"deleted_at": null,
"metadata": null
},
"items": [{
"id": "item_1234",
"cart_id": "cart_1234",
"order_id": null,
"swap_id": null,
"claim_order_id": null,
"title": "Terry Towel",
"description": "30x30",
"thumbnail": "",
"is_giftcard": false,
"should_merge": true,
"allow_discounts": true,
"has_shipping": false,
"unit_price": 6000,
"variant_id": "variant_1234",
"variant": {
"id": "variant_1234",
"title": "30x30",
"product_id": "prod_1234",
"sku": null,
"barcode": null,
"ean": null,
"upc": null,
"inventory_quantity": 9999,
"allow_backorder": false,
"manage_inventory": true,
"hs_code": null,
"origin_country": null,
"mid_code": null,
"material": null,
"weight": null,
"length": null,
"height": null,
"width": null,
"created_at": "2021-01-19T17:09:01.864Z",
"updated_at": "2021-02-02T02:43:17.718Z",
"deleted_at": null,
"metadata": null
},
"quantity": 1,
"fulfilled_quantity": null,
"returned_quantity": null,
"shipped_quantity": null,
"created_at": "2021-02-04T15:42:12.514Z",
"updated_at": "2021-02-04T15:42:12.514Z",
"metadata": {}
}],
"region_id": "reg_1234",
"region": {
"id": "reg_1234",
"name": "Denmark",
"currency_code": "dkk",
"tax_rate": "25",
"tax_code": null,
"countries": [{
"id": 60,
"iso_2": "dk",
"iso_3": "dnk",
"num_code": 208,
"name": "DENMARK",
"display_name": "Denmark",
"region_id": "reg_1234"
}],
"payment_providers": [{
"id": "stripe",
"is_installed": true
}],
"fulfillment_providers": [{
"id": "manual",
"is_installed": true
}],
"created_at": "2021-01-19T17:09:01.864Z",
"updated_at": "2021-01-21T15:44:05.492Z",
"deleted_at": null,
"metadata": null
},
"items": [
{
"is_giftcard": false,
"has_shipping": false,
"returned": false,
"fulfilled": false,
"fulfilled_quantity": 0,
"returned_quantity": 0,
"_id": "5f72f79c2834b400216b1a54",
"title": "Line Item",
"description": "240x260",
"quantity": 1,
"thumbnail": "https://example-thumbnail.com",
"content": {
"unit_price": 303.2,
"variant": {
"_id": "5f4cf81a2ac41700211f6e63",
"barcode": "",
"image": "",
"published": true,
"inventory_quantity": 11,
"allow_backorder": false,
"manage_inventory": true,
"title": "1234",
"sku": "SKU1234",
"ean": "1111111111111",
"options": [
{
"_id": "5f4cf81a2ac41700211f6e64",
"value": "1234",
"option_id": "5f4cf81a2ac41700211f6e62"
}
],
"prices": [
{
"_id": "5f4cf81a2ac41700211f6e65",
"currency_code": "SEK",
"amount": 3180
},
{
"_id": "5f4cf81a2ac41700211f6e66",
"currency_code": "EUR",
"amount": 303.2
},
{
"_id": "5f4cf81a2ac41700211f6e67",
"currency_code": "DKK",
"amount": 2260
}
],
"metadata": {
"origin_country": "Portugal"
}
},
"product": {
"_id": "5f4cf81a2ac41700211f6e61",
"description": "100% Organic",
"tags": "",
"is_giftcard": false,
"images": [],
"thumbnail": "https://example-thumbnail.com",
"variants": [
"5f4cf81a2ac41700211f6e63"
],
"published": false,
"title": "Line Item",
"options": [
{
"values": [],
"_id": "5f4cf81a2ac41700211f6e62",
"title": "Size"
}
]
},
"quantity": 1
}
}
],
"discounts": [],
"payment_sessions": [],
"gift_cards": [],
"customer_id": null,
"payment_sessions": [{
"id": "ps_1234",
"cart_id": "cart_1234",
"provider_id": "stripe",
"is_selected": true,
"status": "pending",
"data": {},
"created_at": "2021-02-08T11:37:17.906Z",
"updated_at": "2021-02-08T11:37:17.906Z",
"idempotency_key": null
}],
"payment_id": null,
"payment": null,
"shipping_methods": [],
"type": "default",
"completed_at": null,
"created_at": "2021-02-04T15:40:54.691Z",
"updated_at": "2021-02-04T15:40:54.691Z",
"deleted_at": null,
"metadata": null,
"idempotency_key": null,
"payment_session": {
"id": "ps_1234",
"cart_id": "cart_1234",
"provider_id": "stripe",
"is_selected": true,
"status": "pending",
"data": {},
"created_at": "2021-02-08T11:37:17.906Z",
"updated_at": "2021-02-08T11:37:17.906Z",
"idempotency_key": null
},
"shipping_total": 0,
"discount_total": 0,
"tax_total": 0,
"subtotal": 303.2,
"total": 303.2,
"region": {
"_id": "5f4cd57a5d1e3200214c0e4e",
"tax_rate": 0,
"countries": [
"US"
],
"payment_providers": [
"stripe"
],
"fulfillment_providers": [
"shiphero"
],
"name": "United States",
"currency_code": "EUR",
"tax_code": "ABR"
}
"tax_total": 1500,
"gift_card_total": 0,
"subtotal": 6000,
"total": 7500
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ routes:
path: /customers/:id/reset-password-token
route: /customers
description: >
Customers can create a login to view Order history and manage details.
Customers can create a login to view order history and manage details.
Customers must have unique emails.
endpoints:
- path: /
+1 -1
View File
@@ -1,4 +1,4 @@
dist
dist/
node_modules
*yarn-error.log
@@ -0,0 +1,205 @@
const { dropDatabase } = require("pg-god");
const path = require("path");
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb } = require("../../../helpers/use-db");
const adminSeeder = require("../../helpers/admin-seeder");
const productSeeder = require("../../helpers/product-seeder");
jest.setTimeout(30000);
describe("/admin/products", () => {
let medusaProcess;
let dbConnection;
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
afterAll(async () => {
await dbConnection.close();
await dropDatabase({ databaseName: "medusa-integration" });
medusaProcess.kill();
});
describe("POST /admin/products", () => {
beforeEach(async () => {
try {
await productSeeder(dbConnection);
await adminSeeder(dbConnection);
} catch (err) {
console.log(err);
throw err;
}
});
afterEach(async () => {
const manager = dbConnection.manager;
await manager.query(`DELETE FROM "product_option_value"`);
await manager.query(`DELETE FROM "product_option"`);
await manager.query(`DELETE FROM "money_amount"`);
await manager.query(`DELETE FROM "product_variant"`);
await manager.query(`DELETE FROM "product"`);
await manager.query(`DELETE FROM "product_collection"`);
await manager.query(`DELETE FROM "product_tag"`);
await manager.query(`DELETE FROM "product_type"`);
await manager.query(
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
);
await manager.query(`DELETE FROM "region"`);
await manager.query(`DELETE FROM "user"`);
});
it("creates a product", async () => {
const api = useApi();
const payload = {
title: "Test product",
description: "test-product-description",
type: { value: "test-type" },
collection_id: "test-collection",
tags: [{ value: "123" }, { value: "456" }],
options: [{ title: "size" }, { title: "color" }],
variants: [
{
title: "Test variant",
inventory_quantity: 10,
prices: [{ currency_code: "usd", amount: 100 }],
options: [{ value: "large" }, { value: "green" }],
},
],
};
const response = await api
.post("/admin/products", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
expect(response.data.product).toEqual(
expect.objectContaining({
title: "Test product",
handle: "test-product",
tags: [
expect.objectContaining({
value: "123",
}),
expect.objectContaining({
value: "456",
}),
],
type: expect.objectContaining({
value: "test-type",
}),
collection: expect.objectContaining({
id: "test-collection",
title: "Test collection",
}),
options: [
expect.objectContaining({
title: "size",
}),
expect.objectContaining({
title: "color",
}),
],
variants: [
expect.objectContaining({
title: "Test variant",
prices: [
expect.objectContaining({
currency_code: "usd",
amount: 100,
}),
],
options: [
expect.objectContaining({
value: "large",
}),
expect.objectContaining({
value: "green",
}),
],
}),
],
})
);
});
it("updates a product (update tags, delete collection, delete type)", async () => {
const api = useApi();
const payload = {
collection_id: null,
type: null,
tags: [{ value: "123" }],
};
const response = await api
.post("/admin/products/test-product", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
expect(response.data.product).toEqual(
expect.objectContaining({
tags: [
expect.objectContaining({
value: "123",
}),
],
type: null,
collection: null,
})
);
});
it("add option", async () => {
const api = useApi();
const payload = {
title: "should_add",
};
const response = await api
.post("/admin/products/test-product/options", payload, {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
expect(response.data.product).toEqual(
expect.objectContaining({
options: [
expect.objectContaining({
title: "should_add",
product_id: "test-product",
}),
],
})
);
});
});
});
@@ -0,0 +1,68 @@
const {
ProductCollection,
ProductTag,
ProductType,
Region,
Product,
ShippingProfile,
ProductVariant,
} = require("@medusajs/medusa");
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
});
const coll = manager.create(ProductCollection, {
id: "test-collection",
title: "Test collection",
});
await manager.save(coll);
const tag = manager.create(ProductTag, {
id: "tag1",
value: "123",
});
await manager.save(tag);
const type = manager.create(ProductType, {
id: "test-type",
value: "test-type",
});
await manager.save(type);
await manager.insert(Region, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
await manager.insert(Product, {
id: "test-product",
title: "Test product",
profile_id: defaultProfile.id,
description: "test-product-description",
collection_id: "test-collection",
type: { id: "test-type", value: "test-type" },
tags: [
{ id: "tag1", value: "123" },
{ tag: "tag2", value: "456" },
],
options: [{ id: "test-option", title: "Default value" }],
});
await manager.insert(ProductVariant, {
id: "test-variant",
inventory_quantity: 10,
title: "Test variant",
product_id: "test-product",
prices: [{ id: "test-price", currency_code: "usd", amount: 100 }],
options: [{ id: "test-variant-option", value: "Default variant" }],
});
};
+1 -1
View File
@@ -16,4 +16,4 @@
"@babel/node": "^7.12.10",
"babel-preset-medusa-package": "^1.1.0"
}
}
}
+1
View File
@@ -18,4 +18,5 @@ module.exports = {
`.cache`,
],
transform: { "^.+\\.[jt]s$": `<rootDir>/jest-transformer.js` },
setupFilesAfterEnv: ["<rootDir>/integration-tests/setup.js"],
};
+5
View File
@@ -0,0 +1,5 @@
const { dropDatabase } = require("pg-god");
afterAll(() => {
dropDatabase({ databaseName: "medusa-integration" });
});
+1 -1
View File
@@ -5,7 +5,7 @@
],
"command": {
"publish": {
"allowBranch": "master",
"allowBranch": "release/*",
"bump": "patch",
"conventionalCommits": true,
"message": "chore(release): Publish"
+1
View File
@@ -27,6 +27,7 @@
"typeorm": "^0.2.30"
},
"scripts": {
"publish:next": "lerna publish --canary --preid next --dist-tag next",
"bootstrap": "lerna bootstrap",
"jest": "jest",
"test": "jest",
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-file-spaces@1.1.2...medusa-file-spaces@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-file-spaces
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-file-spaces@1.1.1...medusa-file-spaces@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-file-spaces
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-file-spaces",
"version": "1.1.2",
"version": "1.1.3",
"description": "Digital Ocean Spaces file connector for Medusa",
"main": "index.js",
"repository": {
@@ -41,7 +41,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"stripe": "^8.50.0"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.1...medusa-fulfillment-webshipper@1.1.2) (2021-02-17)
### Features
* notifications ([#172](https://github.com/medusajs/medusa/issues/172)) ([7308946](https://github.com/medusajs/medusa/commit/7308946e567ed4e63e1ed3d9d31b30c4f1a73f0d))
## [1.1.1](https://github.com/medusajs/medusa/compare/medusa-fulfillment-webshipper@1.1.0...medusa-fulfillment-webshipper@1.1.1) (2021-02-03)
@@ -1,6 +1,6 @@
{
"name": "medusa-fulfillment-webshipper",
"version": "1.1.1",
"version": "1.1.2",
"description": "Webshipper Fulfillment provider for Medusa",
"main": "index.js",
"repository": {
@@ -422,8 +422,41 @@ class WebshipperFulfillmentService extends FulfillmentService {
/**
* This plugin doesn't support shipment documents.
*/
async getShipmentDocuments() {
return []
async retrieveDocuments(fulfillmentData, documentType) {
switch (documentType) {
case "label":
const labelRelation = fulfillmentData?.relationships?.labels
if (labelRelation) {
const docs = await this.retrieveRelationship(labelRelation)
.then(({ data }) => data)
.catch((_) => [])
return docs.map((d) => ({
name: d.attributes.document_type,
base_64: d.attributes.base64,
type: "application/pdf",
}))
}
return []
case "invoice":
const docRelation = fulfillmentData?.relationships?.documents
if (docRelation) {
const docs = await this.retrieveRelationship(docRelation)
.then(({ data }) => data)
.catch((_) => [])
return docs.map((d) => ({
name: d.attributes.document_type,
base_64: d.attributes.base64,
type: "application/pdf",
}))
}
return []
default:
return []
}
}
/**
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.1](https://github.com/medusajs/medusa/compare/medusa-interfaces@1.1.0...medusa-interfaces@1.1.1) (2021-02-17)
### Features
* notifications ([#172](https://github.com/medusajs/medusa/issues/172)) ([7308946](https://github.com/medusajs/medusa/commit/7308946e567ed4e63e1ed3d9d31b30c4f1a73f0d))
# [1.1.0](https://github.com/medusajs/medusa/compare/medusa-interfaces@1.0.14...medusa-interfaces@1.1.0) (2021-01-26)
**Note:** Version bump only for package medusa-interfaces
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-interfaces",
"version": "1.1.0",
"version": "1.1.1",
"description": "Core interfaces for Medusa",
"main": "dist/index.js",
"repository": {
+1
View File
@@ -3,4 +3,5 @@ export { default as BaseModel } from "./base-model"
export { default as PaymentService } from "./payment-service"
export { default as FulfillmentService } from "./fulfillment-service"
export { default as FileService } from "./file-service"
export { default as NotificationService } from "./notification-service"
export { default as OauthService } from "./oauth-service"
@@ -0,0 +1,28 @@
import BaseService from "./base-service"
/**
* Interface for Notification Providers
* @interface
*/
class BaseNotificationService extends BaseService {
constructor() {
super()
}
getIdentifier() {
return this.constructor.identifier
}
/**
* Used to retrieve documents related to a shipment.
*/
sendNotification(event, data) {
throw new Error("Must be overridden by child")
}
resendNotification(notification, config = {}) {
throw new Error("Must be overridden by child")
}
}
export default BaseNotificationService
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-payment-adyen@1.1.2...medusa-payment-adyen@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-payment-adyen
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-payment-adyen@1.1.1...medusa-payment-adyen@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-payment-adyen
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-adyen",
"version": "1.1.2",
"version": "1.1.3",
"description": "Adyen Payment provider for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -24,7 +24,7 @@
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"scripts": {
"build": "babel src -d .",
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-payment-klarna@1.1.3...medusa-payment-klarna@1.1.4) (2021-02-17)
**Note:** Version bump only for package medusa-payment-klarna
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-payment-klarna@1.1.2...medusa-payment-klarna@1.1.3) (2021-02-09)
### Bug Fixes
* expired klarna orders ([5ef13d4](https://github.com/medusajs/medusa/commit/5ef13d49c0a2f6d82c5c2342ad800749e41d46fb))
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-payment-klarna@1.1.1...medusa-payment-klarna@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-payment-klarna
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-klarna",
"version": "1.1.2",
"version": "1.1.4",
"description": "Klarna Payment provider for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -41,7 +41,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}
@@ -397,6 +397,11 @@ class KlarnaProviderService extends PaymentService {
return this.klarna_
.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order)
.then(({ data }) => data)
.catch(async (_) => {
return this.klarna_
.post(this.klarnaOrderUrl_, order)
.then(({ data }) => data)
})
}
return paymentData
+14
View File
@@ -0,0 +1,14 @@
{
"plugins": [
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-instanceof",
"@babel/plugin-transform-classes"
],
"presets": ["@babel/preset-env"],
"env": {
"test": {
"plugins": ["@babel/plugin-transform-runtime"]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
+16
View File
@@ -0,0 +1,16 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
/dist
/api
/services
/models
/subscribers
/loaders
/__mocks__
+13
View File
@@ -0,0 +1,13 @@
/lib
node_modules
.DS_store
.env*
/*.js
!index.js
yarn.lock
src
.gitignore
.eslintrc
.babelrc
.prettierrc
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
@@ -0,0 +1,27 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.3](https://github.com/medusajs/medusa/compare/medusa-payment-paypal@1.0.2...medusa-payment-paypal@1.0.3) (2021-02-17)
**Note:** Version bump only for package medusa-payment-paypal
## [1.0.2](https://github.com/medusajs/medusa/compare/medusa-payment-paypal@1.0.1...medusa-payment-paypal@1.0.2) (2021-02-09)
**Note:** Version bump only for package medusa-payment-paypal
## 1.0.1 (2021-02-08)
### Features
* adds paypal ([#168](https://github.com/medusajs/medusa/issues/168)) ([#169](https://github.com/medusajs/medusa/issues/169)) ([427ae25](https://github.com/medusajs/medusa/commit/427ae25016bb3a22ebc05aa7b18017132846567c))
+11
View File
@@ -0,0 +1,11 @@
# medusa-payment-paypal
## Options
```
sandbox: [default: false],
client_id: "CLIENT_ID", REQUIRED
client_secret: "CLIENT_SECRET", REQUIRED
auth_webhook_id: REQUIRED for webhook to work
```
+1
View File
@@ -0,0 +1 @@
// noop
@@ -0,0 +1,47 @@
{
"name": "medusa-payment-paypal",
"version": "1.0.3",
"description": "Paypal Payment provider for Meduas Commerce",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
"directory": "packages/medusa-payment-paypal"
},
"author": "Sebastian Rindom",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/node": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@babel/plugin-transform-classes": "^7.9.5",
"@babel/plugin-transform-instanceof": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.5",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.9.6",
"client-sessions": "^0.8.0",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.3"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__",
"prepare": "cross-env NODE_ENV=production npm run build",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest"
},
"peerDependencies": {
"medusa-interfaces": "1.x"
},
"dependencies": {
"@paypal/checkout-server-sdk": "^1.0.2",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}
@@ -0,0 +1,10 @@
import { Router } from "express"
import hooks from "./routes/hooks"
export default (container) => {
const app = Router()
hooks(app)
return app
}
@@ -0,0 +1 @@
export default (fn) => (...args) => fn(...args).catch(args[2])
@@ -0,0 +1,5 @@
import { default as wrap } from "./await-middleware"
export default {
wrap,
}
@@ -0,0 +1,13 @@
import { Router } from "express"
import bodyParser from "body-parser"
import middlewares from "../../middlewares"
const route = Router()
export default (app) => {
app.use("/paypal", route)
route.use(bodyParser.json())
route.post("/hooks", middlewares.wrap(require("./paypal").default))
return app
}
@@ -0,0 +1,55 @@
export default async (req, res) => {
const auth_algo = req.headers["paypal-auth-algo"]
const cert_url = req.headers["paypal-cert-url"]
const transmission_id = req.headers["paypal-transmission-id"]
const transmission_sig = req.headers["paypal-transmission-sig"]
const transmission_time = req.headers["paypal-transmission-time"]
const paypalService = req.scope.resolve("paypalProviderService")
try {
await paypalService.verifyWebhook({
auth_algo,
cert_url,
transmission_id,
transmission_sig,
transmission_time,
webhook_event: req.body,
})
} catch (err) {
res.sendStatus(401)
return
}
try {
const authId = req.body.resource.id
const auth = await paypalService.retrieveAuthorization(authId)
const order = await paypalService.retrieveOrderFromAuth(auth)
const purchaseUnit = order.purchase_units[0]
const cartId = purchaseUnit.custom_id
const manager = req.scope.resolve("manager")
const cartService = req.scope.resolve("cartService")
const orderService = req.scope.resolve("orderService")
await manager.transaction(async (m) => {
const order = await orderService
.withTransaction(m)
.retrieveByCartId(cartId)
.catch((_) => undefined)
if (!order) {
await cartService.withTransaction(m).setPaymentSession(cartId, "paypal")
await cartService.withTransaction(m).authorizePayment(cartId)
await orderService.withTransaction(m).createFromCart(cartId)
}
})
res.sendStatus(200)
} catch (err) {
console.error(err)
res.sendStatus(409)
}
}
@@ -0,0 +1,402 @@
import _ from "lodash"
import PayPal from "@paypal/checkout-server-sdk"
import { PaymentService } from "medusa-interfaces"
class PayPalProviderService extends PaymentService {
static identifier = "paypal"
constructor({ customerService, totalsService, regionService }, options) {
super()
/**
* Required PayPal options:
* {
* sandbox: [default: false],
* client_id: "CLIENT_ID", REQUIRED
* client_secret: "CLIENT_SECRET", REQUIRED
* auth_webhook_id: REQUIRED for webhook to work
* }
*/
this.options_ = options
let environment
if (this.options_.sandbox) {
environment = new PayPal.core.SandboxEnvironment(
options.client_id,
options.client_secret
)
} else {
environment = new PayPal.core.LiveEnvironment(
options.client_id,
options.client_secret
)
}
/** @private @const {PayPalHttpClient} */
this.paypal_ = new PayPal.core.PayPalHttpClient(environment)
/** @private @const {CustomerService} */
this.customerService_ = customerService
/** @private @const {RegionService} */
this.regionService_ = regionService
/** @private @const {TotalsService} */
this.totalsService_ = totalsService
}
/**
* Fetches an open PayPal order and maps its status to Medusa payment
* statuses.
* @param {object} paymentData - the data stored with the payment
* @returns {Promise<string>} the status of the order
*/
async getStatus(paymentData) {
const order = await this.retrievePayment(paymentData)
let status = "pending"
switch (order.status) {
case "CREATED":
return "pending"
case "COMPLETED":
return "authorized"
case "SAVED":
case "APPROVED":
case "PAYER_ACTION_REQUIRED":
return "requires_more"
case "VOIDED":
return "canceled"
// return "captured"
default:
return status
}
}
/**
* Not supported
*/
async retrieveSavedMethods(customer) {
return Promise.resolve([])
}
/**
* Creates a PayPal order, with an Authorize intent. The plugin doesn't
* support shipping details at the moment.
* Reference docs: https://developer.paypal.com/docs/api/orders/v2/
* @param {object} cart - cart to create a payment for
* @returns {object} the data to be stored with the payment session.
*/
async createPayment(cart) {
const { region_id } = cart
const { currency_code } = await this.regionService_.retrieve(region_id)
const amount = await this.totalsService_.getTotal(cart)
const request = new PayPal.orders.OrdersCreateRequest()
request.requestBody({
intent: "AUTHORIZE",
application_context: {
shipping_preference: "NO_SHIPPING",
},
purchase_units: [
{
custom_id: cart.id,
amount: {
currency_code: currency_code.toUpperCase(),
value: (amount / 100).toFixed(2),
},
},
],
})
const res = await this.paypal_.execute(request)
return { id: res.result.id }
}
/**
* Retrieves a PayPal order.
* @param {object} data - the data stored with the payment
* @returns {Promise<object>} PayPal order
*/
async retrievePayment(data) {
try {
const request = new PayPal.orders.OrdersGetRequest(data.id)
const res = await this.paypal_.execute(request)
return res.result
} catch (error) {
throw error
}
}
/**
* Gets the payment data from a payment session
* @param {object} session - the session to fetch payment data for.
* @returns {Promise<object>} the PayPal order object
*/
async getPaymentData(session) {
try {
return this.retrievePayment(session.data)
} catch (error) {
throw error
}
}
/**
* This method does not call the PayPal authorize function, but fetches the
* status of the payment as it is expected to have been authorized client side.
* @param {object} session - payment session
* @param {object} context - properties relevant to current context
* @returns {Promise<{ status: string, data: object }>} result with data and status
*/
async authorizePayment(session, context = {}) {
const stat = await this.getStatus(session.data)
try {
return { data: session.data, status: stat }
} catch (error) {
throw error
}
}
/**
* Updates the data stored with the payment session.
* @param {object} data - the currently stored data.
* @param {object} update - the update data to store.
* @returns {object} the merged data of the two arguments.
*/
async updatePaymentData(data, update) {
try {
return {
...data,
...update.data,
}
} catch (error) {
throw error
}
}
/**
* Updates the PayPal order.
* @param {object} sessionData - payment session data.
* @param {object} cart - the cart to update by.
* @returns {object} the resulting order object.
*/
async updatePayment(sessionData, cart) {
try {
const { region_id } = cart
const { currency_code } = await this.regionService_.retrieve(region_id)
const request = new PayPal.orders.OrdersPatchRequest(sessionData.id)
request.requestBody([
{
op: "replace",
path: "/purchase_units/@reference_id=='default'",
value: {
amount: {
currency_code: currency_code.toUpperCase(),
value: (cart.total / 100).toFixed(),
},
},
},
])
await this.paypal_.execute(request)
return sessionData
} catch (error) {
throw error
}
}
/**
* Not suported
*/
async deletePayment(payment) {
return
}
/**
* Captures a previously authorized order.
* @param {object} payment - the payment to capture
* @returns {object} the PayPal order
*/
async capturePayment(payment) {
const { purchase_units } = payment.data
const id = purchase_units[0].payments.authorizations[0].id
try {
const request = new PayPal.payments.AuthorizationsCaptureRequest(id)
await this.paypal_.execute(request)
return this.retrievePayment(payment.data)
} catch (error) {
throw error
}
}
/**
* Refunds a given amount.
* @param {object} payment - payment to refund
* @param {number} amountToRefund - amount to refund
* @returns {string} the resulting PayPal order
*/
async refundPayment(payment, amountToRefund) {
const { purchase_units } = payment.data
try {
const payments = purchase_units[0].payments
if (!(payments && payments.captures.length)) {
throw new Error("Order not yet captured")
}
const payId = payments.captures[0].id
const request = new PayPal.payments.CapturesRefundRequest(payId)
request.requestBody({
amount: {
currency_code: payment.currency_code.toUpperCase(),
value: (amountToRefund / 100).toFixed(),
},
})
await this.paypal_.execute(request)
return this.retrievePayment(payment.id)
} catch (error) {
throw error
}
}
/**
* Cancels payment for Stripe payment intent.
* @param {object} paymentData - payment method data from cart
* @returns {object} canceled payment intent
*/
async cancelPayment(payment) {
try {
const { purchase_units } = payment.data
if (payment.captured_at) {
const payments = purchase_units[0].payments
const payId = payments.captures[0].id
const request = new PayPal.payments.CapturesRefundRequest(payId)
await this.paypal_.execute(request)
} else {
const id = purchase_units[0].payments.authorizations[0].id
const request = new PayPal.payments.AuthorizationsVoidRequest(id)
await this.paypal_.execute(request)
}
return this.retrievePayment(payment.data)
} catch (error) {
throw error
}
}
/**
* Given a PayPal authorization object the method will find the order that
* created the authorization, by following the HATEOAS link to the order.
* @param {object} auth - the authorization object.
* @returns {Promise<object>} the PayPal order object
*/
async retrieveOrderFromAuth(auth) {
const link = auth.links.find((l) => l.rel === "up")
const parts = link.href.split("/")
const orderId = parts[parts.length - 1]
const orderReq = new PayPal.orders.OrdersGetRequest(orderId)
const res = await this.paypal_.execute(orderReq)
if (res.result) {
return res.result
}
return null
}
/**
* Retrieves a PayPal authorization.
* @param {string} id - the id of the authorization.
* @returns {Promise<object>} the authorization.
*/
async retrieveAuthorization(id) {
const authReq = new PayPal.payments.AuthorizationsGetRequest(id)
const res = await this.paypal_.execute(authReq)
if (res.result) {
return res.result
}
return null
}
/**
* Checks if a webhook is verified.
* @param {object} data - the verficiation data.
* @returns {Promise<object>} the response of the verification request.
*/
async verifyWebhook(data) {
const verifyReq = {
verb: "POST",
path: "/v1/notifications/verify-webhook-signature",
headers: {
"Content-Type": "application/json",
},
body: {
webhook_id: this.options_.auth_webhook_id,
...data,
},
}
return this.paypal_.execute(verifyReq)
}
/**
* Upserts a webhook that listens for order authorizations.
*/
async ensureWebhooks() {
if (!this.options_.backend_url) {
return
}
const webhookReq = {
verb: "GET",
path: "/v1/notifications/webhooks",
}
const webhookRes = await this.paypal_.execute(webhookReq)
console.log(webhookRes.result.webhooks)
let found
if (webhookRes.result && webhookRes.result.webhooks) {
found = webhookRes.result.webhooks.find((w) => {
const notificationType = w.event_types.find(
(e) => e.name === "PAYMENT.AUTHORIZATION.CREATED"
)
return (
!!notificationType &&
w.url === `${this.options_.backend_url}/paypal/hooks`
)
})
}
if (!found) {
const whCreateReq = {
verb: "POST",
path: "/v1/notifications/webhooks",
headers: {
"Content-Type": "application/json",
},
body: {
id: "medusa-auth-notification",
url: `${this.options_.backend_url}/paypal/hooks`,
event_types: [
{
name: "PAYMENT.AUTHORIZATION.CREATED",
},
],
},
}
await this.paypal_.execute(whCreateReq)
}
}
}
export default PayPalProviderService
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.2...medusa-payment-stripe@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-payment-stripe
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-payment-stripe@1.1.1...medusa-payment-stripe@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-payment-stripe
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-payment-stripe",
"version": "1.1.2",
"version": "1.1.3",
"description": "Stripe Payment provider for Meduas Commerce",
"main": "index.js",
"repository": {
@@ -26,7 +26,7 @@
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__",
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-add-ons@1.1.2...medusa-plugin-add-ons@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-add-ons
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-add-ons@1.1.1...medusa-plugin-add-ons@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-add-ons
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-add-ons",
"version": "1.1.2",
"version": "1.1.3",
"description": "Add-on plugin for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -25,7 +25,7 @@
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"scripts": {
"build": "babel src -d . --ignore **/__tests__",
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-brightpearl@1.1.3...medusa-plugin-brightpearl@1.1.4) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-brightpearl
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-brightpearl@1.1.2...medusa-plugin-brightpearl@1.1.3) (2021-02-08)
**Note:** Version bump only for package medusa-plugin-brightpearl
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-brightpearl@1.1.1...medusa-plugin-brightpearl@1.1.2) (2021-02-03)
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-brightpearl",
"version": "1.1.2",
"version": "1.1.4",
"description": "Brightpearl plugin for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -27,7 +27,7 @@
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"prettier": "^2.0.5"
},
"scripts": {
@@ -12,4 +12,5 @@ yarn.lock
/services
/models
/subscribers
/loaders
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-contentful@1.1.2...medusa-plugin-contentful@1.1.3) (2021-02-17)
### Features
* **medusa:** Product category, type and tags ([c4d1203](https://github.com/medusajs/medusa/commit/c4d1203155b7cc03e8892f0409efec83e030063e))
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-contentful@1.1.1...medusa-plugin-contentful@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-contentful
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-contentful",
"version": "1.1.2",
"version": "1.1.3",
"description": "Contentful plugin for Medusa Commerce",
"main": "index.js",
"repository": {
@@ -40,7 +40,7 @@
"contentful-management": "^5.27.1",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"redis": "^3.0.2"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
@@ -0,0 +1,59 @@
const checkContentTypes = async (container) => {
const contentfulService = container.resolve("contentfulService")
let product
let variant
try {
product = await contentfulService.getType("product")
variant = await contentfulService.getType("productVariant")
} catch (error) {
if (!product) {
throw Error("Content type: `product` is missing in Contentful")
}
if (!variant) {
throw Error("Content type: `productVariant` is missing in Contentful")
}
}
if (product && product.fields) {
const productFields = product.fields
const keys = Object.values(productFields).map((f) => f.id)
if (!requiredProductFields.every((f) => keys.includes(f))) {
throw Error(
`Contentful: Content type ${`product`} is missing some required key(s). Required: ${requiredProductFields.join(
", "
)}`
)
}
}
if (variant && variant.fields) {
const variantFields = variant.fields
const keys = Object.values(variantFields).map((f) => f.id)
if (!requiredVariantFields.every((f) => keys.includes(f))) {
throw Error(
`Contentful: Content type ${`productVariant`} is missing some required key(s). Required: ${requiredVariantFields.join(
", "
)}`
)
}
}
}
const requiredProductFields = [
"title",
"variants",
"options",
"objectId",
"type",
"collection",
"tags",
"handle",
]
const requiredVariantFields = ["title", "sku", "prices", "options", "objectId"]
export default checkContentTypes
@@ -114,28 +114,58 @@ class ContentfulService extends BaseService {
async createProductInContentful(product) {
try {
const p = await this.productService_.retrieve(product.id, {
relations: ["variants", "options"],
relations: ["variants", "options", "tags", "type", "collection"],
})
const environment = await this.getContentfulEnvironment_()
const variantEntries = await this.getVariantEntries_(p.variants)
const variantLinks = this.getVariantLinks_(variantEntries)
const result = await environment.createEntryWithId("product", p.id, {
fields: {
title: {
"en-US": p.title,
},
variants: {
"en-US": variantLinks,
},
options: {
"en-US": p.options,
},
objectId: {
"en-US": p.id,
},
const fields = {
title: {
"en-US": p.title,
},
variants: {
"en-US": variantLinks,
},
options: {
"en-US": p.options,
},
objectId: {
"en-US": p.id,
},
}
if (p.type) {
const type = {
"en-US": p.type.value,
}
fields.type = type
}
if (p.collection) {
const collection = {
"en-US": p.collection.title,
}
fields.collection = collection
}
if (p.tags) {
const tags = {
"en-US": p.tags,
}
fields.tags = tags
}
if (p.handle) {
const handle = {
"en-US": p.handle,
}
fields.handle = handle
}
const result = await environment.createEntryWithId("product", p.id, {
fields,
})
const ignoreIds = (await this.getIgnoreIds_("product")) || []
@@ -210,7 +240,7 @@ class ContentfulService extends BaseService {
}
const p = await this.productService_.retrieve(product.id, {
relations: ["options", "variants"],
relations: ["options", "variants", "type", "collection", "tags"],
})
const variantEntries = await this.getVariantEntries_(p.variants)
@@ -232,6 +262,34 @@ class ContentfulService extends BaseService {
},
}
if (p.type) {
const type = {
"en-US": p.type.value,
}
productEntryFields.type = type
}
if (p.collection) {
const collection = {
"en-US": p.collection.title,
}
productEntryFields.collection = collection
}
if (p.tags) {
const tags = {
"en-US": p.tags,
}
productEntryFields.tags = tags
}
if (p.handle) {
const handle = {
"en-US": p.handle,
}
productEntryFields.handle = handle
}
productEntry.fields = productEntryFields
const updatedEntry = await productEntry.update()
@@ -372,6 +430,11 @@ class ContentfulService extends BaseService {
throw error
}
}
async getType(type) {
const environment = await this.getContentfulEnvironment_()
return environment.getContentType(type)
}
}
export default ContentfulService
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-economic@1.1.2...medusa-plugin-economic@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-economic
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-economic@1.1.1...medusa-plugin-economic@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-economic
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-economic",
"version": "1.1.2",
"version": "1.1.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-economic",
"version": "1.1.2",
"version": "1.1.3",
"description": "E-conomic financial reporting",
"main": "index.js",
"repository": {
@@ -40,7 +40,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"moment": "^2.27.0"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-mailchimp@1.1.2...medusa-plugin-mailchimp@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-mailchimp
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-mailchimp@1.1.1...medusa-plugin-mailchimp@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-mailchimp
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-mailchimp",
"version": "1.1.2",
"version": "1.1.3",
"description": "Mailchimp newsletter subscriptions",
"main": "index.js",
"repository": {
@@ -41,7 +41,7 @@
"express": "^4.17.1",
"mailchimp-api-v3": "^1.14.0",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-permissions@1.1.2...medusa-plugin-permissions@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-permissions
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-permissions@1.1.1...medusa-plugin-permissions@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-permissions
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-permissions",
"version": "1.1.2",
"version": "1.1.3",
"description": "Role permission for Medusa core",
"main": "dist/index.js",
"repository": {
@@ -33,7 +33,7 @@
},
"dependencies": {
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"mongoose": "^5.8.0"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.4](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.3...medusa-plugin-segment@1.1.4) (2021-02-17)
### Bug Fixes
* item reporting revenue discounted ([6e6669d](https://github.com/medusajs/medusa/commit/6e6669dee8cb3f7e044a4001fa1e183bb61766dd))
* price after discounts ([0ed6b4a](https://github.com/medusajs/medusa/commit/0ed6b4a6a09d1d2ef5c9d6a199db1e01234bc3c2))
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-segment@1.1.2...medusa-plugin-segment@1.1.3) (2021-02-03)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-segment",
"version": "1.1.3",
"version": "1.1.4",
"description": "Segment Analytics",
"main": "index.js",
"repository": {
@@ -40,7 +40,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}
@@ -106,10 +106,10 @@ class SegmentService extends BaseService {
let name = item.title
const unit_price = item.unit_price
const line_total = (unit_price * item.quantity) / 100
const line_total = this.totalsService_.getLineItemRefund(order, item)
const revenue = await this.getReportingValue(
order.currency_code,
line_total
line_total / 100
)
let sku = ""
@@ -126,7 +126,7 @@ class SegmentService extends BaseService {
return {
name,
variant,
price: unit_price / 100,
price: line_total / 100 / item.quantity,
reporting_revenue: revenue,
product_id: item.variant.product_id,
sku,
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-sendgrid@1.1.2...medusa-plugin-sendgrid@1.1.3) (2021-02-17)
### Bug Fixes
* attachment generator for invoices ([0cc2ccd](https://github.com/medusajs/medusa/commit/0cc2ccd3b2100960ce7d3422e20ce89ca96704a0))
* handles normalizeThumb without thumbnail ([93f6812](https://github.com/medusajs/medusa/commit/93f68126bfb27f694211b6f890ce0370c42fa08a))
### Features
* notifications ([#172](https://github.com/medusajs/medusa/issues/172)) ([7308946](https://github.com/medusajs/medusa/commit/7308946e567ed4e63e1ed3d9d31b30c4f1a73f0d))
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-sendgrid@1.1.1...medusa-plugin-sendgrid@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-sendgrid
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-sendgrid",
"version": "1.1.2",
"version": "1.1.3",
"description": "SendGrid transactional emails",
"main": "index.js",
"repository": {
@@ -40,7 +40,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}
@@ -1,7 +1,9 @@
import { BaseService } from "medusa-interfaces"
import { NotificationService } from "medusa-interfaces"
import SendGrid from "@sendgrid/mail"
class SendGridService extends BaseService {
class SendGridService extends NotificationService {
static identifier = "sendgrid"
/**
* @param {Object} options - options defined in `medusa-config.js`
* e.g.
@@ -15,69 +17,220 @@ class SendGridService extends BaseService {
* customer_password_reset_template: 1111,
* }
*/
constructor({}, options) {
constructor(
{
storeService,
orderService,
returnService,
swapService,
lineItemService,
claimService,
fulfillmentService,
fulfillmentProviderService,
totalsService,
},
options
) {
super()
this.options_ = options
this.fulfillmentProviderService_ = fulfillmentProviderService
this.storeService_ = storeService
this.lineItemService_ = lineItemService
this.orderService_ = orderService
this.claimService_ = claimService
this.returnService_ = returnService
this.swapService_ = swapService
this.fulfillmentService_ = fulfillmentService
this.totalsService_ = totalsService
SendGrid.setApiKey(options.api_key)
}
/**
* Sends a transactional email based on an event using SendGrid.
* @param {string} event - event related to the order
* @param {Object} order - the order object sent to SendGrid, that must
* correlate with the structure specificed in the dynamic template
* @returns {Promise} result of the send operation
*/
async transactionalEmail(event, data) {
let templateId
async fetchAttachments(event, data, attachmentGenerator) {
switch (event) {
case "order.gift_card_created":
templateId = this.options_.gift_card_created_template
data = {
...data,
display_value: data.giftcard.rule.value * (1 + data.tax_rate),
case "swap.created":
case "order.return_requested": {
let attachments = []
const { shipping_method, shipping_data } = data.return_request
if (shipping_method) {
const provider = shipping_method.shipping_option.provider_id
const lbl = await this.fulfillmentProviderService_.retrieveDocuments(
provider,
shipping_data,
"label"
)
attachments = attachments.concat(
lbl.map((d) => ({
name: "return-label",
base64: d.base_64,
type: d.type,
}))
)
}
break
case "order.placed":
templateId = this.options_.order_placed_template
break
case "order.updated":
templateId = this.options_.order_updated_template
break
case "order.shipment_created":
templateId = this.options_.order_shipped_template
break
case "order.cancelled":
templateId = this.options_.order_cancelled_template
break
case "order.completed":
templateId = this.options_.order_completed_template
break
case "user.password_reset":
templateId = this.options_.user_password_reset_template
break
case "customer.password_reset":
templateId = this.options_.customer_password_reset_template
break
default:
return
}
try {
if (templateId) {
return SendGrid.send({
template_id: templateId,
from: this.options_.from,
to: data.email,
dynamic_template_data: data,
})
if (attachmentGenerator && attachmentGenerator.createReturnInvoice) {
const base64 = await attachmentGenerator.createReturnInvoice(
data.order,
data.return_request.items
)
attachments.push({
name: "invoice",
base64,
type: "application/pdf",
})
}
return attachments
}
} catch (error) {
throw error
default:
return []
}
}
async fetchData(event, eventData, attachmentGenerator) {
switch (event) {
case "order.return_requested":
return this.returnRequestedData(eventData, attachmentGenerator)
case "swap.shipment_created":
return this.swapShipmentCreatedData(eventData, attachmentGenerator)
case "claim.shipment_created":
return this.claimShipmentCreatedData(eventData, attachmentGenerator)
case "order.items_returned":
return this.itemsReturnedData(eventData, attachmentGenerator)
case "order.swap_received":
return this.swapReceivedData(eventData, attachmentGenerator)
case "swap.created":
return this.swapCreatedData(eventData, attachmentGenerator)
case "gift_card.created":
return this.gcCreatedData(eventData, attachmentGenerator)
case "order.gift_card_created":
return this.gcCreatedData(eventData, attachmentGenerator)
case "order.placed":
return this.orderPlacedData(eventData, attachmentGenerator)
case "order.shipment_created":
return this.orderShipmentCreatedData(eventData, attachmentGenerator)
case "order.canceled":
return this.orderCanceledData(eventData, attachmentGenerator)
case "user.password_reset":
return this.userPasswordResetData(eventData, attachmentGenerator)
case "customer.password_reset":
return this.customerPasswordResetData(eventData, attachmentGenerator)
default:
return {}
}
}
getTemplateId(event) {
switch (event) {
case "order.return_requested":
return this.options_.order_return_requested_template
case "swap.shipment_created":
return this.options_.swap_shipment_created_template
case "claim.shipment_created":
return this.options_.claim_shipment_created_template
case "order.items_returned":
return this.options_.order_items_returned_template
case "order.swap_received":
return this.options_.order_swap_received_template
case "swap.created":
return this.options_.swap_created_template
case "gift_card.created":
return this.options_.gift_card_created_template
case "order.gift_card_created":
return this.options_.gift_card_created_template
case "order.placed":
return this.options_.order_placed_template
case "order.shipment_created":
return this.options_.order_shipped_template
case "order.canceled":
return this.options_.order_canceled_template
case "user.password_reset":
return this.options_.user_password_reset_template
case "customer.password_reset":
return this.options_.customer_password_reset_template
default:
return null
}
}
async sendNotification(event, eventData, attachmentGenerator) {
let templateId = this.getTemplateId(event)
if (!templateId) {
return false
}
const data = await this.fetchData(event, eventData, attachmentGenerator)
const attachments = await this.fetchAttachments(
event,
data,
attachmentGenerator
)
const sendOptions = {
template_id: templateId,
from: this.options_.from,
to: data.email,
dynamic_template_data: data,
has_attachments: attachments?.length,
}
if (attachments?.length) {
sendOptions.has_attachments = true
sendOptions.attachments = attachments.map((a) => {
return {
content: a.base64,
filename: a.name,
type: a.type,
disposition: "attachment",
contentId: a.name,
}
})
}
const status = await SendGrid.send(sendOptions)
.then(() => "sent")
.catch(() => "failed")
// We don't want heavy docs stored in DB
delete sendOptions.attachments
return { to: data.email, status, data: sendOptions }
}
async resendNotification(notification, config, attachmentGenerator) {
const sendOptions = {
...notification.data,
to: config.to || notification.to,
}
const attachs = await this.fetchAttachments(
notification.event_name,
notification.data.dynamic_template_data,
attachmentGenerator
)
sendOptions.attachments = attachs.map((a) => {
return {
content: a.base64,
filename: a.name,
type: a.type,
disposition: "attachment",
contentId: a.name,
}
})
const status = await SendGrid.send(sendOptions)
.then(() => "sent")
.catch(() => "failed")
return { to: sendOptions.to, status, data: sendOptions }
}
/**
* Sends an email using SendGrid.
* @param {string} templateId - id of template in SendGrid
@@ -93,6 +246,418 @@ class SendGridService extends BaseService {
throw error
}
}
async orderShipmentCreatedData({ id, fulfillment_id }, attachmentGenerator) {
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
"refundable_amount",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
],
})
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id, {
relations: ["items"],
})
return {
order,
date: shipment.shipped_at.toDateString(),
email: order.email,
fulfillment: shipment,
tracking_number: shipment.tracking_numbers.join(", "),
}
}
async orderPlacedData({ id }) {
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
],
})
const {
subtotal,
tax_total,
discount_total,
shipping_total,
gift_card_total,
total,
} = order
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
const items = this.processItems_(order.items, taxRate, currencyCode)
let discounts = []
if (order.discounts) {
discounts = order.discounts.map((discount) => {
return {
is_giftcard: false,
code: discount.code,
descriptor: `${discount.rule.value}${
discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
}`,
}
})
}
let giftCards = []
if (order.gift_cards) {
giftCards = order.gift_cards.map((gc) => {
return {
is_giftcard: true,
code: gc.code,
descriptor: `${gc.value} ${currencyCode}`,
}
})
discounts.concat(giftCards)
}
return {
...order,
has_discounts: order.discounts.length,
has_gift_cards: order.gift_cards.length,
date: order.created_at.toDateString(),
items,
discounts,
subtotal: `${this.humanPrice_(subtotal * (1 + taxRate))} ${currencyCode}`,
gift_card_total: `${this.humanPrice_(
gift_card_total * (1 + taxRate)
)} ${currencyCode}`,
tax_total: `${this.humanPrice_(tax_total)} ${currencyCode}`,
discount_total: `${this.humanPrice_(
discount_total * (1 + taxRate)
)} ${currencyCode}`,
shipping_total: `${this.humanPrice_(
shipping_total * (1 + taxRate)
)} ${currencyCode}`,
total: `${this.humanPrice_(total)} ${currencyCode}`,
}
}
async gcCreatedData({ id }) {
const giftCard = await this.giftCardService_.retrieve(id, {
relations: ["region", "order"],
})
const taxRate = giftCard.region.tax_rate / 100
return {
...giftCard,
email: giftCard.order.email,
display_value: giftCard.value * (1 + taxRate),
}
}
async returnRequestedData({ id, return_id }) {
// Fetch the return request
const returnRequest = await this.returnService_.retrieve(return_id, {
relations: [
"items",
"items.item",
"items.item.variant",
"items.item.variant.product",
"shipping_method",
"shipping_method.shipping_option",
],
})
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
})
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id)
return {
...item,
item: found,
}
})
// Fetch the order
const order = await this.orderService_.retrieve(id, {
select: ["total"],
relations: ["items", "discounts", "shipping_address", "returns"],
})
// Calculate which items are in the return
const returnItems = returnRequest.items.map((i) => {
const found = order.items.find((oi) => oi.id === i.item_id)
return {
...found,
quantity: i.quantity,
}
})
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
// Get total of the returned products
const item_subtotal = this.totalsService_.getRefundTotal(order, returnItems)
// If the return has a shipping method get the price and any attachments
let shippingTotal = 0
if (returnRequest.shipping_method) {
shippingTotal = returnRequest.shipping_method.price * (1 + taxRate)
}
return {
has_shipping: !!returnRequest.shipping_method,
email: order.email,
items: this.processItems_(returnItems, taxRate, currencyCode),
subtotal: `${this.humanPrice_(item_subtotal)} ${currencyCode}`,
shipping_total: `${this.humanPrice_(shippingTotal)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(
returnRequest.refund_amount
)} ${currencyCode}`,
return_request: {
...returnRequest,
refund_amount: `${this.humanPrice_(
returnRequest.refund_amount
)} ${currencyCode}`,
},
order,
date: returnRequest.updated_at.toDateString(),
}
}
async swapCreatedData({ id }) {
const store = await this.storeService_.retrieve()
const swap = await this.swapService_.retrieve(id, {
relations: [
"additional_items",
"return_order",
"return_order.items",
"return_order.items.item",
"return_order.shipping_method",
"return_order.shipping_method.shipping_option",
],
})
const returnRequest = swap.return_order
const items = await this.lineItemService_.list({
id: returnRequest.items.map(({ item_id }) => item_id),
})
returnRequest.items = returnRequest.items.map((item) => {
const found = items.find((i) => i.id === item.item_id)
return {
...item,
item: found,
}
})
const swapLink = store.swap_link_template.replace(
/\{cart_id\}/,
swap.cart_id
)
const order = await this.orderService_.retrieve(swap.order_id, {
select: ["total"],
relations: ["items", "discounts", "shipping_address"],
})
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
const returnItems = this.processItems_(
swap.return_order.items.map((i) => {
const found = order.items.find((oi) => oi.id === i.item_id)
return {
...found,
quantity: i.quantity,
}
}),
taxRate,
currencyCode
)
const returnTotal = this.totalsService_.getRefundTotal(order, returnItems)
const constructedOrder = {
...order,
shipping_methods: [],
items: swap.additional_items,
}
const additionalTotal = this.totalsService_.getTotal(constructedOrder)
const refundAmount = swap.return_order.refund_amount
return {
swap,
order,
return_request: returnRequest,
date: swap.updated_at.toDateString(),
swap_link: swapLink,
email: order.email,
items: this.processItems_(swap.additional_items, taxRate, currencyCode),
return_items: returnItems,
return_total: `${this.humanPrice_(returnTotal)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(refundAmount)} ${currencyCode}`,
additional_total: `${this.humanPrice_(additionalTotal)} ${currencyCode}`,
}
}
async itemsReturnedData(data) {
return this.returnRequestedData(data)
}
async swapShipmentCreatedData({ id, fulfillment_id }) {
const swap = await this.swapService_.retrieve(id, {
relations: [
"shipping_address",
"shipping_methods",
"additional_items",
"return_order",
"return_order.items",
],
})
const order = await this.orderService_.retrieve(swap.order_id, {
relations: ["items", "discounts"],
})
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
const returnItems = this.processItems_(
swap.return_order.items.map((i) => {
const found = order.items.find((oi) => oi.id === i.item_id)
return {
...found,
quantity: i.quantity,
}
}),
taxRate,
currencyCode
)
const returnTotal = this.totalsService_.getRefundTotal(order, returnItems)
const constructedOrder = {
...order,
shipping_methods: swap.shipping_methods,
items: swap.additional_items,
}
const additionalTotal = this.totalsService_.getTotal(constructedOrder)
const refundAmount = swap.return_order.refund_amount
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
return {
swap,
order,
items: this.processItems_(swap.additional_items, taxRate, currencyCode),
date: swap.updated_at.toDateString(),
email: order.email,
tax_amount: `${this.humanPrice_(
swap.difference_due * taxRate
)} ${currencyCode}`,
paid_total: `${this.humanPrice_(swap.difference_due)} ${currencyCode}`,
return_total: `${this.humanPrice_(returnTotal)} ${currencyCode}`,
refund_amount: `${this.humanPrice_(refundAmount)} ${currencyCode}`,
additional_total: `${this.humanPrice_(additionalTotal)} ${currencyCode}`,
fulfillment: shipment,
tracking_number: shipment.tracking_numbers.join(", "),
}
}
async claimShipmentCreatedData({ id, fulfillment_id }) {
const claim = await this.claimService_.retrieve(id, {
relations: ["order", "order.items", "order.shipping_address"],
})
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
return {
email: claim.order.email,
claim,
order: claim.order,
fulfillment: shipment,
tracking_number: shipment.tracking_numbers.join(", "),
}
}
userPasswordResetData(data) {
return data
}
customerPasswordResetData(data) {
return data
}
processItems_(items, taxRate, currencyCode) {
return items.map((i) => {
return {
...i,
thumbnail: this.normalizeThumbUrl_(i.thumbnail),
price: `${this.humanPrice_(
i.unit_price * (1 + taxRate)
)} ${currencyCode}`,
}
})
}
humanPrice_(amount) {
return amount ? (amount / 100).toFixed(2) : "0.00"
}
normalizeThumbUrl_(url) {
if (!url) {
return null
}
if (url.startsWith("http")) {
return url
} else if (url.startsWith("//")) {
return `https:${url}`
}
return url
}
}
export default SendGridService
@@ -3,187 +3,26 @@ class OrderSubscriber {
totalsService,
orderService,
sendgridService,
eventBusService,
notificationService,
fulfillmentService,
}) {
this.orderService_ = orderService
this.totalsService_ = totalsService
this.sendgridService_ = sendgridService
this.eventBus_ = eventBusService
this.notificationService_ = notificationService
this.fulfillmentService_ = fulfillmentService
this.eventBus_.subscribe(
"order.shipment_created",
async ({ id, fulfillment_id }) => {
const order = await this.orderService_.retrieve(id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
"refundable_amount",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
"swaps",
"swaps.return_order",
"swaps.payment",
"swaps.shipping_methods",
"swaps.shipping_address",
"swaps.additional_items",
"swaps.fulfillments",
],
})
const shipment = await this.fulfillmentService_.retrieve(fulfillment_id)
const data = {
...order,
tracking_number: shipment.tracking_numbers.join(", "),
}
await this.sendgridService_.transactionalEmail(
"order.shipment_created",
data
)
}
)
this.eventBus_.subscribe("order.gift_card_created", async (order) => {
await this.sendgridService_.transactionalEmail(
"order.gift_card_created",
order
)
})
this.eventBus_.subscribe("order.placed", async (orderObj) => {
try {
const order = await this.orderService_.retrieve(orderObj.id, {
select: [
"shipping_total",
"discount_total",
"tax_total",
"refunded_total",
"gift_card_total",
"subtotal",
"total",
],
relations: [
"customer",
"billing_address",
"shipping_address",
"discounts",
"shipping_methods",
"shipping_methods.shipping_option",
"payments",
"fulfillments",
"returns",
"gift_cards",
"gift_card_transactions",
"swaps",
"swaps.return_order",
"swaps.payment",
"swaps.shipping_methods",
"swaps.shipping_address",
"swaps.additional_items",
"swaps.fulfillments",
],
})
const {
subtotal,
tax_total,
discount_total,
shipping_total,
total,
} = order
const taxRate = order.tax_rate / 100
const currencyCode = order.currency_code.toUpperCase()
const items = order.items.map((i) => {
return {
...i,
price: `${((i.unit_price / 100) * (1 + taxRate)).toFixed(
2
)} ${currencyCode}`,
}
})
let discounts = []
if (order.discounts) {
discounts = order.discounts.map((discount) => {
return {
is_giftcard: false,
code: discount.code,
descriptor: `${discount.rule.value}${
discount.rule.type === "percentage" ? "%" : ` ${currencyCode}`
}`,
}
})
}
let giftCards = []
if (order.gift_cards) {
giftCards = order.gift_cards.map((gc) => {
return {
is_giftcard: true,
code: gc.code,
descriptor: `${gc.value} ${currencyCode}`,
}
})
discounts.concat(giftCards)
}
const data = {
...order,
date: order.created_at.toDateString(),
items,
discounts,
subtotal: `${((subtotal / 100) * (1 + taxRate)).toFixed(
2
)} ${currencyCode}`,
tax_total: `${(tax_total / 100).toFixed(2)} ${currencyCode}`,
discount_total: `${((discount_total / 100) * (1 + taxRate)).toFixed(
2
)} ${currencyCode}`,
shipping_total: `${((shipping_total / 100) * (1 + taxRate)).toFixed(
2
)} ${currencyCode}`,
total: `${(total / 100).toFixed(2)} ${currencyCode}`,
}
await this.sendgridService_.transactionalEmail("order.placed", data)
} catch (error) {
console.log(error)
}
})
this.eventBus_.subscribe("order.cancelled", async (order) => {
await this.sendgridService_.transactionalEmail("order.cancelled", order)
})
this.eventBus_.subscribe("order.completed", async (order) => {
await this.sendgridService_.transactionalEmail("order.completed", order)
})
this.eventBus_.subscribe("order.updated", async (order) => {
await this.sendgridService_.transactionalEmail("order.updated", order)
})
this.notificationService_.subscribe("order.shipment_created", "sendgrid")
this.notificationService_.subscribe("order.gift_card_created", "sendgrid")
this.notificationService_.subscribe("gift_card.created", "sendgrid")
this.notificationService_.subscribe("order.placed", "sendgrid")
this.notificationService_.subscribe("order.canceled", "sendgrid")
this.notificationService_.subscribe("customer.password_reset", "sendgrid")
this.notificationService_.subscribe("claim.shipment_created", "sendgrid")
this.notificationService_.subscribe("swap.shipment_created", "sendgrid")
this.notificationService_.subscribe("swap.created", "sendgrid")
this.notificationService_.subscribe("order.items_returned", "sendgrid")
this.notificationService_.subscribe("order.return_requested", "sendgrid")
}
}
@@ -10,13 +10,6 @@ class UserSubscriber {
data
)
})
this.eventBus_.subscribe("customer.password_reset", async (data) => {
await this.sendgridService_.transactionalEmail(
"customer.password_reset",
data
)
})
}
}
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-slack-notification@1.1.2...medusa-plugin-slack-notification@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-slack-notification
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-slack-notification@1.1.1...medusa-plugin-slack-notification@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-slack-notification
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-economic",
"version": "1.1.2",
"version": "1.1.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-slack-notification",
"version": "1.1.2",
"version": "1.1.3",
"description": "Slack notifications",
"main": "index.js",
"repository": {
@@ -40,7 +40,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"moment": "^2.27.0"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.2...medusa-plugin-twilio-sms@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-twilio-sms
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-twilio-sms@1.1.1...medusa-plugin-twilio-sms@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-twilio-sms
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-twilio-sms",
"version": "1.1.2",
"version": "1.1.3",
"main": "index.js",
"repository": {
"type": "git",
@@ -36,7 +36,7 @@
"@babel/plugin-transform-classes": "^7.9.5",
"body-parser": "^1.19.0",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"twilio": "^3.49.1"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-plugin-wishlist@1.1.2...medusa-plugin-wishlist@1.1.3) (2021-02-17)
**Note:** Version bump only for package medusa-plugin-wishlist
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-plugin-wishlist@1.1.1...medusa-plugin-wishlist@1.1.2) (2021-02-03)
**Note:** Version bump only for package medusa-plugin-wishlist
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-plugin-wishlist",
"version": "1.1.2",
"version": "1.1.3",
"description": "Provides /customers/:id/wishlist to add items to a customr's wishlist",
"main": "index.js",
"repository": {
@@ -38,7 +38,7 @@
"body-parser": "^1.19.0",
"express": "^4.17.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2"
"medusa-test-utils": "^1.1.3"
},
"gitHead": "0646bd395a6056657cb0aa93c13699c4a9dbbcdd"
}
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.3](https://github.com/medusajs/medusa/compare/medusa-test-utils@1.1.2...medusa-test-utils@1.1.3) (2021-02-17)
### Bug Fixes
* use parallel relation fetching ([#173](https://github.com/medusajs/medusa/issues/173)) ([46006e4](https://github.com/medusajs/medusa/commit/46006e4b0647bada1dc2cb417766e22f65bad23e))
## [1.1.2](https://github.com/medusajs/medusa/compare/medusa-test-utils@1.1.1...medusa-test-utils@1.1.2) (2021-02-03)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "medusa-test-utils",
"version": "1.1.2",
"version": "1.1.3",
"description": "Test utils for Medusa",
"main": "dist/index.js",
"repository": {
@@ -6,6 +6,7 @@ class MockRepo {
softRemove,
find,
findOne,
findOneWithRelations,
findOneOrFail,
save,
findAndCount,
@@ -19,6 +20,7 @@ class MockRepo {
this.findOneOrFail_ = findOneOrFail;
this.save_ = save;
this.findAndCount_ = findAndCount;
this.findOneWithRelations_ = findOneWithRelations;
}
setFindOne(fn) {
@@ -53,6 +55,11 @@ class MockRepo {
return this.findOneOrFail_(...args);
}
});
findOneWithRelations = jest.fn().mockImplementation((...args) => {
if (this.findOneWithRelations_) {
return this.findOneWithRelations_(...args);
}
});
findOne = jest.fn().mockImplementation((...args) => {
if (this.findOne_) {
return this.findOne_(...args);
File diff suppressed because it is too large Load Diff
+29
View File
@@ -3,6 +3,35 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.8](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.7...@medusajs/medusa@1.1.8) (2021-02-17)
### Bug Fixes
* test ([4229e24](https://github.com/medusajs/medusa/commit/4229e241d03986039d283d98e034eaaefb50e04d))
* use parallel relation fetching ([#173](https://github.com/medusajs/medusa/issues/173)) ([46006e4](https://github.com/medusajs/medusa/commit/46006e4b0647bada1dc2cb417766e22f65bad23e))
### Features
* notifications ([#172](https://github.com/medusajs/medusa/issues/172)) ([7308946](https://github.com/medusajs/medusa/commit/7308946e567ed4e63e1ed3d9d31b30c4f1a73f0d))
* **medusa:** Product category, type and tags ([c4d1203](https://github.com/medusajs/medusa/commit/c4d1203155b7cc03e8892f0409efec83e030063e))
## [1.1.7](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.6...@medusajs/medusa@1.1.7) (2021-02-08)
### Features
* adds paypal ([#168](https://github.com/medusajs/medusa/issues/168)) ([#169](https://github.com/medusajs/medusa/issues/169)) ([427ae25](https://github.com/medusajs/medusa/commit/427ae25016bb3a22ebc05aa7b18017132846567c))
## [1.1.6](https://github.com/medusajs/medusa/compare/@medusajs/medusa@1.1.5...@medusajs/medusa@1.1.6) (2021-02-03)
**Note:** Version bump only for package @medusajs/medusa
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@medusajs/medusa",
"version": "1.1.6",
"version": "1.1.8",
"description": "E-commerce for JAMstack",
"main": "dist/index.js",
"repository": {
@@ -49,6 +49,7 @@
"dependencies": {
"@babel/plugin-transform-classes": "^7.9.5",
"@hapi/joi": "^16.1.8",
"@types/lodash": "^4.14.168",
"awilix": "^4.2.3",
"body-parser": "^1.19.0",
"bull": "^3.12.1",
@@ -67,7 +68,7 @@
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"medusa-core-utils": "^1.1.0",
"medusa-test-utils": "^1.1.2",
"medusa-test-utils": "^1.1.3",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"passport": "^0.4.0",
@@ -0,0 +1,65 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("POST /admin/collections", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/collections", {
payload: {
title: "Suits",
handle: "suits",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns created product collection", () => {
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
})
it("calls production collection service create", () => {
expect(ProductCollectionServiceMock.create).toHaveBeenCalledTimes(1)
expect(ProductCollectionServiceMock.create).toHaveBeenCalledWith({
title: "Suits",
handle: "suits",
})
})
})
describe("invalid data returns error details", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/collections", {
payload: {
handle: "no-title-collection",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error details", () => {
expect(subject.body.name).toEqual("invalid_data")
expect(subject.body.message[0].message).toEqual(`"title" is required`)
})
})
})
@@ -0,0 +1,42 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("DELETE /admin/collections/:id", () => {
describe("successful removes collection", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/collections/${IdMap.getId("collection")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls product collection service delete", () => {
expect(ProductCollectionServiceMock.delete).toHaveBeenCalledTimes(1)
expect(ProductCollectionServiceMock.delete).toHaveBeenCalledWith(
IdMap.getId("collection")
)
})
it("returns delete result", () => {
expect(subject.body).toEqual({
id: IdMap.getId("collection"),
object: "product-collection",
deleted: true,
})
})
})
})
@@ -0,0 +1,37 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("GET /admin/categories/:id", () => {
describe("get collection by id successfully", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/admin/collections/${IdMap.getId("col")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls retrieve from product collection service", () => {
expect(ProductCollectionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductCollectionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("col")
)
})
it("returns variant decorated", () => {
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
})
})
})
@@ -0,0 +1,28 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("GET /admin/collections", () => {
describe("successful retrieval", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("GET", `/admin/collections`, {
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls product collection service list", () => {
expect(ProductCollectionServiceMock.list).toHaveBeenCalledTimes(1)
})
})
})
@@ -0,0 +1,44 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ProductCollectionServiceMock } from "../../../../../services/__mocks__/product-collection"
describe("POST /admin/collections/:id", () => {
describe("successful update", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/collections/${IdMap.getId("col")}`,
{
payload: {
title: "Suits and vests",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns updated product collection", () => {
expect(subject.body.collection.id).toEqual(IdMap.getId("col"))
})
it("product collection service update", () => {
expect(ProductCollectionServiceMock.update).toHaveBeenCalledTimes(1)
expect(ProductCollectionServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("col"),
{
title: "Suits and vests",
}
)
})
})
})
@@ -0,0 +1,29 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
title: Validator.string().required(),
handle: Validator.string()
.optional()
.allow(""),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const created = await productCollectionService.create(value)
const collection = await productCollectionService.retrieve(created.id)
res.status(200).json({ collection })
} catch (err) {
throw err
}
}
@@ -0,0 +1,18 @@
export default async (req, res) => {
const { id } = req.params
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
await productCollectionService.delete(id)
res.json({
id,
object: "product-collection",
deleted: true,
})
} catch (err) {
throw err
}
}
@@ -0,0 +1,13 @@
export default async (req, res) => {
const { id } = req.params
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const collection = await productCollectionService.retrieve(id)
res.status(200).json({ collection })
} catch (err) {
throw err
}
}
@@ -0,0 +1,21 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/collections", route)
route.post("/", middlewares.wrap(require("./create-collection").default))
route.post("/:id", middlewares.wrap(require("./update-collection").default))
route.delete("/:id", middlewares.wrap(require("./delete-collection").default))
route.get("/:id", middlewares.wrap(require("./get-collection").default))
route.get("/", middlewares.wrap(require("./list-collections").default))
return app
}
export const defaultFields = ["id", "title", "handle"]
export const defaultRelations = ["products"]
@@ -0,0 +1,30 @@
import { defaultFields, defaultRelations } from "."
export default async (req, res) => {
try {
const selector = {}
const limit = parseInt(req.query.limit) || 10
const offset = parseInt(req.query.offset) || 0
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const listConfig = {
select: defaultFields,
relations: defaultRelations,
skip: offset,
take: limit,
}
const collections = await productCollectionService.list(
selector,
listConfig
)
res.status(200).json({ collections })
} catch (err) {
throw err
}
}
@@ -0,0 +1,29 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
title: Validator.string().optional(),
handle: Validator.string().optional(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const productCollectionService = req.scope.resolve(
"productCollectionService"
)
const updated = await productCollectionService.update(id, value)
const collection = await productCollectionService.retrieve(updated.id)
res.status(200).json({ collection })
} catch (err) {
throw err
}
}
@@ -19,6 +19,8 @@ import swapRoutes from "./swaps"
import returnRoutes from "./returns"
import variantRoutes from "./variants"
import draftOrderRoutes from "./draft-orders"
import collectionRoutes from "./collections"
import notificationRoutes from "./notifications"
const route = Router()
@@ -62,6 +64,8 @@ export default (app, container, config) => {
returnRoutes(route)
variantRoutes(route)
draftOrderRoutes(route)
collectionRoutes(route)
notificationRoutes(route)
return app
}
@@ -0,0 +1,48 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/notifications", route)
/**
* List notifications
*/
route.get("/", middlewares.wrap(require("./list-notifications").default))
/**
* Resend a notification
*/
route.post(
"/:id/resend",
middlewares.wrap(require("./resend-notification").default)
)
return app
}
export const defaultRelations = ["resends"]
export const allowedRelations = ["resends"]
export const defaultFields = [
"id",
"resource_type",
"resource_id",
"event_name",
"to",
"provider_id",
"created_at",
"updated_at",
]
export const allowedFields = [
"id",
"resource_type",
"resource_id",
"provider_id",
"event_name",
"to",
"created_at",
"updated_at",
]
@@ -0,0 +1,64 @@
import _ from "lodash"
import { defaultRelations, defaultFields } from "./"
export default async (req, res) => {
try {
const notificationService = req.scope.resolve("notificationService")
const limit = parseInt(req.query.limit) || 50
const offset = parseInt(req.query.offset) || 0
let selector = {}
let includeFields = []
if ("fields" in req.query) {
includeFields = req.query.fields.split(",")
}
let expandFields = []
if ("expand" in req.query) {
expandFields = req.query.expand.split(",")
}
if ("event_name" in req.query) {
const values = req.query.event_name.split(",")
selector.event_name = values.length > 1 ? values : values[0]
}
if ("resource_type" in req.query) {
const values = req.query.resource_type.split(",")
selector.resource_type = values.length > 1 ? values : values[0]
}
if ("resource_id" in req.query) {
const values = req.query.resource_id.split(",")
selector.resource_id = values.length > 1 ? values : values[0]
}
if ("to" in req.query) {
const values = req.query.to.split(",")
selector.to = values.length > 1 ? values : values[0]
}
if (!("include_resends" in req.query)) {
selector.parent_id = null
}
const listConfig = {
select: includeFields.length ? includeFields : defaultFields,
relations: expandFields.length ? expandFields : defaultRelations,
skip: offset,
take: limit,
order: { created_at: "DESC" },
}
const notifications = await notificationService.list(selector, listConfig)
const fields = [...listConfig.select, ...listConfig.relations]
const data = notifications.map(o => _.pick(o, fields))
res.json({ notifications: data, offset, limit })
} catch (error) {
throw error
}
}
@@ -0,0 +1,36 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
to: Validator.string().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const notificationService = req.scope.resolve("notificationService")
const config = {}
if (value.to) {
config.to = value.to
}
await notificationService.resend(id, config)
const notification = await notificationService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ notification })
} catch (error) {
throw error
}
}
@@ -0,0 +1,38 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
export default async (req, res) => {
const { id, claim_id } = req.params
const schema = Validator.object().keys({
fulfillment_id: Validator.string().required(),
tracking_numbers: Validator.array()
.items(Validator.string())
.optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const orderService = req.scope.resolve("orderService")
const claimService = req.scope.resolve("claimService")
await claimService.createShipment(
claim_id,
value.fulfillment_id,
value.tracking_numbers
)
const order = await orderService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.json({ order })
} catch (error) {
throw error
}
}
@@ -161,6 +161,14 @@ export default app => {
middlewares.wrap(require("./fulfill-claim").default)
)
/**
* Creates claim fulfillment
*/
route.post(
"/:id/claims/:claim_id/shipments",
middlewares.wrap(require("./create-claim-shipment").default)
)
/**
* Delete metadata key / value pair.
*/
@@ -53,6 +53,7 @@ export default async (req, res) => {
try {
const orderService = req.scope.resolve("orderService")
const returnService = req.scope.resolve("returnService")
const eventBus = req.scope.resolve("eventBusService")
let inProgress = true
let err = false
@@ -98,6 +99,13 @@ export default async (req, res) => {
.fulfill(createdReturn.id)
}
await eventBus
.withTransaction(manager)
.emit("order.return_requested", {
id,
return_id: createdReturn.id,
})
return {
recovery_point: "return_requested",
}
@@ -12,7 +12,7 @@ describe("POST /admin/products", () => {
payload: {
title: "Test Product",
description: "Test Description",
tags: "hi,med,dig",
tags: [{ id: "test", value: "test" }],
handle: "test-product",
},
adminSession: {
@@ -36,7 +36,7 @@ describe("POST /admin/products", () => {
expect(ProductServiceMock.create).toHaveBeenCalledWith({
title: "Test Product",
description: "Test Description",
tags: "hi,med,dig",
tags: [{ id: "test", value: "test" }],
handle: "test-product",
is_giftcard: false,
profile_id: IdMap.getId("default_shipping_profile"),
@@ -34,11 +34,12 @@ describe("GET /admin/products/:id", () => {
"title",
"subtitle",
"description",
"tags",
"handle",
"is_giftcard",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"weight",
"length",
"height",
@@ -57,6 +58,9 @@ describe("GET /admin/products/:id", () => {
"variants.options",
"images",
"options",
"tags",
"type",
"collection",
],
}
)
@@ -6,13 +6,28 @@ export default async (req, res) => {
title: Validator.string().required(),
subtitle: Validator.string().allow(""),
description: Validator.string().allow(""),
tags: Validator.string().optional(),
is_giftcard: Validator.boolean().default(false),
images: Validator.array()
.items(Validator.string())
.optional(),
thumbnail: Validator.string().optional(),
handle: Validator.string().optional(),
type: Validator.object()
.keys({
id: Validator.string().optional(),
value: Validator.string().required(),
})
.allow(null)
.optional(),
collection_id: Validator.string()
.allow(null)
.optional(),
tags: Validator.array()
.items({
id: Validator.string().optional(),
value: Validator.string().required(),
})
.optional(),
options: Validator.array().items({
title: Validator.string().required(),
}),
@@ -48,7 +63,7 @@ export default async (req, res) => {
Validator.object()
.keys({
region_id: Validator.string(),
currency_code: Validator.string().required(),
currency_code: Validator.string(),
amount: Validator.number()
.integer()
.required(),
@@ -8,6 +8,11 @@ export default app => {
route.post("/", middlewares.wrap(require("./create-product").default))
route.post("/:id", middlewares.wrap(require("./update-product").default))
route.get("/types", middlewares.wrap(require("./list-types").default))
route.get(
"/tag-usage",
middlewares.wrap(require("./list-tag-usage-count").default)
)
route.post(
"/:id/variants",
@@ -52,6 +57,9 @@ export const defaultRelations = [
"variants.options",
"images",
"options",
"tags",
"type",
"collection",
]
export const defaultFields = [
@@ -59,11 +67,12 @@ export const defaultFields = [
"title",
"subtitle",
"description",
"tags",
"handle",
"is_giftcard",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"weight",
"length",
"height",
@@ -82,11 +91,12 @@ export const allowedFields = [
"title",
"subtitle",
"description",
"tags",
"handle",
"is_giftcard",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"weight",
"length",
"height",
@@ -105,4 +115,7 @@ export const allowedRelations = [
"variants.prices",
"images",
"options",
"tags",
"type",
"collection",
]

Some files were not shown because too many files have changed in this diff Show More