feat(core-flows,types,utils): make payment optional when cart balance is 0 (#11872)
what: - make payment optional when cart balance is 0
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core-flows,types,utils): make payment optional when cart balance is 0
|
||||||
@@ -1085,7 +1085,9 @@ medusaIntegrationTestRunner({
|
|||||||
{ option_id: shippingOption.id },
|
{ option_id: shippingOption.id },
|
||||||
storeHeaders
|
storeHeaders
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should successfully complete cart", async () => {
|
||||||
const paymentCollection = (
|
const paymentCollection = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/store/payment-collections`,
|
`/store/payment-collections`,
|
||||||
@@ -1099,9 +1101,7 @@ medusaIntegrationTestRunner({
|
|||||||
{ provider_id: "pp_system_default" },
|
{ provider_id: "pp_system_default" },
|
||||||
storeHeaders
|
storeHeaders
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
it("should successfully complete cart", async () => {
|
|
||||||
createCartCreditLinesWorkflow.run({
|
createCartCreditLinesWorkflow.run({
|
||||||
input: [
|
input: [
|
||||||
{
|
{
|
||||||
@@ -1144,6 +1144,53 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should successfully complete cart with credit lines alone", async () => {
|
||||||
|
const oldCart = (
|
||||||
|
await api.get(`/store/carts/${cart.id}`, storeHeaders)
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
createCartCreditLinesWorkflow.run({
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
cart_id: oldCart.id,
|
||||||
|
amount: oldCart.total,
|
||||||
|
currency_code: "usd",
|
||||||
|
reference: "test",
|
||||||
|
reference_id: "test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
container: appContainer,
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts/${cart.id}/complete`,
|
||||||
|
{},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.order).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
currency_code: "usd",
|
||||||
|
credit_line_total: 2395,
|
||||||
|
discount_total: 100,
|
||||||
|
credit_lines: [
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 2395,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
items: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
unit_price: 1500,
|
||||||
|
compare_at_unit_price: null,
|
||||||
|
quantity: 1,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("should successfully complete cart without shipping for digital products", async () => {
|
it("should successfully complete cart without shipping for digital products", async () => {
|
||||||
/**
|
/**
|
||||||
* Product has a shipping profile so cart item should not require shipping
|
* Product has a shipping profile so cart item should not require shipping
|
||||||
@@ -1181,7 +1228,7 @@ medusaIntegrationTestRunner({
|
|||||||
)
|
)
|
||||||
).data.product
|
).data.product
|
||||||
|
|
||||||
let cart = (
|
cart = (
|
||||||
await api.post(
|
await api.post(
|
||||||
`/store/carts`,
|
`/store/carts`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1148,7 +1148,7 @@ medusaIntegrationTestRunner({
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: order.id,
|
id: order.id,
|
||||||
total: 0,
|
total: 0,
|
||||||
subtotal: -6,
|
subtotal: 100,
|
||||||
summary: expect.objectContaining({
|
summary: expect.objectContaining({
|
||||||
current_order_total: 0,
|
current_order_total: 0,
|
||||||
accounting_total: 0,
|
accounting_total: 0,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CartWorkflowDTO } from "@medusajs/framework/types"
|
import { CartWorkflowDTO } from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
isPresent,
|
isPresent,
|
||||||
|
MathBN,
|
||||||
MedusaError,
|
MedusaError,
|
||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
@@ -20,13 +21,13 @@ export const validateCartPaymentsStepId = "validate-cart-payments"
|
|||||||
/**
|
/**
|
||||||
* This step validates a cart's payment sessions. Their status must
|
* This step validates a cart's payment sessions. Their status must
|
||||||
* be `pending` or `requires_more`. If not valid, the step throws an error.
|
* be `pending` or `requires_more`. If not valid, the step throws an error.
|
||||||
*
|
*
|
||||||
* :::tip
|
* :::tip
|
||||||
*
|
*
|
||||||
* You can use the {@link retrieveCartStep} to retrieve a cart's details.
|
* You can use the {@link retrieveCartStep} to retrieve a cart's details.
|
||||||
*
|
*
|
||||||
* :::
|
* :::
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const data = validateCartPaymentsStep({
|
* const data = validateCartPaymentsStep({
|
||||||
* // retrieve the details of the cart from another workflow
|
* // retrieve the details of the cart from another workflow
|
||||||
@@ -38,9 +39,16 @@ export const validateCartPaymentsStep = createStep(
|
|||||||
validateCartPaymentsStepId,
|
validateCartPaymentsStepId,
|
||||||
async (data: ValidateCartPaymentsStepInput) => {
|
async (data: ValidateCartPaymentsStepInput) => {
|
||||||
const {
|
const {
|
||||||
cart: { payment_collection: paymentCollection },
|
cart: { payment_collection: paymentCollection, total, credit_line_total },
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
|
const canSkipPayment =
|
||||||
|
MathBN.convert(credit_line_total).gte(0) && MathBN.convert(total).lte(0)
|
||||||
|
|
||||||
|
if (canSkipPayment) {
|
||||||
|
return new StepResponse([])
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPresent(paymentCollection)) {
|
if (!isPresent(paymentCollection)) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
UsageComputedActions,
|
UsageComputedActions,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
|
isDefined,
|
||||||
Modules,
|
Modules,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
OrderWorkflowEvents,
|
OrderWorkflowEvents,
|
||||||
@@ -167,14 +168,16 @@ export const completeCartWorkflow = createWorkflow(
|
|||||||
|
|
||||||
const cartToOrder = transform({ cart, payment }, ({ cart, payment }) => {
|
const cartToOrder = transform({ cart, payment }, ({ cart, payment }) => {
|
||||||
const transactions =
|
const transactions =
|
||||||
payment?.captures?.map((capture) => {
|
(payment &&
|
||||||
return {
|
payment?.captures?.map((capture) => {
|
||||||
amount: capture.raw_amount ?? capture.amount,
|
return {
|
||||||
currency_code: payment.currency_code,
|
amount: capture.raw_amount ?? capture.amount,
|
||||||
reference: "capture",
|
currency_code: payment.currency_code,
|
||||||
reference_id: capture.id,
|
reference: "capture",
|
||||||
}
|
reference_id: capture.id,
|
||||||
}) ?? []
|
}
|
||||||
|
})) ??
|
||||||
|
[]
|
||||||
|
|
||||||
const allItems = (cart.items ?? []).map((item) => {
|
const allItems = (cart.items ?? []).map((item) => {
|
||||||
const input: PrepareLineItemDataInput = {
|
const input: PrepareLineItemDataInput = {
|
||||||
@@ -280,19 +283,31 @@ export const completeCartWorkflow = createWorkflow(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
parallelize(
|
const linksToCreate = transform(
|
||||||
createRemoteLinkStep([
|
{ cart, createdOrder },
|
||||||
{
|
({ cart, createdOrder }) => {
|
||||||
[Modules.ORDER]: { order_id: createdOrder.id },
|
const links: Record<string, any>[] = [
|
||||||
[Modules.CART]: { cart_id: cart.id },
|
{
|
||||||
},
|
[Modules.ORDER]: { order_id: createdOrder.id },
|
||||||
{
|
[Modules.CART]: { cart_id: cart.id },
|
||||||
[Modules.ORDER]: { order_id: createdOrder.id },
|
|
||||||
[Modules.PAYMENT]: {
|
|
||||||
payment_collection_id: cart.payment_collection.id,
|
|
||||||
},
|
},
|
||||||
},
|
]
|
||||||
]),
|
|
||||||
|
if (isDefined(cart.payment_collection?.id)) {
|
||||||
|
links.push({
|
||||||
|
[Modules.ORDER]: { order_id: createdOrder.id },
|
||||||
|
[Modules.PAYMENT]: {
|
||||||
|
payment_collection_id: cart.payment_collection.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
parallelize(
|
||||||
|
createRemoteLinkStep(linksToCreate),
|
||||||
updateCartsStep([updateCompletedAt]),
|
updateCartsStep([updateCompletedAt]),
|
||||||
reserveInventoryStep(formatedInventoryItems),
|
reserveInventoryStep(formatedInventoryItems),
|
||||||
emitEventStep({
|
emitEventStep({
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ export const authorizePaymentSessionStep = createStep(
|
|||||||
Modules.PAYMENT
|
Modules.PAYMENT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!input.id) {
|
||||||
|
return new StepResponse(null)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
payment = await paymentModule.authorizePaymentSession(
|
payment = await paymentModule.authorizePaymentSession(
|
||||||
input.id,
|
input.id,
|
||||||
|
|||||||
@@ -1033,6 +1033,16 @@ export interface CartDTO {
|
|||||||
* The raw original shipping tax total of the cart.
|
* The raw original shipping tax total of the cart.
|
||||||
*/
|
*/
|
||||||
raw_original_shipping_tax_total: BigNumberRawValue
|
raw_original_shipping_tax_total: BigNumberRawValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw credit lines total of the cart.
|
||||||
|
*/
|
||||||
|
raw_credit_line_total: BigNumberRawValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The credit lines total of the cart.
|
||||||
|
*/
|
||||||
|
credit_line_total: BigNumberValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -768,7 +768,7 @@ describe("Total calculation", function () {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
total: 48,
|
total: 48,
|
||||||
subtotal: 60,
|
subtotal: 100,
|
||||||
tax_total: 8,
|
tax_total: 8,
|
||||||
discount_total: 22,
|
discount_total: 22,
|
||||||
discount_subtotal: 20,
|
discount_subtotal: 20,
|
||||||
|
|||||||
@@ -208,8 +208,6 @@ export function decorateCartTotals(
|
|||||||
taxRate: creditLinesSumTaxRate,
|
taxRate: creditLinesSumTaxRate,
|
||||||
})
|
})
|
||||||
|
|
||||||
subtotal = MathBN.sub(subtotal, creditLinesSubtotal)
|
|
||||||
|
|
||||||
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
|
const taxTotal = MathBN.add(itemsTaxTotal, shippingTaxTotal)
|
||||||
|
|
||||||
const originalTaxTotal = MathBN.add(
|
const originalTaxTotal = MathBN.add(
|
||||||
@@ -222,7 +220,7 @@ export function decorateCartTotals(
|
|||||||
|
|
||||||
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
|
// TODO: subtract (cart.gift_card_total + cart.gift_card_tax_total)
|
||||||
const tempTotal = MathBN.add(subtotal, taxTotal)
|
const tempTotal = MathBN.add(subtotal, taxTotal)
|
||||||
const total = MathBN.sub(tempTotal, discountSubtotal)
|
const total = MathBN.sub(tempTotal, discountSubtotal, creditLinesTotal)
|
||||||
|
|
||||||
const cart = cartLike as any
|
const cart = cartLike as any
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user