From 304431e7c35e73b5dcd3ad4f28574b7cda091355 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Wed, 2 Dec 2020 17:37:46 +0100 Subject: [PATCH] fix: allow swaps with total < 0 (#143) --- .../src/services/brightpearl.js | 8 +- .../src/api/routes/admin/orders/index.js | 4 +- ...apture-swap.js => process-swap-payment.js} | 2 +- packages/medusa/src/models/swap.js | 1 + packages/medusa/src/services/swap.js | 170 ++++++++++++------ 5 files changed, 130 insertions(+), 55 deletions(-) rename packages/medusa/src/api/routes/admin/orders/{capture-swap.js => process-swap-payment.js} (90%) diff --git a/packages/medusa-plugin-brightpearl/src/services/brightpearl.js b/packages/medusa-plugin-brightpearl/src/services/brightpearl.js index 579a6f1d5b..5b2919bda0 100644 --- a/packages/medusa-plugin-brightpearl/src/services/brightpearl.js +++ b/packages/medusa-plugin-brightpearl/src/services/brightpearl.js @@ -477,7 +477,7 @@ class BrightpearlService extends BaseService { const soId = fromSwap.metadata && fromSwap.metadata.brightpearl_sales_order_id - if (!soId) { + if (!soId || fromSwap.amount_paid <= 0) { return } @@ -558,6 +558,10 @@ class BrightpearlService extends BaseService { const order = await client.orders.retrieve(salesOrderId) await client.warehouses.createReservation(order, this.options.warehouse) + const total = order.rows.reduce((acc, next) => { + return acc + parseFloat(next.net) + parseFloat(next.tax) + }, 0) + const paymentMethod = fromOrder.payment_method const paymentType = "RECEIPT" const payment = { @@ -566,7 +570,7 @@ class BrightpearlService extends BaseService { paymentMethodCode: this.options.payment_method_code || "1220", orderId: salesOrderId, currencyIsoCode: fromOrder.currency_code, - amountPaid: fromSwap.return.refund_amount, + amountPaid: total, paymentDate: new Date(), paymentType, } diff --git a/packages/medusa/src/api/routes/admin/orders/index.js b/packages/medusa/src/api/routes/admin/orders/index.js index 5625716f02..1790844721 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.js +++ b/packages/medusa/src/api/routes/admin/orders/index.js @@ -128,8 +128,8 @@ export default app => { * Captures the payment associated with a swap */ route.post( - "/:id/swaps/:swap_id/capture", - middlewares.wrap(require("./capture-swap").default) + "/:id/swaps/:swap_id/process-payment", + middlewares.wrap(require("./process-swap-payment").default) ) /** diff --git a/packages/medusa/src/api/routes/admin/orders/capture-swap.js b/packages/medusa/src/api/routes/admin/orders/process-swap-payment.js similarity index 90% rename from packages/medusa/src/api/routes/admin/orders/capture-swap.js rename to packages/medusa/src/api/routes/admin/orders/process-swap-payment.js index 25085d5f2e..b83bb35d8e 100644 --- a/packages/medusa/src/api/routes/admin/orders/capture-swap.js +++ b/packages/medusa/src/api/routes/admin/orders/process-swap-payment.js @@ -7,7 +7,7 @@ export default async (req, res) => { const order = await orderService.retrieve(id) - await swapService.capturePayment(swap_id) + await swapService.processDifference(swap_id) // Decorate the order const data = await orderService.decorate( diff --git a/packages/medusa/src/models/swap.js b/packages/medusa/src/models/swap.js index 0599b53634..3b0710c803 100644 --- a/packages/medusa/src/models/swap.js +++ b/packages/medusa/src/models/swap.js @@ -25,6 +25,7 @@ class SwapModel extends BaseModel { amount_paid: { type: Number }, region_id: { type: String }, currency_code: { type: String }, + order_payment: { type: PaymentMethodSchema }, order_id: { type: String }, cart_id: { type: String }, created: { type: String, default: Date.now }, diff --git a/packages/medusa/src/services/swap.js b/packages/medusa/src/services/swap.js index c909ae9148..f45d9e0430 100644 --- a/packages/medusa/src/services/swap.js +++ b/packages/medusa/src/services/swap.js @@ -12,6 +12,8 @@ class SwapService extends BaseService { PAYMENT_COMPLETED: "swap.payment_completed", PAYMENT_CAPTURED: "swap.payment_captured", PAYMENT_CAPTURE_FAILED: "swap.payment_capture_failed", + PROCESS_REFUND_FAILED: "swap.process_refund_failed", + REFUND_PROCESSED: "swap.refund_processed", } constructor({ @@ -189,6 +191,7 @@ class SwapService extends BaseService { return this.swapModel_.create({ order_id: order._id, + order_payment: order.payment_method, region_id: order.region_id, currency_code: order.currency_code, return_items: validatedReturnItems, @@ -197,6 +200,70 @@ class SwapService extends BaseService { }) } + async processDifference(swapId) { + const swap = await this.retrieve(swapId) + + if (!swap.is_paid) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Cannot process a swap that hasn't been confirmed by the customer" + ) + } + + if (swap.amount_paid < 0) { + const { provider_id, data } = swap.order_payment + const paymentProvider = this.paymentProviderService_.retrieveProvider( + provider_id + ) + + try { + await paymentProvider.refundPayment(data, -1 * swap.amount_paid) + } catch (err) { + return this.swapModel_ + .updateOne( + { + _id: swapId, + }, + { + $set: { payment_status: "requires_action" }, + } + ) + .then(result => { + this.eventBus_.emit( + SwapService.Events.PROCESS_REFUND_FAILED, + result + ) + return result + }) + } + + return this.swapModel_ + .updateOne( + { + _id: swapId, + }, + { + $set: { payment_status: "difference_refunded" }, + } + ) + .then(result => { + this.eventBus_.emit(SwapService.Events.REFUND_PROCESSED, result) + return result + }) + } else if (swap.amount_paid === 0) { + return this.swapModel_.updateOne( + { + _id: swapId, + }, + { + $set: { payment_status: "difference_refunded" }, + } + ) + } + + return capturePayment(swapId) + } + async capturePayment(swapId) { const swap = await this.retrieve(swapId) @@ -351,61 +418,64 @@ class SwapService extends BaseService { ) } - let paymentSession = {} - let paymentData = {} - const { payment_method, payment_sessions } = cart - - if (!payment_method) { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Cart does not contain a payment method" - ) - } - - if (!payment_sessions || !payment_sessions.length) { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "cart must have payment sessions" - ) - } - - paymentSession = payment_sessions.find( - ps => ps.provider_id === payment_method.provider_id - ) - - // Throw if payment method does not exist - if (!paymentSession) { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Cart does not have an authorized payment session" - ) - } - - const paymentProvider = this.paymentProviderService_.retrieveProvider( - paymentSession.provider_id - ) - const paymentStatus = await paymentProvider.getStatus(paymentSession.data) - - // If payment status is not authorized, we throw - if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Payment method is not authorized" - ) - } - - paymentData = await paymentProvider.retrievePayment(paymentSession.data) + const total = await this.totalsService_.getTotal(cart) let payment = {} - if (paymentSession.provider_id) { - payment = { - provider_id: paymentSession.provider_id, - data: paymentData, + + if (total > 0) { + let paymentSession = {} + let paymentData = {} + const { payment_method, payment_sessions } = cart + + if (!payment_method) { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Cart does not contain a payment method" + ) + } + + if (!payment_sessions || !payment_sessions.length) { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "cart must have payment sessions" + ) + } + + paymentSession = payment_sessions.find( + ps => ps.provider_id === payment_method.provider_id + ) + + // Throw if payment method does not exist + if (!paymentSession) { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Cart does not have an authorized payment session" + ) + } + + const paymentProvider = this.paymentProviderService_.retrieveProvider( + paymentSession.provider_id + ) + const paymentStatus = await paymentProvider.getStatus(paymentSession.data) + + // If payment status is not authorized, we throw + if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Payment method is not authorized" + ) + } + + paymentData = await paymentProvider.retrievePayment(paymentSession.data) + + if (paymentSession.provider_id) { + payment = { + provider_id: paymentSession.provider_id, + data: paymentData, + } } } - const total = await this.totalsService_.getTotal(cart) - return this.swapModel_ .updateOne( { _id: swap._id },