fix merge conflicts

This commit is contained in:
olivermrbl committed 2021-02-17 17:24:34 +01:00
commit 283f8fbe54
152 files changed
+5559 -2541

No files matched your search

+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",
]
@@ -0,0 +1,11 @@
export default async (req, res) => {
try {
const productService = req.scope.resolve("productService")
const tags = await productService.listTagsByUsage()
res.json({ tags })
} catch (error) {
throw error
}
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
try {
const productService = req.scope.resolve("productService")
const types = await productService.listTypes()
res.json({ types })
} catch (error) {
throw error
}
}
@@ -7,7 +7,22 @@ export default async (req, res) => {
const schema = Validator.object().keys({
title: Validator.string().optional(),
description: Validator.string().optional(),
tags: 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(),
handle: Validator.string().optional(),
weight: Validator.number().optional(),
length: Validator.number().optional(),
@@ -34,7 +34,7 @@ describe("POST /store/customers", () => {
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(CustomerServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("lebron"),
{ relations: ["orders", "shipping_addresses"] }
{ relations: ["shipping_addresses"] }
)
})
@@ -42,7 +42,7 @@ describe("POST /store/customers/:id", () => {
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(CustomerServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("lebron"),
{ relations: ["orders", "shipping_addresses"] }
{ relations: ["shipping_addresses"] }
)
})
@@ -17,7 +17,7 @@ export default async (req, res) => {
let customer = await customerService.addAddress(id, value.address)
customer = await customerService.retrieve(id, {
relations: ["orders", "shipping_addresses"],
relations: ["shipping_addresses"],
})
res.status(200).json({ customer })
@@ -27,7 +27,7 @@ export default async (req, res) => {
})
customer = await customerService.retrieve(customer.id, {
relations: ["orders", "shipping_addresses"],
relations: ["shipping_addresses"],
})
res.status(200).json({ customer })
@@ -6,7 +6,7 @@ export default async (req, res) => {
let customer = await customerService.removeAddress(id, address_id)
customer = await customerService.retrieve(id, {
relations: ["orders", "shipping_addresses"],
relations: ["shipping_addresses"],
})
res.json({ customer: data })
@@ -3,7 +3,7 @@ export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
const customer = await customerService.retrieve(id, {
relations: ["orders", "shipping_addresses"],
relations: ["shipping_addresses"],
})
res.json({ customer })
} catch (err) {
@@ -33,6 +33,8 @@ export default (app, container) => {
route.get("/:id", middlewares.wrap(require("./get-customer").default))
route.post("/:id", middlewares.wrap(require("./update-customer").default))
route.get("/:id/orders", middlewares.wrap(require("./list-orders").default))
route.post(
"/:id/addresses",
middlewares.wrap(require("./create-address").default)
Loaded 100 of 152 files, more files were not shown because too many files have changed in this diff. Show more