merge develop
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -96,4 +96,33 @@ Joi.orderFilter = () => {
|
||||
})
|
||||
}
|
||||
|
||||
Joi.productFilter = () => {
|
||||
return Joi.object().keys({
|
||||
id: Joi.string(),
|
||||
q: Joi.string().allow(null, ""),
|
||||
status: Joi.array()
|
||||
.items(Joi.string().valid("proposed", "draft", "published", "rejected"))
|
||||
.single(),
|
||||
collection_id: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single(),
|
||||
tags: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single(),
|
||||
title: Joi.string(),
|
||||
description: Joi.string(),
|
||||
handle: Joi.string(),
|
||||
is_giftcard: Joi.string(),
|
||||
type: Joi.string(),
|
||||
offset: Joi.string(),
|
||||
limit: Joi.string(),
|
||||
expand: Joi.string(),
|
||||
fields: Joi.string(),
|
||||
order: Joi.string().optional(),
|
||||
created_at: Joi.dateFilter(),
|
||||
updated_at: Joi.dateFilter(),
|
||||
deleted_at: Joi.dateFilter(),
|
||||
})
|
||||
}
|
||||
|
||||
export default Joi
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
15
packages/medusa-file-s3/README.md
Normal file
15
packages/medusa-file-s3/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# medusa-file-s3
|
||||
|
||||
Upload files to an AWS S3 bucket.
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
s3_url: [url of your s3 bucket],
|
||||
access_key_id: [access-key],
|
||||
secret_access_key: [secret-access-key],
|
||||
bucket: [name of your bucket],
|
||||
region: [region of your bucket],
|
||||
```
|
||||
|
||||
Follow [this guide](https://docs.medusa-commerce.com/how-to/uploading-images-to-s3) to configure the plugin.
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
15
packages/medusa-file-spaces/README.md
Normal file
15
packages/medusa-file-spaces/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# medusa-file-spaces
|
||||
|
||||
Upload files to a DigitalOcean Space.
|
||||
|
||||
## Options
|
||||
|
||||
```
|
||||
spaces_url: [url of your DigitalOcean space],
|
||||
access_key_id: [access-key],
|
||||
secret_access_key: [secret-access-key],
|
||||
bucket: [name of your bucket],
|
||||
endpoint: [endpoint of you DigitalOcean space],
|
||||
```
|
||||
|
||||
Follow [this guide](https://docs.medusa-commerce.com/how-to/uploading-images-to-spaces) to configure the plugin.
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports["default"] = void 0;
|
||||
|
||||
var _medusaInterfaces = require("medusa-interfaces");
|
||||
|
||||
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
|
||||
|
||||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
||||
|
||||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
||||
|
||||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
||||
|
||||
function _createSuper(Derived) { return function () { var Super = _getPrototypeOf(Derived), result; if (_isNativeReflectConstruct()) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
||||
|
||||
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
|
||||
|
||||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
||||
|
||||
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
|
||||
|
||||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
var ManualFulfillmentService = /*#__PURE__*/function (_FulfillmentService) {
|
||||
_inherits(ManualFulfillmentService, _FulfillmentService);
|
||||
|
||||
var _super = _createSuper(ManualFulfillmentService);
|
||||
|
||||
function ManualFulfillmentService() {
|
||||
_classCallCheck(this, ManualFulfillmentService);
|
||||
|
||||
return _super.call(this);
|
||||
}
|
||||
|
||||
_createClass(ManualFulfillmentService, [{
|
||||
key: "getFulfillmentOptions",
|
||||
value: function getFulfillmentOptions() {
|
||||
return [{
|
||||
id: "manual-fulfillment"
|
||||
}];
|
||||
}
|
||||
}, {
|
||||
key: "validateFulfillmentData",
|
||||
value: function validateFulfillmentData(data, cart) {
|
||||
return data;
|
||||
}
|
||||
}, {
|
||||
key: "validateOption",
|
||||
value: function validateOption(data) {
|
||||
return true;
|
||||
}
|
||||
}, {
|
||||
key: "canCalculate",
|
||||
value: function canCalculate() {
|
||||
return false;
|
||||
}
|
||||
}, {
|
||||
key: "calculatePrice",
|
||||
value: function calculatePrice() {
|
||||
throw Error("Manual Fulfillment service cannot calculatePrice");
|
||||
}
|
||||
}, {
|
||||
key: "createOrder",
|
||||
value: function createOrder() {
|
||||
// No data is being sent anywhere
|
||||
return;
|
||||
}
|
||||
}]);
|
||||
|
||||
return ManualFulfillmentService;
|
||||
}(_medusaInterfaces.FulfillmentService);
|
||||
|
||||
_defineProperty(ManualFulfillmentService, "identifier", "manual");
|
||||
|
||||
var _default = ManualFulfillmentService;
|
||||
exports["default"] = _default;
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -106,7 +106,13 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
|
||||
const fromOrder = await this.orderService_.retrieve(orderId, {
|
||||
select: ["total"],
|
||||
relations: ["discounts", "shipping_address", "returns"],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_address",
|
||||
"returns",
|
||||
],
|
||||
})
|
||||
|
||||
const methodData = returnOrder.shipping_method.data
|
||||
@@ -286,10 +292,11 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
)) &&
|
||||
this.invoiceGenerator_.createCertificateOfOrigin
|
||||
) {
|
||||
const base64Coo = await this.invoiceGenerator_.createCertificateOfOrigin(
|
||||
fromOrder,
|
||||
fulfillmentItems
|
||||
)
|
||||
const base64Coo =
|
||||
await this.invoiceGenerator_.createCertificateOfOrigin(
|
||||
fromOrder,
|
||||
fulfillmentItems
|
||||
)
|
||||
|
||||
certificateOfOrigin = await this.client_.documents
|
||||
.create({
|
||||
@@ -419,9 +426,8 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
||||
url: l.url,
|
||||
tracking_number: l.number,
|
||||
}))
|
||||
const [orderId, fulfillmentIndex] = wsOrder.data.attributes.ext_ref.split(
|
||||
"."
|
||||
)
|
||||
const [orderId, fulfillmentIndex] =
|
||||
wsOrder.data.attributes.ext_ref.split(".")
|
||||
|
||||
if (orderId.charAt(0).toLowerCase() === "s") {
|
||||
if (fulfillmentIndex.startsWith("ful")) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
241
packages/medusa-payment-stripe/src/services/stripe-bancontact.js
Normal file
241
packages/medusa-payment-stripe/src/services/stripe-bancontact.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import _ from "lodash"
|
||||
import Stripe from "stripe"
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class BancontactProviderService extends PaymentService {
|
||||
static identifier = "stripe-bancontact"
|
||||
|
||||
constructor(
|
||||
{ stripeProviderService, customerService, totalsService, regionService },
|
||||
options
|
||||
) {
|
||||
super()
|
||||
|
||||
/**
|
||||
* Required Stripe options:
|
||||
* {
|
||||
* api_key: "stripe_secret_key", REQUIRED
|
||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||
* // Use this flag to capture payment immediately (default is false)
|
||||
* capture: true
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
|
||||
/** @private @const {Stripe} */
|
||||
this.stripe_ = Stripe(options.api_key)
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.stripeProviderService_ = stripeProviderService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Stripe payment intent. Check its status and returns the
|
||||
* corresponding Medusa status.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {string} the status of the payment intent
|
||||
*/
|
||||
async getStatus(paymentData) {
|
||||
return await this.stripeProviderService_.getStatus(paymentData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @returns {Promise<Array<object>>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a Stripe customer
|
||||
* @param {string} customerId - Stripe customer id
|
||||
* @returns {Promise<object>} Stripe customer
|
||||
*/
|
||||
async retrieveCustomer(customerId) {
|
||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe customer using a Medusa customer.
|
||||
* @param {object} customer - Customer data from Medusa
|
||||
* @returns {Promise<object>} Stripe customer
|
||||
*/
|
||||
async createCustomer(customer) {
|
||||
return await this.stripeProviderService_.createCustomer(customer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Stripe payment intent.
|
||||
* If customer is not registered in Stripe, we do so.
|
||||
* @param {object} cart - cart to create a payment for
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async createPayment(cart) {
|
||||
const { customer_id, region_id, email } = cart
|
||||
const region = await this.regionService_.retrieve(region_id)
|
||||
const { currency_code } = region
|
||||
|
||||
const amount = await this.totalsService_.getTotal(cart)
|
||||
|
||||
const intentRequest = {
|
||||
amount: Math.round(amount),
|
||||
currency: currency_code,
|
||||
payment_method_types: ["bancontact"],
|
||||
capture_method: "automatic",
|
||||
metadata: { cart_id: `${cart.id}` },
|
||||
}
|
||||
|
||||
if (customer_id) {
|
||||
const customer = await this.customerService_.retrieve(customer_id)
|
||||
|
||||
if (customer.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
id: customer_id,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
} else {
|
||||
const stripeCustomer = await this.createCustomer({
|
||||
email,
|
||||
})
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
|
||||
const paymentIntent = await this.stripe_.paymentIntents.create(
|
||||
intentRequest
|
||||
)
|
||||
|
||||
return paymentIntent
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Stripe payment intent.
|
||||
* @param {object} data - the data of the payment to retrieve
|
||||
* @returns {Promise<object>} Stripe payment intent
|
||||
*/
|
||||
async retrievePayment(data) {
|
||||
return await this.stripeProviderService_.retrievePayment(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Stripe payment intent and returns it.
|
||||
* @param {object} sessionData - the data of the payment to retrieve
|
||||
* @returns {Promise<object>} Stripe payment intent
|
||||
*/
|
||||
async getPaymentData(sessionData) {
|
||||
return await this.stripeProviderService_.getPaymentData(sessionData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorizes Stripe payment intent by simply returning
|
||||
* the status for the payment intent in use.
|
||||
* @param {object} sessionData - payment session data
|
||||
* @param {object} context - properties relevant to current context
|
||||
* @returns {Promise<{ status: string, data: object }>} result with data and status
|
||||
*/
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
return await this.stripeProviderService_.authorizePayment(
|
||||
sessionData,
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return await this.stripeProviderService_.updatePaymentData(
|
||||
sessionData,
|
||||
update
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates Stripe payment intent.
|
||||
* @param {object} sessionData - payment session data.
|
||||
* @param {object} update - objec to update intent with
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async updatePayment(sessionData, cart) {
|
||||
try {
|
||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||
|
||||
if (stripeId !== sessionData.customer) {
|
||||
return this.createPayment(cart)
|
||||
} else {
|
||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
||||
amount: Math.round(cart.total),
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return await this.stripeProviderService_.deletePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates customer of Stripe payment intent.
|
||||
* @param {string} paymentIntentId - id of payment intent to update
|
||||
* @param {string} customerId - id of new Stripe customer
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||
paymentIntentId,
|
||||
customerId
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures payment for Stripe payment intent.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {object} Stripe payment intent
|
||||
*/
|
||||
async capturePayment(payment) {
|
||||
return await this.stripeProviderService_.capturePayment(payment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refunds payment for Stripe payment intent.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @param {number} amountToRefund - amount to refund
|
||||
* @returns {string} refunded payment intent
|
||||
*/
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
return await this.stripeProviderService_.refundPayment(
|
||||
payment,
|
||||
amountToRefund
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels payment for Stripe payment intent.
|
||||
* @param {object} paymentData - payment method data from cart
|
||||
* @returns {object} canceled payment intent
|
||||
*/
|
||||
async cancelPayment(payment) {
|
||||
return await this.stripeProviderService_.cancelPayment(payment)
|
||||
}
|
||||
}
|
||||
|
||||
export default BancontactProviderService
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.ContentfulMock = void 0;
|
||||
var ContentfulMock = {
|
||||
createClient: jest.fn()
|
||||
};
|
||||
exports.ContentfulMock = ContentfulMock;
|
||||
@@ -0,0 +1,6 @@
|
||||
export const createClient = jest.fn()
|
||||
const mock = jest.fn().mockImplementation(() => {
|
||||
return { createClient }
|
||||
})
|
||||
|
||||
export default mock
|
||||
@@ -0,0 +1,179 @@
|
||||
import ContentfulService from "../contentful"
|
||||
|
||||
describe("ContentfulService", () => {
|
||||
describe("delete in medusa", () => {
|
||||
const regionService = {
|
||||
retrieve: jest.fn((id) => {
|
||||
if (id === "exists") {
|
||||
return Promise.resolve({ id: "exists" })
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
}
|
||||
const productService = {
|
||||
retrieve: jest.fn((id) => {
|
||||
if (id === "exists") {
|
||||
return Promise.resolve({ id: "exists" })
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
}
|
||||
const redisClient = {
|
||||
get: async (id) => {
|
||||
// const key = `${id}_ignore_${side}`
|
||||
if (id === `ignored_ignore_contentful`) {
|
||||
return { id }
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
set: async (id) => {
|
||||
return undefined
|
||||
},
|
||||
}
|
||||
const productVariantService = {
|
||||
retrieve: jest.fn((id) => {
|
||||
if (id === "exists") {
|
||||
return Promise.resolve({ id: "exists" })
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
}
|
||||
const eventBusService = {}
|
||||
|
||||
const service = new ContentfulService(
|
||||
{
|
||||
regionService,
|
||||
productService,
|
||||
redisClient,
|
||||
productVariantService,
|
||||
eventBusService,
|
||||
},
|
||||
{
|
||||
space_id: "test_id",
|
||||
environment: "master",
|
||||
access_token: "test_token",
|
||||
}
|
||||
)
|
||||
|
||||
const entry = {
|
||||
unpublish: jest.fn(async () => {
|
||||
return {
|
||||
id: "id",
|
||||
}
|
||||
}),
|
||||
archive: jest.fn(async () => {
|
||||
return {
|
||||
id: "id",
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
service.contentful_ = {
|
||||
getSpace: async (space_id) => {
|
||||
return {
|
||||
getEnvironment: async (env) => {
|
||||
return {
|
||||
getEntry: async (id) => {
|
||||
if (id === "onlyMedusa") {
|
||||
throw new Error("doesn't exist")
|
||||
}
|
||||
return entry
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("archiveProductInContentful", () => {
|
||||
it("Calls entry.unpublish and entry.archive", async () => {
|
||||
await service.archiveProductInContentful({ id: "test" })
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(1)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("Doesn't call entry.unpublish and entry.archive if the product still exists in medusa", async () => {
|
||||
await service.archiveProductInContentful({ id: "exists" })
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("Doesn't call productService if request should be ignored", async () => {
|
||||
await service.archiveProductInContentful({ id: "ignored" })
|
||||
|
||||
expect(productService.retrieve).toHaveBeenCalledTimes(0)
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("archiveProductVariantInContentful", () => {
|
||||
it("Calls entry.unpublish and entry.archive", async () => {
|
||||
await service.archiveProductVariantInContentful({ id: "test" })
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(1)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("Doesn't call entry.unpublish and entry.archive if the variant still exists in medusa", async () => {
|
||||
await service.archiveProductVariantInContentful({ id: "exists" })
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("Doesn't call productVariantService if request should be ignored", async () => {
|
||||
await service.archiveProductVariantInContentful({ id: "ignored" })
|
||||
|
||||
expect(productVariantService.retrieve).toHaveBeenCalledTimes(0)
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("archiveRegionInContentful", () => {
|
||||
it("Calls entry.unpublish and entry.archive", async () => {
|
||||
await service.archiveRegionInContentful({ id: "test" })
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(1)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("Doesn't call entry.unpublish and entry.archive if the region still exists in medusa", async () => {
|
||||
await service.archiveRegionInContentful({ id: "exists" })
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it("Doesn't call RegionService if request should be ignored", async () => {
|
||||
await service.archiveRegionInContentful({ id: "ignored" })
|
||||
|
||||
expect(regionService.retrieve).toHaveBeenCalledTimes(0)
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("archiveEntryWidthId", () => {
|
||||
it("Calls archive if entry exists", async () => {
|
||||
await service.archiveEntryWidthId("exists")
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(1)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
it("Doesnt call archive if entry doesn't exists", async () => {
|
||||
await service.archiveEntryWidthId("onlyMedusa")
|
||||
|
||||
expect(entry.unpublish).toHaveBeenCalledTimes(0)
|
||||
expect(entry.archive).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -91,7 +91,7 @@ class ContentfulService extends BaseService {
|
||||
async createImageAssets(product) {
|
||||
const environment = await this.getContentfulEnvironment_()
|
||||
|
||||
let assets = []
|
||||
const assets = []
|
||||
await Promise.all(
|
||||
product.images
|
||||
.filter((image) => image.url !== product.thumbnail)
|
||||
@@ -646,6 +646,92 @@ class ContentfulService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async archiveProductVariantInContentful(variant) {
|
||||
let variantEntity
|
||||
try {
|
||||
const ignore = await this.shouldIgnore_(variant.id, "contentful")
|
||||
if (ignore) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
try {
|
||||
variantEntity = await this.productVariantService_.retrieve(variant.id)
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (variantEntity) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return await this.archiveEntryWidthId(variant.id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async archiveProductInContentful(product) {
|
||||
let productEntity
|
||||
try {
|
||||
const ignore = await this.shouldIgnore_(product.id, "contentful")
|
||||
if (ignore) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
try {
|
||||
productEntity = await this.productService_.retrieve(product.id)
|
||||
} catch (err) {}
|
||||
|
||||
if (productEntity) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return await this.archiveEntryWidthId(product.id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async archiveRegionInContentful(region) {
|
||||
let regionEntity
|
||||
try {
|
||||
const ignore = await this.shouldIgnore_(region.id, "contentful")
|
||||
if (ignore) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
try {
|
||||
regionEntity = await this.regionService_.retrieve(region.id)
|
||||
} catch (err) {}
|
||||
|
||||
if (regionEntity) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return await this.archiveEntryWidthId(region.id)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async archiveEntryWidthId(id) {
|
||||
const environment = await this.getContentfulEnvironment_()
|
||||
// check if product exists
|
||||
let entry = undefined
|
||||
try {
|
||||
entry = await environment.getEntry(id)
|
||||
} catch (error) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const unpublishEntry = await entry.unpublish()
|
||||
const archivedEntry = await entry.archive()
|
||||
|
||||
await this.addIgnore_(id, "medusa")
|
||||
|
||||
return archivedEntry
|
||||
}
|
||||
|
||||
async sendContentfulProductToAdmin(productId) {
|
||||
const ignore = await this.shouldIgnore_(productId, "medusa")
|
||||
if (ignore) {
|
||||
@@ -658,7 +744,7 @@ class ContentfulService extends BaseService {
|
||||
|
||||
const product = await this.productService_.retrieve(productId)
|
||||
|
||||
let update = {}
|
||||
const update = {}
|
||||
|
||||
const title =
|
||||
productEntry.fields[this.getCustomField("title", "product")]["en-US"]
|
||||
@@ -741,9 +827,9 @@ class ContentfulService extends BaseService {
|
||||
isArray = false
|
||||
}
|
||||
|
||||
let output = []
|
||||
const output = []
|
||||
for (const obj of input) {
|
||||
let transformed = Object.assign({}, obj)
|
||||
const transformed = Object.assign({}, obj)
|
||||
transformed.medusaId = obj.id
|
||||
output.push(transformed)
|
||||
}
|
||||
|
||||
@@ -18,10 +18,18 @@ class ContentfulSubscriber {
|
||||
await this.contentfulService_.updateRegionInContentful(data)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("region.deleted", async (data) => {
|
||||
await this.contentfulService_.updateRegionInContentful(data)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("product-variant.updated", async (data) => {
|
||||
await this.contentfulService_.updateProductVariantInContentful(data)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("product-variant.deleted", async (data) => {
|
||||
await this.contentfulService_.archiveProductVariantInContentful(data)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("product.updated", async (data) => {
|
||||
await this.contentfulService_.updateProductInContentful(data)
|
||||
})
|
||||
@@ -29,6 +37,10 @@ class ContentfulSubscriber {
|
||||
this.eventBus_.subscribe("product.created", async (data) => {
|
||||
await this.contentfulService_.createProductInContentful(data)
|
||||
})
|
||||
|
||||
this.eventBus_.subscribe("product.deleted", async (data) => {
|
||||
await this.contentfulService_.archiveProductInContentful(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -59,7 +59,9 @@ class MeiliSearchService extends SearchService {
|
||||
}
|
||||
|
||||
transformProducts(products) {
|
||||
if (!products) return []
|
||||
if (!products) {
|
||||
return []
|
||||
}
|
||||
return products.map(transformProduct)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ export const transformProduct = (product) => {
|
||||
variantKeys.forEach((k) => {
|
||||
if (k === "options" && variant[k]) {
|
||||
const values = variant[k].map((option) => option.value)
|
||||
obj[`${prefix}_options_value`] = obj[`${prefix}_options_value`].concat(
|
||||
values
|
||||
)
|
||||
obj[`${prefix}_options_value`] =
|
||||
obj[`${prefix}_options_value`].concat(values)
|
||||
return
|
||||
}
|
||||
return variant[k] && obj[`${prefix}_${k}`].push(variant[k])
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -135,6 +135,57 @@ describe("RestockNotificationService", () => {
|
||||
})
|
||||
|
||||
describe("triggerRestock", () => {
|
||||
afterEach(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it("trigger delay default to 0", async () => {
|
||||
const restockNotiService = new RestockNotificationService({
|
||||
manager: MockManager,
|
||||
productVariantService: ProductVariantService,
|
||||
restockNotificationModel: RestockNotificationModel,
|
||||
eventBusService: EventBusService,
|
||||
})
|
||||
|
||||
restockNotiService.restockExecute_ = jest.fn()
|
||||
|
||||
jest.clearAllMocks()
|
||||
jest.useFakeTimers()
|
||||
|
||||
restockNotiService.triggerRestock("variant_test")
|
||||
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1)
|
||||
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 0)
|
||||
})
|
||||
|
||||
it("trigger delay 10", async () => {
|
||||
const restockNotiService = new RestockNotificationService(
|
||||
{
|
||||
manager: MockManager,
|
||||
productVariantService: ProductVariantService,
|
||||
restockNotificationModel: RestockNotificationModel,
|
||||
eventBusService: EventBusService,
|
||||
},
|
||||
{ trigger_delay: 10 }
|
||||
)
|
||||
|
||||
restockNotiService.restockExecute_ = jest.fn()
|
||||
|
||||
jest.clearAllMocks()
|
||||
jest.useFakeTimers()
|
||||
|
||||
restockNotiService.triggerRestock("variant_test")
|
||||
|
||||
jest.runAllTimers()
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledTimes(1)
|
||||
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 10)
|
||||
})
|
||||
})
|
||||
|
||||
describe("restockExecute_", () => {
|
||||
const restockNotiService = new RestockNotificationService({
|
||||
manager: MockManager,
|
||||
productVariantService: ProductVariantService,
|
||||
@@ -145,20 +196,20 @@ describe("RestockNotificationService", () => {
|
||||
it("non-existing noti does nothing", async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
await expect(restockNotiService.triggerRestock("variant_test")).resolves
|
||||
await expect(restockNotiService.restockExecute_("variant_test")).resolves
|
||||
})
|
||||
|
||||
it("existing noti but out of stock does nothing", async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
await expect(restockNotiService.triggerRestock("variant_outofstock"))
|
||||
await expect(restockNotiService.restockExecute_("variant_outofstock"))
|
||||
.resolves
|
||||
})
|
||||
|
||||
it("existing noti emits and deletes", async () => {
|
||||
jest.clearAllMocks()
|
||||
|
||||
await restockNotiService.triggerRestock("variant_1234")
|
||||
await restockNotiService.restockExecute_("variant_1234")
|
||||
|
||||
expect(EventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
expect(EventBusService.emit).toHaveBeenCalledWith(
|
||||
@@ -187,7 +238,7 @@ describe("RestockNotificationService", () => {
|
||||
{ inventory_required: 5 }
|
||||
)
|
||||
|
||||
await service.triggerRestock("variant_1234")
|
||||
await service.restockExecute_("variant_1234")
|
||||
|
||||
expect(EventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
expect(EventBusService.emit).toHaveBeenCalledWith(
|
||||
@@ -214,7 +265,7 @@ describe("RestockNotificationService", () => {
|
||||
{ inventory_required: 5 }
|
||||
)
|
||||
|
||||
await service.triggerRestock("variant_low_inventory")
|
||||
await service.restockExecute_("variant_low_inventory")
|
||||
|
||||
expect(EventBusService.emit).toHaveBeenCalledTimes(0)
|
||||
expect(RestockNotificationModel.delete).toHaveBeenCalledTimes(0)
|
||||
|
||||
@@ -108,10 +108,15 @@ class RestockNotificationService extends BaseService {
|
||||
* and emits a restocked event to the event bus. After successful emission the
|
||||
* restock notification is deleted.
|
||||
* @param {string} variantId - the variant id to trigger restock for
|
||||
* @return {Promise<RestockNotification>} The resulting restock notification
|
||||
* @return The resulting restock notification
|
||||
*/
|
||||
async triggerRestock(variantId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
triggerRestock(variantId) {
|
||||
const delay = this.options_?.trigger_delay ?? 0
|
||||
setTimeout(() => this.restockExecute_(variantId), delay)
|
||||
}
|
||||
|
||||
async restockExecute_(variantId) {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const restockRepo = manager.getRepository(this.restockNotificationModel_)
|
||||
|
||||
const existing = await this.retrieve(variantId)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -44,6 +44,8 @@ class OrderSubscriber {
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
@@ -146,6 +148,8 @@ class OrderSubscriber {
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
@@ -253,6 +257,8 @@ class OrderSubscriber {
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -320,6 +320,8 @@ class SendGridService extends NotificationService {
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"payments",
|
||||
@@ -363,6 +365,8 @@ class SendGridService extends NotificationService {
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"payments",
|
||||
@@ -496,6 +500,8 @@ class SendGridService extends NotificationService {
|
||||
relations: [
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_address",
|
||||
"returns",
|
||||
"swaps",
|
||||
@@ -601,6 +607,8 @@ class SendGridService extends NotificationService {
|
||||
relations: [
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_address",
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
@@ -686,7 +694,14 @@ class SendGridService extends NotificationService {
|
||||
})
|
||||
|
||||
const order = await this.orderService_.retrieve(swap.order_id, {
|
||||
relations: ["items", "discounts", "swaps", "swaps.additional_items"],
|
||||
relations: [
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
],
|
||||
})
|
||||
|
||||
let merged = [...order.items]
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class SlackService extends BaseService {
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier"],
|
||||
"extends": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"semi": "error",
|
||||
"no-unused-expressions": "true"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"endOfLine": "lf",
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
"glob": "^7.1.6",
|
||||
"ioredis": "^4.17.3",
|
||||
"ioredis-mock": "^5.6.0",
|
||||
"iso8601-duration": "^1.3.0",
|
||||
"joi": "^17.3.0",
|
||||
"joi-objectid": "^3.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
@@ -87,4 +88,4 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
|
||||
}
|
||||
}
|
||||
17
packages/medusa/src/api/routes/admin/auth/delete-session.js
Normal file
17
packages/medusa/src/api/routes/admin/auth/delete-session.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import _ from "lodash"
|
||||
|
||||
/**
|
||||
* @oas [get] /auth
|
||||
* operationId: "DeleteAuth"
|
||||
* summary: "Delete Session"
|
||||
* description: "Deletes the current session for the logged in user."
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* "200":
|
||||
* description: OK
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
req.session.destroy()
|
||||
res.status(200).end()
|
||||
}
|
||||
@@ -13,5 +13,11 @@ export default app => {
|
||||
)
|
||||
route.post("/", middlewares.wrap(require("./create-session").default))
|
||||
|
||||
route.delete(
|
||||
"/",
|
||||
middlewares.authenticate(),
|
||||
middlewares.wrap(require("./delete-session").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
],
|
||||
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
],
|
||||
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ describe("POST /admin/discounts", () => {
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
ends_at: "03/14/2021 04:30",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
@@ -39,12 +41,99 @@ describe("POST /admin/discounts", () => {
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("02/02/2021 13:45"),
|
||||
ends_at: new Date("03/14/2021 04:30"),
|
||||
is_disabled: false,
|
||||
is_dynamic: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("unsuccessful creation with dynamic discount using an invalid iso8601 duration", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "PaMT2D",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"valid_duration" must be a valid ISO 8601 duration`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful creation with dynamic discount", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service create", () => {
|
||||
expect(DiscountServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("02/02/2021 13:45"),
|
||||
is_disabled: false,
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails on invalid data", () => {
|
||||
let subject
|
||||
|
||||
@@ -74,4 +163,84 @@ describe("POST /admin/discounts", () => {
|
||||
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails on invalid date intervals", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
ends_at: "02/02/2021",
|
||||
starts_at: "03/14/2021",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"ends_at" must be greater than "ref:starts_at"`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("succesfully creates a dynamic discount without setting valid duration", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
is_dynamic: true,
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "03/14/2021 14:30",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
|
||||
code: "TEST",
|
||||
is_dynamic: true,
|
||||
is_disabled: false,
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("03/14/2021 14:30"),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
const defaultRelations = [
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
const defaultRelations = [
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
const defaultRelations = [
|
||||
|
||||
@@ -7,6 +7,7 @@ describe("POST /admin/discounts", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
@@ -50,4 +51,139 @@ describe("POST /admin/discounts", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("unsuccessful update with dynamic discount using an invalid iso8601 duration", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
{
|
||||
payload: {
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "PaMT2D",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"valid_duration" must be a valid ISO 8601 duration`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful update with dynamic discount", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
{
|
||||
payload: {
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service update", () => {
|
||||
expect(DiscountServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("total10"),
|
||||
{
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("02/02/2021 13:45"),
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails on invalid date intervals", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
{
|
||||
payload: {
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
ends_at: "02/02/2021",
|
||||
starts_at: "03/14/2021",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"ends_at" must be greater than "ref:starts_at"`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /discounts
|
||||
@@ -71,7 +72,13 @@ export default async (req, res) => {
|
||||
.required(),
|
||||
is_disabled: Validator.boolean().default(false),
|
||||
starts_at: Validator.date().optional(),
|
||||
ends_at: Validator.date().optional(),
|
||||
ends_at: Validator.date()
|
||||
.greater(Validator.ref("starts_at"))
|
||||
.optional(),
|
||||
valid_duration: Validator.string()
|
||||
.isoDuration()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
usage_limit: Validator.number()
|
||||
.positive()
|
||||
.optional(),
|
||||
@@ -90,11 +97,10 @@ export default async (req, res) => {
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
|
||||
const created = await discountService.create(value)
|
||||
const discount = await discountService.retrieve(created.id, [
|
||||
"rule",
|
||||
"rule.valid_for",
|
||||
"regions",
|
||||
])
|
||||
const discount = await discountService.retrieve(
|
||||
created.id,
|
||||
defaultRelations
|
||||
)
|
||||
|
||||
res.status(200).json({ discount })
|
||||
} catch (err) {
|
||||
|
||||
@@ -37,9 +37,9 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
await discountService.createDynamicCode(discount_id, value)
|
||||
const created = await discountService.createDynamicCode(discount_id, value)
|
||||
|
||||
const discount = await discountService.retrieve(discount_id, {
|
||||
const discount = await discountService.retrieve(created.id, {
|
||||
relations: ["rule", "rule.valid_for", "regions"],
|
||||
})
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ export const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
export const defaultRelations = [
|
||||
|
||||
@@ -68,7 +68,16 @@ export default async (req, res) => {
|
||||
.optional(),
|
||||
is_disabled: Validator.boolean().optional(),
|
||||
starts_at: Validator.date().optional(),
|
||||
ends_at: Validator.date().optional(),
|
||||
ends_at: Validator.when("starts_at", {
|
||||
not: undefined,
|
||||
then: Validator.date()
|
||||
.greater(Validator.ref("starts_at"))
|
||||
.optional(),
|
||||
otherwise: Validator.date().optional(),
|
||||
}),
|
||||
valid_duration: Validator.string()
|
||||
.isoDuration().allow(null)
|
||||
.optional(),
|
||||
usage_limit: Validator.number()
|
||||
.positive()
|
||||
.optional(),
|
||||
@@ -78,6 +87,7 @@ export default async (req, res) => {
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export const defaultCartRelations = [
|
||||
"payment_sessions",
|
||||
"shipping_methods.shipping_option",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
]
|
||||
|
||||
export const defaultCartFields = [
|
||||
|
||||
@@ -43,7 +43,14 @@ export default async (req, res) => {
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
select: ["total"],
|
||||
relations: ["discounts", "shipping_methods", "region", "items"],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"region",
|
||||
"items",
|
||||
],
|
||||
})
|
||||
|
||||
await paymentProviderService
|
||||
|
||||
@@ -7,6 +7,8 @@ const defaultRelations = [
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
@@ -25,6 +27,7 @@ const defaultRelations = [
|
||||
"claims.additional_items",
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.item",
|
||||
"claims.claim_items.images",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
@@ -54,6 +57,7 @@ const defaultFields = [
|
||||
"metadata",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
|
||||
@@ -202,7 +202,12 @@ export default async (req, res) => {
|
||||
const order = await orderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, {
|
||||
relations: ["items", "discounts"],
|
||||
relations: [
|
||||
"items",
|
||||
"cart",
|
||||
"cart.discounts",
|
||||
"cart.discounts.rule",
|
||||
],
|
||||
})
|
||||
|
||||
await claimService.withTransaction(manager).create({
|
||||
|
||||
@@ -45,6 +45,17 @@ import { defaultFields, defaultRelations } from "./"
|
||||
* quantity:
|
||||
* description: The quantity of the Product Variant to ship.
|
||||
* type: integer
|
||||
* custom_shipping_options:
|
||||
* description: The custom shipping options to potentially create a Shipping Method from.
|
||||
* type: array
|
||||
* items:
|
||||
* properties:
|
||||
* option_id:
|
||||
* description: The id of the Shipping Option to override with a custom price.
|
||||
* type: string
|
||||
* price:
|
||||
* description: The custom price of the Shipping Option.
|
||||
* type: integer
|
||||
* no_notification:
|
||||
* description: If set to true no notification will be send related to this Swap.
|
||||
* type: boolean
|
||||
@@ -85,6 +96,12 @@ export default async (req, res) => {
|
||||
variant_id: Validator.string().required(),
|
||||
quantity: Validator.number().required(),
|
||||
}),
|
||||
custom_shipping_options: Validator.array()
|
||||
.items({
|
||||
option_id: Validator.string().required(),
|
||||
price: Validator.number().required(),
|
||||
})
|
||||
.default([]),
|
||||
no_notification: Validator.boolean().optional(),
|
||||
allow_backorder: Validator.boolean().default(true),
|
||||
})
|
||||
@@ -149,7 +166,9 @@ export default async (req, res) => {
|
||||
}
|
||||
)
|
||||
|
||||
await swapService.withTransaction(manager).createCart(swap.id)
|
||||
await swapService
|
||||
.withTransaction(manager)
|
||||
.createCart(swap.id, value.custom_shipping_options)
|
||||
const returnOrder = await returnService
|
||||
.withTransaction(manager)
|
||||
.retrieveBySwap(swap.id)
|
||||
|
||||
@@ -221,6 +221,8 @@ export const defaultRelations = [
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
@@ -239,6 +241,7 @@ export const defaultRelations = [
|
||||
"claims.additional_items",
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.item",
|
||||
"claims.claim_items.images",
|
||||
// "claims.claim_items.tags",
|
||||
"swaps",
|
||||
@@ -269,6 +272,7 @@ export const defaultFields = [
|
||||
"metadata",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
@@ -316,6 +320,8 @@ export const allowedRelations = [
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
|
||||
@@ -46,7 +46,11 @@ export default app => {
|
||||
)
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-product").default))
|
||||
route.get("/", middlewares.wrap(require("./list-products").default))
|
||||
route.get(
|
||||
"/",
|
||||
middlewares.normalizeQuery(),
|
||||
middlewares.wrap(require("./list-products").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -121,3 +125,18 @@ export const allowedRelations = [
|
||||
"type",
|
||||
"collection",
|
||||
]
|
||||
|
||||
export const filterableFields = [
|
||||
"id",
|
||||
"status",
|
||||
"collection_id",
|
||||
"tags",
|
||||
"title",
|
||||
"description",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"type",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from "lodash"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
import { defaultFields, defaultRelations, filterableFields } from "./"
|
||||
|
||||
/**
|
||||
* @oas [get] /products
|
||||
@@ -31,6 +31,17 @@ import { defaultFields, defaultRelations } from "./"
|
||||
* $ref: "#/components/schemas/product"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.productFilter()
|
||||
|
||||
const { value, error } = schema.validate(req.query)
|
||||
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
JSON.stringify(error.details)
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
|
||||
@@ -53,21 +64,16 @@ export default async (req, res) => {
|
||||
expandFields = req.query.expand.split(",")
|
||||
}
|
||||
|
||||
if ("is_giftcard" in req.query) {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
for (const k of filterableFields) {
|
||||
if (k in value) {
|
||||
selector[k] = value[k]
|
||||
}
|
||||
}
|
||||
|
||||
if ("status" in req.query) {
|
||||
const schema = Validator.array()
|
||||
.items(
|
||||
Validator.string().valid("proposed", "draft", "published", "rejected")
|
||||
)
|
||||
.single()
|
||||
|
||||
const { value, error } = schema.validate(req.query.status)
|
||||
|
||||
if (value && !error) {
|
||||
selector.status = value
|
||||
if (selector.status?.indexOf("null") > -1) {
|
||||
selector.status.splice(selector.status.indexOf("null"), 1)
|
||||
if (selector.status.length === 0) {
|
||||
delete selector.status
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShipping", () => {
|
||||
it("calls CartService addShippingMethod", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
@@ -45,6 +45,50 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("successfully adds a shipping method", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const cartId = IdMap.getId("swap-cart")
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/carts/${cartId}/shipping-methods`,
|
||||
{
|
||||
payload: {
|
||||
option_id: IdMap.getId("freeShipping"),
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShippingMethod", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("swap-cart"),
|
||||
IdMap.getId("freeShipping"),
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it("calls CartService retrieve", () => {
|
||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns the cart", () => {
|
||||
expect(subject.body.cart).toEqual(
|
||||
expect.objectContaining({ type: "swap", id: IdMap.getId("test-swap") })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successfully adds a shipping method with additional data", () => {
|
||||
let subject
|
||||
|
||||
@@ -68,7 +112,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShipping", () => {
|
||||
it("calls CartService addShippingMethod", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
|
||||
@@ -44,7 +44,9 @@ export default async (req, res) => {
|
||||
|
||||
await manager.transaction(async m => {
|
||||
const txCartService = cartService.withTransaction(m)
|
||||
|
||||
await txCartService.addShippingMethod(id, value.option_id, value.data)
|
||||
|
||||
const updated = await txCartService.retrieve(id, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -54,12 +56,12 @@ export default async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
const cart = await cartService.retrieve(id, {
|
||||
const updatedCart = await cartService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ cart })
|
||||
res.status(200).json({ cart: updatedCart })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -82,6 +82,14 @@ export default async (req, res) => {
|
||||
if (!value.region_id) {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const regions = await regionService.withTransaction(manager).list({})
|
||||
|
||||
if (!regions?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`A region is required to create a cart`
|
||||
)
|
||||
}
|
||||
|
||||
regionId = regions[0].id
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user