fix(medusa-payment-klarna, medusa-payment-stripe, medusa-payment-paypal): Totals calculation (#2381)
This commit is contained in:
7
.changeset/dull-sheep-raise.md
Normal file
7
.changeset/dull-sheep-raise.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"medusa-payment-klarna": patch
|
||||
"medusa-payment-paypal": patch
|
||||
"medusa-payment-stripe": patch
|
||||
---
|
||||
|
||||
Rely on cart totals in payment providers
|
||||
@@ -16,6 +16,7 @@ export const carts = {
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
tax_lines: [],
|
||||
content: [
|
||||
{
|
||||
unit_price: 8,
|
||||
@@ -45,6 +46,7 @@ export const carts = {
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
tax_lines: [],
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
@@ -62,6 +64,7 @@ export const carts = {
|
||||
{
|
||||
id: IdMap.getId("freeShipping"),
|
||||
name: "Free shipping",
|
||||
tax_lines: [],
|
||||
data: {
|
||||
name: "test",
|
||||
},
|
||||
|
||||
@@ -2,27 +2,10 @@ export default async (req, res) => {
|
||||
// In Medusa, we store the cart id in merchant_data
|
||||
const { shipping_address, merchant_data } = req.body
|
||||
|
||||
try {
|
||||
const manager = req.scope.resolve("manager")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
||||
|
||||
const result = await manager.transaction("SERIALIZABLE", async (m) => {
|
||||
const cart = await cartService.retrieve(merchant_data, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region",
|
||||
"items",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
|
||||
if (shipping_address) {
|
||||
const shippingAddress = {
|
||||
first_name: shipping_address.given_name,
|
||||
@@ -46,38 +29,13 @@ export default async (req, res) => {
|
||||
phone: shipping_address.phone,
|
||||
}
|
||||
|
||||
await cartService.update(cart.id, {
|
||||
await cartService.update(merchant_data, {
|
||||
shipping_address: shippingAddress,
|
||||
billing_address: billingAddress,
|
||||
email: shipping_address.email,
|
||||
})
|
||||
|
||||
const shippingOptions = await shippingProfileService.fetchCartOptions(
|
||||
cart
|
||||
)
|
||||
|
||||
if (shippingOptions?.length) {
|
||||
const option = shippingOptions.find(
|
||||
(o) => o.data && !o.data.require_drop_point
|
||||
)
|
||||
await cartService
|
||||
.withTransaction(m)
|
||||
.addShippingMethod(cart.id, option.id, option.data)
|
||||
}
|
||||
|
||||
// Fetch and return updated Klarna order
|
||||
const updatedCart = await cartService
|
||||
.withTransaction(m)
|
||||
.retrieve(cart.id, {
|
||||
select: [
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
"shipping_total",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"subtotal",
|
||||
],
|
||||
let cart = await cartService.retrieveWithTotals(merchant_data, {
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
@@ -89,20 +47,33 @@ export default async (req, res) => {
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
return klarnaProviderService.cartToKlarnaOrder(updatedCart)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
})
|
||||
const shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
||||
|
||||
if (result) {
|
||||
res.json(result)
|
||||
return
|
||||
if (shippingOptions?.length) {
|
||||
const option = shippingOptions.find(
|
||||
(o) => o.data && !o.data.require_drop_point
|
||||
)
|
||||
if (option) {
|
||||
await cartService.addShippingMethod(cart.id, option.id, option.data)
|
||||
cart = await cartService.retrieveWithTotals(cart.id, {
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region",
|
||||
"shipping_methods",
|
||||
"shipping_methods.shipping_option",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const order = await klarnaProviderService.cartToKlarnaOrder(cart)
|
||||
res.json(order)
|
||||
} else {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ export default async (req, res) => {
|
||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
||||
|
||||
const cart = await cartService.retrieve(merchant_data, {
|
||||
select: ["subtotal"],
|
||||
const cart = await cartService.retrieveWithTotals(
|
||||
merchant_data,
|
||||
{
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
@@ -20,7 +21,9 @@ export default async (req, res) => {
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
},
|
||||
{ force_taxes: true }
|
||||
)
|
||||
let shippingOptions = await shippingProfileService.fetchCartOptions(cart)
|
||||
|
||||
shippingOptions = shippingOptions.filter(
|
||||
@@ -35,16 +38,9 @@ export default async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
const newCart = await cartService.retrieve(cart.id, {
|
||||
select: [
|
||||
"gift_card_total",
|
||||
"subtotal",
|
||||
"total",
|
||||
"shipping_total",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"subtotal",
|
||||
],
|
||||
const newCart = await cartService.retrieveWithTotals(
|
||||
cart.id,
|
||||
{
|
||||
relations: [
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
@@ -56,7 +52,9 @@ export default async (req, res) => {
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
},
|
||||
{ force_taxes: true }
|
||||
)
|
||||
|
||||
const order = await klarnaProviderService.cartToKlarnaOrder(newCart)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PaymentService } from "medusa-interfaces"
|
||||
class KlarnaProviderService extends PaymentService {
|
||||
static identifier = "klarna"
|
||||
|
||||
constructor({ shippingProfileService, totalsService }, options) {
|
||||
constructor({ logger, shippingProfileService, totalsService }, options) {
|
||||
super()
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,7 @@ class KlarnaProviderService extends PaymentService {
|
||||
* }
|
||||
*/
|
||||
this.options_ = options
|
||||
this.logger_ = logger
|
||||
|
||||
/** @private @const {Klarna} */
|
||||
this.klarna_ = axios.create({
|
||||
@@ -53,22 +54,16 @@ class KlarnaProviderService extends PaymentService {
|
||||
// Withdraw discount from the total item amount
|
||||
const quantity = item.quantity
|
||||
|
||||
const totals = await this.totalsService_.getLineItemTotals(item, cart, {
|
||||
include_tax: true,
|
||||
})
|
||||
|
||||
const tax =
|
||||
totals.tax_lines.reduce((acc, next) => acc + next.rate, 0) / 100
|
||||
const tax = item.tax_lines.reduce((acc, next) => acc + next.rate, 0) / 100
|
||||
|
||||
order_lines.push({
|
||||
name: item.title,
|
||||
tax_rate: tax * 10000,
|
||||
quantity,
|
||||
unit_price: Math.round(totals.original_total / item.quantity),
|
||||
total_amount: totals.total - totals.gift_card_total,
|
||||
total_tax_amount: totals.tax_total,
|
||||
total_discount_amount:
|
||||
totals.original_total - totals.total + totals.gift_card_total,
|
||||
unit_price: Math.round(item.original_total / item.quantity),
|
||||
total_amount: item.total,
|
||||
total_tax_amount: item.tax_total,
|
||||
total_discount_amount: item.original_total - item.total,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,23 +74,15 @@ class KlarnaProviderService extends PaymentService {
|
||||
let taxRate = 0
|
||||
|
||||
if (cart.shipping_total > 0) {
|
||||
for (const next of cart.shipping_methods) {
|
||||
const totals = await this.totalsService_.getShippingMethodTotals(
|
||||
next,
|
||||
cart,
|
||||
{
|
||||
include_tax: true,
|
||||
}
|
||||
)
|
||||
|
||||
for (const method of cart.shipping_methods) {
|
||||
const methodTaxRate =
|
||||
totals.tax_lines.reduce((acc, next) => acc + next.rate, 0) / 100
|
||||
method.tax_lines.reduce((acc, next) => acc + next.rate, 0) / 100
|
||||
|
||||
name.push(next?.shipping_option.name)
|
||||
name.push(method.shipping_option.name)
|
||||
|
||||
total += totals.total
|
||||
taxRate += (totals.price / cart.shipping_total) * methodTaxRate
|
||||
tax += totals.tax_total
|
||||
total += method.total
|
||||
taxRate += (method.price / cart.shipping_total) * methodTaxRate
|
||||
tax += method.tax_total
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,16 +111,17 @@ class KlarnaProviderService extends PaymentService {
|
||||
|
||||
order.order_lines = await this.lineItemsToOrderLines_(cart)
|
||||
|
||||
if (gift_card_total && !region.gift_cards_taxable) {
|
||||
if (gift_card_total) {
|
||||
const taxRate = cart.gift_card_tax_total / cart.gift_card_total
|
||||
|
||||
order.order_lines.push({
|
||||
name: `Gift Card`,
|
||||
name: "Gift Card",
|
||||
quantity: 1,
|
||||
type: "gift_card",
|
||||
unit_price: 0,
|
||||
total_discount_amount: gift_card_total,
|
||||
tax_rate: 0,
|
||||
total_amount: -gift_card_total,
|
||||
total_tax_amount: 0,
|
||||
unit_price: -1 * (cart.gift_card_total + cart.gift_card_tax_total),
|
||||
tax_rate: Math.round(taxRate * 10000),
|
||||
total_amount: -1 * (cart.gift_card_total + cart.gift_card_tax_total),
|
||||
total_tax_amount: -1 * cart.gift_card_tax_total,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -159,7 +147,7 @@ class KlarnaProviderService extends PaymentService {
|
||||
}
|
||||
|
||||
order.order_amount = total
|
||||
order.order_tax_amount = tax_total
|
||||
order.order_tax_amount = tax_total - cart.gift_card_tax_total
|
||||
order.purchase_currency = region.currency_code.toUpperCase()
|
||||
|
||||
order.merchant_urls = {
|
||||
@@ -183,21 +171,13 @@ class KlarnaProviderService extends PaymentService {
|
||||
// If the cart does not have shipping methods yet, preselect one from
|
||||
// shipping_options and set the selected shipping method
|
||||
if (cart.shipping_methods.length) {
|
||||
const shipping_method = cart.shipping_methods[0]
|
||||
const totals = await this.totalsService_.getShippingMethodTotals(
|
||||
shipping_method,
|
||||
cart,
|
||||
{
|
||||
include_tax: true,
|
||||
}
|
||||
)
|
||||
|
||||
const taxRate = totals.tax_total / (totals.total - totals.tax_total)
|
||||
const method = cart.shipping_methods[0]
|
||||
const taxRate = method.tax_total / (method.total - method.tax_total)
|
||||
order.selected_shipping_option = {
|
||||
id: shipping_method.shipping_option.id,
|
||||
name: shipping_method.shipping_option.name,
|
||||
price: totals.total,
|
||||
tax_amount: total.tax_total,
|
||||
id: method.shipping_option.id,
|
||||
name: method.shipping_option.name,
|
||||
price: method.total,
|
||||
tax_amount: method.tax_total,
|
||||
tax_rate: taxRate * 10000,
|
||||
}
|
||||
}
|
||||
@@ -286,6 +266,7 @@ class KlarnaProviderService extends PaymentService {
|
||||
|
||||
return klarnaPayment
|
||||
} catch (error) {
|
||||
this.logger_.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ describe("PaypalProviderService", () => {
|
||||
const paypalProviderService = new PayPalProviderService(
|
||||
{
|
||||
regionService: RegionServiceMock,
|
||||
totalsService: TotalsServiceMock,
|
||||
},
|
||||
{
|
||||
api_key: "test",
|
||||
@@ -98,7 +97,6 @@ describe("PaypalProviderService", () => {
|
||||
const paypalProviderService = new PayPalProviderService(
|
||||
{
|
||||
regionService: RegionServiceMock,
|
||||
totalsService: TotalsServiceMock,
|
||||
},
|
||||
{
|
||||
api_key: "test",
|
||||
@@ -149,7 +147,6 @@ describe("PaypalProviderService", () => {
|
||||
const paypalProviderService = new PayPalProviderService(
|
||||
{
|
||||
regionService: RegionServiceMock,
|
||||
totalsService: TotalsServiceMock,
|
||||
},
|
||||
{
|
||||
api_key: "test",
|
||||
@@ -196,7 +193,6 @@ describe("PaypalProviderService", () => {
|
||||
const paypalProviderService = new PayPalProviderService(
|
||||
{
|
||||
regionService: RegionServiceMock,
|
||||
totalsService: TotalsServiceMock,
|
||||
},
|
||||
{
|
||||
api_key: "test",
|
||||
@@ -275,7 +271,6 @@ describe("PaypalProviderService", () => {
|
||||
const paypalProviderService = new PayPalProviderService(
|
||||
{
|
||||
regionService: RegionServiceMock,
|
||||
totalsService: TotalsServiceMock,
|
||||
},
|
||||
{
|
||||
api_key: "test",
|
||||
@@ -349,9 +344,13 @@ describe("PaypalProviderService", () => {
|
||||
data: { id: "test-voided" },
|
||||
})
|
||||
|
||||
expect(PayPalMock.payments.AuthorizationsVoidRequest).not.toHaveBeenCalled()
|
||||
expect(
|
||||
PayPalMock.payments.AuthorizationsVoidRequest
|
||||
).not.toHaveBeenCalled()
|
||||
expect(PayPalMock.payments.CapturesRefundRequest).not.toHaveBeenCalled()
|
||||
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test-voided")
|
||||
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith(
|
||||
"test-voided"
|
||||
)
|
||||
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(result.id).toEqual("test-voided")
|
||||
@@ -366,9 +365,13 @@ describe("PaypalProviderService", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(PayPalMock.payments.AuthorizationsVoidRequest).not.toHaveBeenCalled()
|
||||
expect(
|
||||
PayPalMock.payments.AuthorizationsVoidRequest
|
||||
).not.toHaveBeenCalled()
|
||||
expect(PayPalMock.payments.CapturesRefundRequest).not.toHaveBeenCalled()
|
||||
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test-refund")
|
||||
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith(
|
||||
"test-refund"
|
||||
)
|
||||
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(result.id).toEqual("test-refund")
|
||||
|
||||
@@ -12,7 +12,7 @@ function roundToTwo(num, currency) {
|
||||
class PayPalProviderService extends PaymentService {
|
||||
static identifier = "paypal"
|
||||
|
||||
constructor({ totalsService, regionService }, options) {
|
||||
constructor({ regionService }, options) {
|
||||
super()
|
||||
|
||||
/**
|
||||
@@ -44,9 +44,6 @@ class PayPalProviderService extends PaymentService {
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,7 +91,7 @@ class PayPalProviderService extends PaymentService {
|
||||
const { region_id } = cart
|
||||
const { currency_code } = await this.regionService_.retrieve(region_id)
|
||||
|
||||
const amount = await this.totalsService_.getTotal(cart)
|
||||
const amount = cart.total
|
||||
|
||||
const request = new PayPal.orders.OrdersCreateRequest()
|
||||
request.requestBody({
|
||||
@@ -289,7 +286,8 @@ class PayPalProviderService extends PaymentService {
|
||||
async cancelPayment(payment) {
|
||||
const order = await this.retrievePayment(payment.data)
|
||||
const isAlreadyCanceled = order.status === "VOIDED"
|
||||
const isCanceledAndFullyRefund = order.status === "COMPLETED" && !!order.invoice_id
|
||||
const isCanceledAndFullyRefund =
|
||||
order.status === "COMPLETED" && !!order.invoice_id
|
||||
if (isAlreadyCanceled || isCanceledAndFullyRefund) {
|
||||
return order
|
||||
}
|
||||
|
||||
@@ -135,9 +135,7 @@ class StripeProviderService extends AbstractPaymentService {
|
||||
.withTransaction(this.manager_)
|
||||
.retrieve(region_id)
|
||||
|
||||
const amount = await this.totalsService_
|
||||
.withTransaction(this.manager_)
|
||||
.getTotal(cart)
|
||||
const amount = cart.total
|
||||
|
||||
const intentRequest = {
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user