feat(core-flows,types,cart): add credit lines to cart (#11419)
* feat(core-flows,types,cart): add credit lines to cart * chore: fix specs * chore: credit lines hook * chore: update types * chore: added credit line totals * chore: add totals fields to query config * chore: add complete cart hook * chore: add credit lines creation to order * chore: pr ready for review * chore: fix tests * Apply suggestions from code review * chore: fix types * chore: adjust summary calculations with new totals
This commit is contained in:
1 parent
be566ca6fb
commit
fb2e86484a
27 files changed
+802
-58
No files matched your search
@@ -95,6 +95,7 @@ export const completeCartFields = [
|
||||
"shipping_address.*",
|
||||
"billing_address.*",
|
||||
"region.*",
|
||||
"credit_lines.*",
|
||||
"payment_collection.*",
|
||||
"payment_collection.payment_sessions.*",
|
||||
"items.variant.id",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CartCreditLineDTO,
|
||||
CartWorkflowDTO,
|
||||
UsageComputedActions,
|
||||
} from "@medusajs/framework/types"
|
||||
@@ -210,6 +211,18 @@ export const completeCartWorkflow = createWorkflow(
|
||||
.map((adjustment) => adjustment.code)
|
||||
.filter(Boolean)
|
||||
|
||||
const creditLines = (cart.credit_lines ?? []).map(
|
||||
(creditLine: CartCreditLineDTO) => {
|
||||
return {
|
||||
amount: creditLine.amount,
|
||||
raw_amount: creditLine.raw_amount,
|
||||
reference: creditLine.reference,
|
||||
reference_id: creditLine.reference_id,
|
||||
metadata: creditLine.metadata,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
region_id: cart.region?.id,
|
||||
customer_id: cart.customer?.id,
|
||||
@@ -222,6 +235,7 @@ export const completeCartWorkflow = createWorkflow(
|
||||
no_notification: false,
|
||||
items: allItems,
|
||||
shipping_methods: shippingMethods,
|
||||
credit_lines: creditLines,
|
||||
metadata: cart.metadata,
|
||||
promo_codes: promoCodes,
|
||||
transactions,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
CartCreditLineDTO,
|
||||
CreateCartCreditLineDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { createEntitiesStep } from "../../common/steps/create-entities"
|
||||
|
||||
export const createCartCreditLinesWorkflowId = "create-cart-credit-lines"
|
||||
export const createCartCreditLinesWorkflow = createWorkflow(
|
||||
createCartCreditLinesWorkflowId,
|
||||
(
|
||||
input: WorkflowData<CreateCartCreditLineDTO[]>
|
||||
): WorkflowResponse<CartCreditLineDTO[]> => {
|
||||
const creditLines = createEntitiesStep({
|
||||
moduleRegistrationName: Modules.CART,
|
||||
invokeMethod: "createCreditLines",
|
||||
compensateMethod: "deleteCreditLines",
|
||||
data: input,
|
||||
})
|
||||
|
||||
return new WorkflowResponse(creditLines)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import {
|
||||
WorkflowData,
|
||||
WorkflowResponse,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { deleteEntitiesStep } from "../../common/steps/delete-entities"
|
||||
|
||||
export const deleteCartCreditLinesWorkflowId = "delete-cart-credit-lines"
|
||||
export const deleteCartCreditLinesWorkflow = createWorkflow(
|
||||
deleteCartCreditLinesWorkflowId,
|
||||
(input: WorkflowData<{ id: string[] }>) => {
|
||||
deleteEntitiesStep({
|
||||
moduleRegistrationName: Modules.CART,
|
||||
invokeMethod: "softDeleteCreditLines",
|
||||
compensateMethod: "restoreCreditLines",
|
||||
data: input.id,
|
||||
})
|
||||
|
||||
return new WorkflowResponse(void 0)
|
||||
}
|
||||
)
|
||||
@@ -2,13 +2,15 @@ export * from "./add-shipping-method-to-cart"
|
||||
export * from "./add-to-cart"
|
||||
export * from "./complete-cart"
|
||||
export * from "./confirm-variant-inventory"
|
||||
export * from "./create-cart-credit-lines"
|
||||
export * from "./create-carts"
|
||||
export * from "./create-payment-collection-for-cart"
|
||||
export * from "./delete-cart-credit-lines"
|
||||
export * from "./list-shipping-options-for-cart"
|
||||
export * from "./list-shipping-options-for-cart-with-pricing"
|
||||
export * from "./refresh-payment-collection"
|
||||
export * from "./refresh-cart-items"
|
||||
export * from "./refresh-cart-shipping-methods"
|
||||
export * from "./refresh-payment-collection"
|
||||
export * from "./transfer-cart-customer"
|
||||
export * from "./update-cart"
|
||||
export * from "./update-cart-promotions"
|
||||
|
||||
@@ -780,6 +780,13 @@ export interface CartDTO {
|
||||
*/
|
||||
items?: CartLineItemDTO[]
|
||||
|
||||
/**
|
||||
* The credit lines for a cart
|
||||
*
|
||||
* @expandable
|
||||
*/
|
||||
credit_lines?: CartCreditLineDTO[]
|
||||
|
||||
/**
|
||||
* The associated shipping methods
|
||||
*
|
||||
@@ -1275,3 +1282,80 @@ export interface FilterableShippingMethodTaxLineProps
|
||||
*/
|
||||
shipping_method?: FilterableShippingMethodProps
|
||||
}
|
||||
|
||||
/**
|
||||
* The cart credit line details.
|
||||
*/
|
||||
export interface CartCreditLineDTO {
|
||||
/**
|
||||
* The ID of the cart credit line.
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* The ID of the cart that the credit line belongs to.
|
||||
*/
|
||||
cart_id: string
|
||||
|
||||
/**
|
||||
* The amount of the credit line.
|
||||
*/
|
||||
amount: BigNumberValue
|
||||
|
||||
/**
|
||||
* The raw amount of the credit line.
|
||||
*/
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
/**
|
||||
* The reference model name that the credit line is generated from
|
||||
*/
|
||||
reference: string | null
|
||||
|
||||
/**
|
||||
* The reference model id that the credit line is generated from
|
||||
*/
|
||||
reference_id: string | null
|
||||
|
||||
/**
|
||||
* The metadata of the cart detail
|
||||
*/
|
||||
metadata: Record<string, unknown> | null
|
||||
|
||||
/**
|
||||
* The date when the cart credit line was created.
|
||||
*/
|
||||
created_at: Date
|
||||
|
||||
/**
|
||||
* The date when the cart credit line was last updated.
|
||||
*/
|
||||
updated_at: Date
|
||||
}
|
||||
|
||||
export interface CreateCartCreditLineDTO {
|
||||
/**
|
||||
* The ID of the cart that the credit line belongs to.
|
||||
*/
|
||||
cart_id: string
|
||||
|
||||
/**
|
||||
* The amount of the credit line.
|
||||
*/
|
||||
amount: number
|
||||
|
||||
/**
|
||||
* The reference model name that the credit line is generated from
|
||||
*/
|
||||
reference: string | null
|
||||
|
||||
/**
|
||||
* The reference model id that the credit line is generated from
|
||||
*/
|
||||
reference_id: string | null
|
||||
|
||||
/**
|
||||
* The metadata of the cart detail
|
||||
*/
|
||||
metadata: Record<string, unknown> | null
|
||||
}
|
||||
@@ -3019,6 +3019,11 @@ export interface OrderCreditLineDTO {
|
||||
*/
|
||||
order: OrderDTO
|
||||
|
||||
/**
|
||||
* The amount of the credit line.
|
||||
*/
|
||||
amount: BigNumberValue
|
||||
|
||||
/**
|
||||
* The reference model name that the credit line is generated from
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { BigNumberInput } from "../totals"
|
||||
import { BigNumberInput, BigNumberValue } from "../totals"
|
||||
import {
|
||||
ChangeActionType,
|
||||
OrderClaimDTO,
|
||||
OrderCreditLineDTO,
|
||||
OrderExchangeDTO,
|
||||
OrderItemDTO,
|
||||
OrderLineItemDTO,
|
||||
@@ -173,6 +174,11 @@ export interface CreateOrderDTO {
|
||||
*/
|
||||
shipping_methods?: Omit<CreateOrderShippingMethodDTO, "order_id">[]
|
||||
|
||||
/**
|
||||
* The credit lines of the order.
|
||||
*/
|
||||
credit_lines?: OrderCreditLineDTO[]
|
||||
|
||||
/**
|
||||
* The transactions of the order.
|
||||
*/
|
||||
@@ -2263,3 +2269,33 @@ export interface UpdateOrderReturnReasonWithSelectorDTO {
|
||||
*/
|
||||
data: Partial<UpdateOrderReturnReasonDTO>
|
||||
}
|
||||
|
||||
/**
|
||||
* The order credit line details.
|
||||
*/
|
||||
export interface CreateOrderCreditLineDTO {
|
||||
/**
|
||||
* The ID of the order that the credit line belongs to.
|
||||
*/
|
||||
order_id: string
|
||||
|
||||
/**
|
||||
* The amount of the credit line.
|
||||
*/
|
||||
amount: BigNumberValue
|
||||
|
||||
/**
|
||||
* The reference model name that the credit line is generated from
|
||||
*/
|
||||
reference: string | null
|
||||
|
||||
/**
|
||||
* The reference model id that the credit line is generated from
|
||||
*/
|
||||
reference_id: string | null
|
||||
|
||||
/**
|
||||
* The metadata of the order detail
|
||||
*/
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
OrderChangeReturn,
|
||||
OrderClaimDTO,
|
||||
OrderClaimItemDTO,
|
||||
OrderCreditLineDTO,
|
||||
OrderDTO,
|
||||
OrderExchangeDTO,
|
||||
OrderExchangeItemDTO,
|
||||
@@ -52,6 +53,7 @@ import {
|
||||
CreateOrderChangeDTO,
|
||||
CreateOrderClaimDTO,
|
||||
CreateOrderClaimItemDTO,
|
||||
CreateOrderCreditLineDTO,
|
||||
CreateOrderDTO,
|
||||
CreateOrderExchangeDTO,
|
||||
CreateOrderExchangeItemDTO,
|
||||
@@ -4925,4 +4927,9 @@ export interface IOrderModuleService extends IModuleService {
|
||||
data: CancelOrderExchangeDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<OrderExchangeDTO>
|
||||
|
||||
createOrderCreditLines(
|
||||
data: CreateOrderCreditLineDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<OrderCreditLineDTO[]>
|
||||
}
|
||||
@@ -81,6 +81,9 @@ describe("Total calculation", function () {
|
||||
original_item_subtotal: 65,
|
||||
original_item_total: 73.5,
|
||||
original_item_tax_total: 8.5,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -149,6 +152,9 @@ describe("Total calculation", function () {
|
||||
original_item_total: 110,
|
||||
original_item_subtotal: 100,
|
||||
original_item_tax_total: 10,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -353,6 +359,9 @@ describe("Total calculation", function () {
|
||||
original_shipping_tax_total: 9.9,
|
||||
original_shipping_subtotal: 99,
|
||||
original_shipping_total: 108.9,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -438,6 +447,9 @@ describe("Total calculation", function () {
|
||||
original_item_total: 100,
|
||||
original_item_subtotal: 90.9090909090909,
|
||||
original_item_tax_total: 9.090909090909092,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
|
||||
expect(serializedWithout).toEqual({
|
||||
@@ -477,6 +489,9 @@ describe("Total calculation", function () {
|
||||
original_item_total: 110,
|
||||
original_item_subtotal: 100,
|
||||
original_item_tax_total: 10,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
|
||||
expect(serializedMixed).toEqual({
|
||||
@@ -536,6 +551,9 @@ describe("Total calculation", function () {
|
||||
original_item_total: 210,
|
||||
original_item_subtotal: 190.9090909090909,
|
||||
original_item_tax_total: 19.09090909090909,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -652,11 +670,21 @@ describe("Total calculation", function () {
|
||||
original_shipping_tax_total: 2.5,
|
||||
original_shipping_subtotal: 25,
|
||||
original_shipping_total: 27.5,
|
||||
credit_lines_subtotal: 0,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it("should calculate order with items + taxes + adjustments", function () {
|
||||
it("should calculate order with items + taxes + adjustments + credit lines", function () {
|
||||
const cart = {
|
||||
credit_lines: [
|
||||
{
|
||||
amount: 40,
|
||||
reference: "order",
|
||||
reference_id: "order_123",
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
@@ -686,6 +714,15 @@ describe("Total calculation", function () {
|
||||
const serialized = JSON.parse(JSON.stringify(decorateCartTotals(cart)))
|
||||
|
||||
expect(serialized).toEqual({
|
||||
credit_lines: [
|
||||
{
|
||||
amount: 40,
|
||||
reference: "order",
|
||||
reference_id: "order_123",
|
||||
subtotal: 40,
|
||||
total: 40,
|
||||
},
|
||||
],
|
||||
items: [
|
||||
{
|
||||
unit_price: 50,
|
||||
@@ -730,8 +767,8 @@ describe("Total calculation", function () {
|
||||
write_off_total: 44,
|
||||
},
|
||||
],
|
||||
total: 88,
|
||||
subtotal: 100,
|
||||
total: 48,
|
||||
subtotal: 60,
|
||||
tax_total: 8,
|
||||
discount_total: 22,
|
||||
discount_subtotal: 20,
|
||||
@@ -750,6 +787,9 @@ describe("Total calculation", function () {
|
||||
return_received_total: 44,
|
||||
return_dismissed_total: 44,
|
||||
write_off_total: 44,
|
||||
credit_lines_subtotal: 40,
|
||||
credit_lines_tax_total: 0,
|
||||
credit_lines_total: 40,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BigNumberInput, CartLikeWithTotals } from "@medusajs/types"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { calculateCreditLinesTotal } from "../credit-lines"
|
||||
import { GetItemTotalInput, getLineItemsTotals } from "../line-item"
|
||||
import { MathBN } from "../math"
|
||||
import {
|
||||
@@ -13,6 +14,9 @@ interface TotalsConfig {
|
||||
}
|
||||
|
||||
export interface DecorateCartLikeInputDTO {
|
||||
credit_lines?: {
|
||||
amount: BigNumberInput
|
||||
}[]
|
||||
items?: {
|
||||
id?: string
|
||||
unit_price: BigNumberInput
|
||||
@@ -52,6 +56,7 @@ export function decorateCartTotals(
|
||||
"detail.written_off_quantity": "write_off_total",
|
||||
}
|
||||
|
||||
const creditLines = cartLike.credit_lines ?? []
|
||||
const items = (cartLike.items ?? []) as unknown as GetItemTotalInput[]
|
||||
const shippingMethods = (cartLike.shipping_methods ??
|
||||
[]) as unknown as GetShippingMethodTotalInput[]
|
||||
@@ -69,6 +74,10 @@ export function decorateCartTotals(
|
||||
|
||||
const extraTotals = {}
|
||||
|
||||
// TODO: Remove this once we have a way to calculate the tax rate for credit lines
|
||||
const creditLinesSumTax = MathBN.convert(0)
|
||||
const creditLinesSumTaxRate = MathBN.div(creditLinesSumTax, 100)
|
||||
|
||||
let subtotal = MathBN.convert(0)
|
||||
|
||||
let discountTotal = MathBN.convert(0)
|
||||
@@ -192,6 +201,15 @@ export function decorateCartTotals(
|
||||
return shippingMethodTotals
|
||||
})
|
||||
|
||||
const { creditLinesTotal, creditLinesSubtotal, creditLinesTaxTotal } =
|
||||
calculateCreditLinesTotal({
|
||||
creditLines,
|
||||
includesTax: false,
|
||||
taxRate: creditLinesSumTaxRate,
|
||||
})
|
||||
|
||||
subtotal = MathBN.sub(subtotal, creditLinesSubtotal)
|
||||
|
||||
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
|
||||
|
||||
const originalTaxTotal = MathBN.add(
|
||||
@@ -205,6 +223,7 @@ export function decorateCartTotals(
|
||||
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
|
||||
const tempTotal = MathBN.add(subtotal, taxTotal)
|
||||
const total = MathBN.sub(tempTotal, discountSubtotal)
|
||||
|
||||
const cart = cartLike as any
|
||||
|
||||
cart.total = new BigNumber(total)
|
||||
@@ -215,6 +234,10 @@ export function decorateCartTotals(
|
||||
cart.discount_subtotal = new BigNumber(discountSubtotal)
|
||||
cart.discount_tax_total = new BigNumber(discountTaxTotal)
|
||||
|
||||
cart.credit_lines_total = new BigNumber(creditLinesTotal)
|
||||
cart.credit_lines_subtotal = new BigNumber(creditLinesSubtotal)
|
||||
cart.credit_lines_tax_total = new BigNumber(creditLinesTaxTotal)
|
||||
|
||||
// cart.gift_card_total = giftCardTotal.total || 0
|
||||
// cart.gift_card_tax_total = giftCardTotal.tax_total || 0
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { BigNumberInput } from "@medusajs/types"
|
||||
import { isDefined } from "../../common"
|
||||
import { BigNumber } from "../big-number"
|
||||
import { MathBN } from "../math"
|
||||
|
||||
export function calculateCreditLinesTotal({
|
||||
creditLines,
|
||||
includesTax,
|
||||
taxRate,
|
||||
}: {
|
||||
creditLines: { amount: BigNumberInput }[]
|
||||
includesTax?: boolean
|
||||
taxRate?: BigNumberInput
|
||||
}) {
|
||||
// the sum of all creditLine amounts excluding tax
|
||||
let creditLinesSubtotal = MathBN.convert(0)
|
||||
// the sum of all creditLine amounts including tax
|
||||
let creditLinesTotal = MathBN.convert(0)
|
||||
// the sum of all taxes on subtotals
|
||||
let creditLinesTaxTotal = MathBN.convert(0)
|
||||
|
||||
for (const cl of creditLines) {
|
||||
if (!isDefined(cl.amount)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const creditLineAmount = MathBN.convert(cl.amount)
|
||||
creditLinesSubtotal = MathBN.add(creditLinesSubtotal, creditLineAmount)
|
||||
|
||||
if (isDefined(taxRate)) {
|
||||
const creditLineSubtotal = includesTax
|
||||
? MathBN.div(creditLineAmount, MathBN.add(1, taxRate))
|
||||
: creditLineAmount
|
||||
|
||||
const creditLineTaxTotal = MathBN.mult(creditLineSubtotal, taxRate)
|
||||
const creditLineTotal = MathBN.add(creditLineSubtotal, creditLineTaxTotal)
|
||||
|
||||
cl["subtotal"] = new BigNumber(creditLineSubtotal)
|
||||
cl["total"] = new BigNumber(creditLineTotal)
|
||||
|
||||
creditLinesTotal = MathBN.add(creditLinesTotal, creditLineTotal)
|
||||
creditLinesTaxTotal = MathBN.add(creditLinesTaxTotal, creditLineTaxTotal)
|
||||
} else {
|
||||
cl["subtotal"] = new BigNumber(creditLineAmount)
|
||||
creditLinesTotal = MathBN.add(creditLinesTotal, creditLineAmount)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
creditLinesTotal,
|
||||
creditLinesSubtotal,
|
||||
creditLinesTaxTotal,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user