feat(core-flows,typers,utils,medusa): add payment auth step to complete cart workflow - [complete cart part 3] (#7248)
* chore: authorize payment sessions for cart * chore: add spec for cart returns * fix: Correctly select fields for cart * chore: fix specs + address comments --------- Co-authored-by: Stevche Radevski <sradevski@live.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
This commit is contained in:
14 files changed
+377
-41
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/types": patch
|
||||||
|
"@medusajs/utils": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core-flows,typers,utils,medusa): add payment auth step to complete cart workflow
|
||||||
@@ -4,10 +4,12 @@ import {
|
|||||||
Modules,
|
Modules,
|
||||||
RemoteLink,
|
RemoteLink,
|
||||||
} from "@medusajs/modules-sdk"
|
} from "@medusajs/modules-sdk"
|
||||||
|
import PaymentModuleService from "@medusajs/payment/dist/services/payment-module"
|
||||||
import {
|
import {
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
ICustomerModuleService,
|
ICustomerModuleService,
|
||||||
IFulfillmentModuleService,
|
IFulfillmentModuleService,
|
||||||
|
IPaymentModuleService,
|
||||||
IPricingModuleService,
|
IPricingModuleService,
|
||||||
IProductModuleService,
|
IProductModuleService,
|
||||||
IPromotionModuleService,
|
IPromotionModuleService,
|
||||||
@@ -17,6 +19,7 @@ import {
|
|||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
|
MedusaError,
|
||||||
PromotionRuleOperator,
|
PromotionRuleOperator,
|
||||||
PromotionType,
|
PromotionType,
|
||||||
RuleOperator,
|
RuleOperator,
|
||||||
@@ -48,6 +51,7 @@ medusaIntegrationTestRunner({
|
|||||||
let fulfillmentModule: IFulfillmentModuleService
|
let fulfillmentModule: IFulfillmentModuleService
|
||||||
let remoteLinkService
|
let remoteLinkService
|
||||||
let regionService: IRegionModuleService
|
let regionService: IRegionModuleService
|
||||||
|
let paymentService: IPaymentModuleService
|
||||||
|
|
||||||
let defaultRegion
|
let defaultRegion
|
||||||
let region
|
let region
|
||||||
@@ -64,6 +68,7 @@ medusaIntegrationTestRunner({
|
|||||||
promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION)
|
promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION)
|
||||||
taxModule = appContainer.resolve(ModuleRegistrationName.TAX)
|
taxModule = appContainer.resolve(ModuleRegistrationName.TAX)
|
||||||
regionService = appContainer.resolve(ModuleRegistrationName.REGION)
|
regionService = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||||
|
paymentService = appContainer.resolve(ModuleRegistrationName.PAYMENT)
|
||||||
fulfillmentModule = appContainer.resolve(
|
fulfillmentModule = appContainer.resolve(
|
||||||
ModuleRegistrationName.FULFILLMENT
|
ModuleRegistrationName.FULFILLMENT
|
||||||
)
|
)
|
||||||
@@ -1480,11 +1485,14 @@ medusaIntegrationTestRunner({
|
|||||||
let shippingProfile
|
let shippingProfile
|
||||||
let fulfillmentSet
|
let fulfillmentSet
|
||||||
let shippingOption
|
let shippingOption
|
||||||
|
let paymentCollection
|
||||||
|
let paymentSession
|
||||||
let stockLocation
|
let stockLocation
|
||||||
let inventoryItem
|
let inventoryItem
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await setupTaxStructure(taxModule)
|
await setupTaxStructure(taxModule)
|
||||||
|
|
||||||
region = await regionService.create({
|
region = await regionService.create({
|
||||||
name: "Test region",
|
name: "Test region",
|
||||||
countries: ["US"],
|
countries: ["US"],
|
||||||
@@ -1616,10 +1624,27 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
).data.shipping_option
|
).data.shipping_option
|
||||||
|
|
||||||
|
paymentCollection = await paymentService.createPaymentCollections({
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 1000,
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
paymentSession = await paymentService.createPaymentSession(
|
||||||
|
paymentCollection.id,
|
||||||
|
{
|
||||||
|
provider_id: "pp_system_default",
|
||||||
|
amount: 1000,
|
||||||
|
currency_code: "usd",
|
||||||
|
data: {},
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create an order and create item reservations", async () => {
|
it("should create an order and create item reservations", async () => {
|
||||||
const cartResponse = await api.post(`/store/carts`, {
|
const cart = (
|
||||||
|
await api.post(`/store/carts`, {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
email: "tony@stark-industries.com",
|
email: "tony@stark-industries.com",
|
||||||
shipping_address: {
|
shipping_address: {
|
||||||
@@ -1633,15 +1658,31 @@ medusaIntegrationTestRunner({
|
|||||||
sales_channel_id: salesChannel.id,
|
sales_channel_id: salesChannel.id,
|
||||||
items: [{ quantity: 1, variant_id: product.variants[0].id }],
|
items: [{ quantity: 1, variant_id: product.variants[0].id }],
|
||||||
})
|
})
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
const paymentCollection = (
|
||||||
|
await api.post(`/store/payment-collections`, {
|
||||||
|
cart_id: cart.id,
|
||||||
|
region_id: region.id,
|
||||||
|
currency_code: region.currency_code,
|
||||||
|
amount: cart.total,
|
||||||
|
})
|
||||||
|
).data.payment_collection
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
|
||||||
|
{ provider_id: "pp_system_default" }
|
||||||
|
)
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/store/carts/${cartResponse.data.cart.id}/complete`,
|
`/store/carts/${cart.id}/complete`,
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.order).toEqual(
|
expect(response.data).toEqual({
|
||||||
expect.objectContaining({
|
type: "order",
|
||||||
|
order: expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
total: 106,
|
total: 106,
|
||||||
subtotal: 100,
|
subtotal: 100,
|
||||||
@@ -1686,8 +1727,8 @@ medusaIntegrationTestRunner({
|
|||||||
province: "ny",
|
province: "ny",
|
||||||
postal_code: "94016",
|
postal_code: "94016",
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const reservation = await api.get(`/admin/reservations`, adminHeaders)
|
const reservation = await api.get(`/admin/reservations`, adminHeaders)
|
||||||
const reservationItem = reservation.data.reservations[0]
|
const reservationItem = reservation.data.reservations[0]
|
||||||
@@ -1697,11 +1738,124 @@ medusaIntegrationTestRunner({
|
|||||||
id: expect.stringContaining("resitem_"),
|
id: expect.stringContaining("resitem_"),
|
||||||
location_id: stockLocation.id,
|
location_id: stockLocation.id,
|
||||||
inventory_item_id: inventoryItem.id,
|
inventory_item_id: inventoryItem.id,
|
||||||
quantity: cartResponse.data.cart.items[0].quantity,
|
quantity: cart.items[0].quantity,
|
||||||
line_item_id: cartResponse.data.cart.items[0].id,
|
line_item_id: cart.items[0].id,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should throw an error when payment collection isn't created", async () => {
|
||||||
|
const cart = (
|
||||||
|
await api.post(`/store/carts`, {
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
items: [{ quantity: 1, variant_id: product.variants[0].id }],
|
||||||
|
})
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
const error = await api
|
||||||
|
.post(`/store/carts/${cart.id}/complete`, {})
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.response.status).toEqual(400)
|
||||||
|
expect(error.response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message: "Payment collection has not been initiated for cart",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw an error when payment collection isn't created", async () => {
|
||||||
|
const cart = (
|
||||||
|
await api.post(`/store/carts`, {
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
items: [{ quantity: 1, variant_id: product.variants[0].id }],
|
||||||
|
})
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
await api.post(`/store/payment-collections`, {
|
||||||
|
cart_id: cart.id,
|
||||||
|
region_id: region.id,
|
||||||
|
currency_code: region.currency_code,
|
||||||
|
amount: cart.total,
|
||||||
|
})
|
||||||
|
|
||||||
|
const error = await api
|
||||||
|
.post(`/store/carts/${cart.id}/complete`, {})
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.response.status).toEqual(400)
|
||||||
|
expect(error.response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message: "Payment sessions are required to complete cart",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return cart when payment authorization fails", async () => {
|
||||||
|
const authorizePaymentSessionSpy = jest.spyOn(
|
||||||
|
PaymentModuleService.prototype,
|
||||||
|
"authorizePaymentSession"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock the authorizePaymentSession to throw error
|
||||||
|
authorizePaymentSessionSpy.mockImplementation(
|
||||||
|
(id, context, sharedContext) => {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Throw a random error`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = (
|
||||||
|
await api.post(`/store/carts`, {
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
shipping_address: {
|
||||||
|
address_1: "test address 1",
|
||||||
|
address_2: "test address 2",
|
||||||
|
city: "ny",
|
||||||
|
country_code: "us",
|
||||||
|
province: "ny",
|
||||||
|
postal_code: "94016",
|
||||||
|
},
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
items: [{ quantity: 1, variant_id: product.variants[0].id }],
|
||||||
|
})
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
const paymentCollection = (
|
||||||
|
await api.post(`/store/payment-collections`, {
|
||||||
|
cart_id: cart.id,
|
||||||
|
region_id: region.id,
|
||||||
|
currency_code: region.currency_code,
|
||||||
|
amount: cart.total,
|
||||||
|
})
|
||||||
|
).data.payment_collection
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
|
||||||
|
{ provider_id: "pp_system_default" }
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts/${cart.id}/complete`,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "cart",
|
||||||
|
cart: expect.objectContaining({}),
|
||||||
|
error: {
|
||||||
|
message: "Payment authorization failed",
|
||||||
|
name: "Error",
|
||||||
|
type: "payment_authorization_error",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ export * from "./retrieve-cart-with-links"
|
|||||||
export * from "./set-tax-lines-for-items"
|
export * from "./set-tax-lines-for-items"
|
||||||
export * from "./update-cart-promotions"
|
export * from "./update-cart-promotions"
|
||||||
export * from "./update-carts"
|
export * from "./update-carts"
|
||||||
|
export * from "./validate-cart-payments"
|
||||||
export * from "./validate-cart-shipping-options"
|
export * from "./validate-cart-shipping-options"
|
||||||
export * from "./validate-variant-prices"
|
export * from "./validate-variant-prices"
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { CartWorkflowDTO } from "@medusajs/types"
|
||||||
|
import { isPresent, MedusaError, PaymentSessionStatus } from "@medusajs/utils"
|
||||||
|
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
cart: CartWorkflowDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateCartPaymentsStepId = "validate-cart-payments"
|
||||||
|
export const validateCartPaymentsStep = createStep(
|
||||||
|
validateCartPaymentsStepId,
|
||||||
|
async (data: StepInput) => {
|
||||||
|
const {
|
||||||
|
cart: { payment_collection: paymentCollection },
|
||||||
|
} = data
|
||||||
|
|
||||||
|
if (!isPresent(paymentCollection)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Payment collection has not been initiated for cart`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We check if any of these payment sessions are present in the cart
|
||||||
|
// If not, we throw an error for the consumer to provide a processable payment session
|
||||||
|
const processablePaymentStatuses = [
|
||||||
|
PaymentSessionStatus.PENDING,
|
||||||
|
PaymentSessionStatus.REQUIRES_MORE,
|
||||||
|
]
|
||||||
|
|
||||||
|
const paymentsToProcess = paymentCollection.payment_sessions?.filter((ps) =>
|
||||||
|
processablePaymentStatuses.includes(ps.status)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!paymentsToProcess?.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Payment sessions are required to complete cart`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(paymentsToProcess)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -5,23 +5,12 @@ import {
|
|||||||
transform,
|
transform,
|
||||||
} from "@medusajs/workflows-sdk"
|
} from "@medusajs/workflows-sdk"
|
||||||
import { useRemoteQueryStep } from "../../../common"
|
import { useRemoteQueryStep } from "../../../common"
|
||||||
import { createOrderFromCartStep } from "../steps"
|
import { authorizePaymentSessionStep } from "../../../payment/steps/authorize-payment-session"
|
||||||
|
import { createOrderFromCartStep, validateCartPaymentsStep } from "../steps"
|
||||||
import { reserveInventoryStep } from "../steps/reserve-inventory"
|
import { reserveInventoryStep } from "../steps/reserve-inventory"
|
||||||
import { updateTaxLinesStep } from "../steps/update-tax-lines"
|
|
||||||
import { completeCartFields } from "../utils/fields"
|
import { completeCartFields } from "../utils/fields"
|
||||||
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
|
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
|
||||||
|
|
||||||
/*
|
|
||||||
- [] Create Tax Lines
|
|
||||||
- [] Authorize Payment
|
|
||||||
- fail:
|
|
||||||
- [] Delete Tax lines
|
|
||||||
- [] Reserve Item from inventory (if enabled)
|
|
||||||
- fail:
|
|
||||||
- [] Delete reservations
|
|
||||||
- [] Cancel Payment
|
|
||||||
- [] Create order
|
|
||||||
*/
|
|
||||||
export const completeCartWorkflowId = "complete-cart"
|
export const completeCartWorkflowId = "complete-cart"
|
||||||
export const completeCartWorkflow = createWorkflow(
|
export const completeCartWorkflow = createWorkflow(
|
||||||
completeCartWorkflowId,
|
completeCartWorkflowId,
|
||||||
@@ -33,6 +22,15 @@ export const completeCartWorkflow = createWorkflow(
|
|||||||
list: false,
|
list: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const paymentSessions = validateCartPaymentsStep({ cart })
|
||||||
|
|
||||||
|
authorizePaymentSessionStep({
|
||||||
|
// We choose the first payment session, as there will only be one active payment session
|
||||||
|
// This might change in the future.
|
||||||
|
id: paymentSessions[0].id,
|
||||||
|
context: { cart_id: cart.id },
|
||||||
|
})
|
||||||
|
|
||||||
const { variants, items, sales_channel_id } = transform(
|
const { variants, items, sales_channel_id } = transform(
|
||||||
{ cart },
|
{ cart },
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -44,7 +42,6 @@ export const completeCartWorkflow = createWorkflow(
|
|||||||
variant_id: item.variant_id,
|
variant_id: item.variant_id,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
})
|
})
|
||||||
|
|
||||||
allVariants.push(item.variant)
|
allVariants.push(item.variant)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -64,8 +61,6 @@ export const completeCartWorkflow = createWorkflow(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
updateTaxLinesStep({ cart_or_cart_id: cart, force_tax_calculation: true })
|
|
||||||
|
|
||||||
reserveInventoryStep(formatedInventoryItems)
|
reserveInventoryStep(formatedInventoryItems)
|
||||||
|
|
||||||
const finalCart = useRemoteQueryStep({
|
const finalCart = useRemoteQueryStep({
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IPaymentModuleService, Logger, PaymentDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ContainerRegistrationKeys,
|
||||||
|
MedusaError,
|
||||||
|
PaymentSessionStatus,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
type StepInput = {
|
||||||
|
id: string
|
||||||
|
context: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const authorizePaymentSessionStepId = "authorize-payment-session-step"
|
||||||
|
export const authorizePaymentSessionStep = createStep(
|
||||||
|
authorizePaymentSessionStepId,
|
||||||
|
async (input: StepInput, { container }) => {
|
||||||
|
let payment: PaymentDTO | undefined
|
||||||
|
const logger = container.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
|
||||||
|
const paymentModule = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
payment = await paymentModule.authorizePaymentSession(
|
||||||
|
input.id,
|
||||||
|
input.context || {}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
`Error was thrown trying to authorize payment session - ${input.id} - ${e}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentSession = await paymentModule.retrievePaymentSession(input.id)
|
||||||
|
|
||||||
|
// Throw a special error type when the status is requires_more as it requires a specific further action
|
||||||
|
// from the consumer
|
||||||
|
if (paymentSession.status === PaymentSessionStatus.REQUIRES_MORE) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.PAYMENT_REQUIRES_MORE_ERROR,
|
||||||
|
`More information is required for payment`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any other error other than requires_more shows up, this usually requires the consumer to create a new payment session
|
||||||
|
// This could also be a system error thats caused by invalid setup or a failure in connecting to external providers
|
||||||
|
if (paymentSession.status !== PaymentSessionStatus.AUTHORIZED || !payment) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR,
|
||||||
|
`Payment authorization failed`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(payment)
|
||||||
|
},
|
||||||
|
// If payment or any other part of complete cart fails post payment step, we cancel any payments made
|
||||||
|
async (payment, { container }) => {
|
||||||
|
if (!payment) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = container.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
|
||||||
|
const paymentModule = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the payment session status is requires_more, we don't have to revert the payment.
|
||||||
|
// Return the same status for the cart completion to be re-run.
|
||||||
|
if (
|
||||||
|
payment.payment_session &&
|
||||||
|
payment.payment_session.status === PaymentSessionStatus.REQUIRES_MORE
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await paymentModule.cancelPayment(payment.id)
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
`Error was thrown trying to cancel payment - ${payment.id} - ${e}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CustomerDTO } from "../customer"
|
import { CustomerDTO } from "../customer"
|
||||||
import { ShippingOptionDTO } from "../fulfillment"
|
import { ShippingOptionDTO } from "../fulfillment"
|
||||||
|
import { PaymentCollectionDTO } from "../payment"
|
||||||
import { ProductDTO } from "../product"
|
import { ProductDTO } from "../product"
|
||||||
import { RegionDTO } from "../region"
|
import { RegionDTO } from "../region"
|
||||||
import { BigNumberInput } from "../totals"
|
import { BigNumberInput } from "../totals"
|
||||||
@@ -149,3 +150,8 @@ export interface ConfirmVariantInventoryWorkflowInputDTO {
|
|||||||
quantity: BigNumberInput
|
quantity: BigNumberInput
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CartWorkflowDTO {
|
||||||
|
id: string
|
||||||
|
payment_collection: PaymentCollectionDTO
|
||||||
|
}
|
||||||
@@ -400,6 +400,12 @@ export interface IPaymentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<PaymentCollectionDTO[]>
|
): Promise<PaymentCollectionDTO[]>
|
||||||
|
|
||||||
|
retrievePaymentSession(
|
||||||
|
paymentSessionId: string,
|
||||||
|
config?: FindConfig<PaymentSessionDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<PaymentSessionDTO>
|
||||||
|
|
||||||
/* ********** PAYMENT SESSION ********** */
|
/* ********** PAYMENT SESSION ********** */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const MedusaErrorTypes = {
|
|||||||
UNEXPECTED_STATE: "unexpected_state",
|
UNEXPECTED_STATE: "unexpected_state",
|
||||||
CONFLICT: "conflict",
|
CONFLICT: "conflict",
|
||||||
PAYMENT_AUTHORIZATION_ERROR: "payment_authorization_error",
|
PAYMENT_AUTHORIZATION_ERROR: "payment_authorization_error",
|
||||||
|
PAYMENT_REQUIRES_MORE_ERROR: "payment_requires_more_error",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MedusaErrorCodes = {
|
export const MedusaErrorCodes = {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { completeCartWorkflow } from "@medusajs/core-flows"
|
import { completeCartWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { MedusaError } from "@medusajs/utils"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||||
import { refetchOrder } from "../../../orders/helpers"
|
import { refetchOrder } from "../../../orders/helpers"
|
||||||
|
import { refetchCart } from "../../helpers"
|
||||||
|
import { defaultStoreCartFields } from "../../query-config"
|
||||||
import { StoreCompleteCartType } from "../../validators"
|
import { StoreCompleteCartType } from "../../validators"
|
||||||
|
import { prepareRetrieveQuery } from "../../../../../utils/get-query-config"
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: MedusaRequest<StoreCompleteCartType>,
|
req: MedusaRequest<StoreCompleteCartType>,
|
||||||
@@ -9,22 +13,49 @@ export const POST = async (
|
|||||||
) => {
|
) => {
|
||||||
const cart_id = req.params.id
|
const cart_id = req.params.id
|
||||||
|
|
||||||
// If the idempotencyKey is present:
|
|
||||||
// - is workflow is running?
|
|
||||||
// = throw error
|
|
||||||
// - else
|
|
||||||
// - re-run the workflow at the failed step
|
|
||||||
|
|
||||||
const { errors, result } = await completeCartWorkflow(req.scope).run({
|
const { errors, result } = await completeCartWorkflow(req.scope).run({
|
||||||
input: { id: req.params.id },
|
input: { id: cart_id },
|
||||||
context: {
|
context: { transactionId: cart_id },
|
||||||
transactionId: cart_id,
|
|
||||||
},
|
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// When an error occurs on the workflow, its potentially got to with cart validations, payments
|
||||||
|
// or inventory checks. Return the cart here along with errors for the consumer to take more action
|
||||||
|
// and fix them
|
||||||
if (Array.isArray(errors) && errors[0]) {
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
throw errors[0].error
|
const error = errors[0].error
|
||||||
|
const statusOKErrors: string[] = [
|
||||||
|
// TODO: add inventory specific errors
|
||||||
|
MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR,
|
||||||
|
MedusaError.Types.PAYMENT_REQUIRES_MORE_ERROR,
|
||||||
|
]
|
||||||
|
|
||||||
|
// If we end up with errors outside of statusOKErrors, it means that the cart is not in a state to be
|
||||||
|
// completed. In these cases, we return a 400.
|
||||||
|
const cart = await refetchCart(
|
||||||
|
cart_id,
|
||||||
|
req.scope,
|
||||||
|
prepareRetrieveQuery(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
defaults: defaultStoreCartFields,
|
||||||
|
}
|
||||||
|
).remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!statusOKErrors.includes(error.type)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
type: "cart",
|
||||||
|
cart,
|
||||||
|
error: {
|
||||||
|
message: error.message,
|
||||||
|
name: error.name,
|
||||||
|
type: error.type,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const order = await refetchOrder(
|
const order = await refetchOrder(
|
||||||
@@ -33,5 +64,8 @@ export const POST = async (
|
|||||||
req.remoteQueryConfig.fields
|
req.remoteQueryConfig.fields
|
||||||
)
|
)
|
||||||
|
|
||||||
res.status(200).json({ order })
|
res.status(200).json({
|
||||||
|
type: "order",
|
||||||
|
order,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,6 @@ export const defaultStoreCartFields = [
|
|||||||
"*region.countries",
|
"*region.countries",
|
||||||
"sales_channel_id",
|
"sales_channel_id",
|
||||||
|
|
||||||
// TODO: To be updated when payment sessions are introduces in the Rest API
|
|
||||||
"payment_collection.id",
|
"payment_collection.id",
|
||||||
"payment_collection.amount",
|
"payment_collection.amount",
|
||||||
"*payment_collection.payment_sessions",
|
"*payment_collection.payment_sessions",
|
||||||
|
|||||||
@@ -89,3 +89,8 @@ export const StoreCompleteCart = z
|
|||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
export type StoreCompleteCartType = z.infer<typeof StoreCompleteCart>
|
export type StoreCompleteCartType = z.infer<typeof StoreCompleteCart>
|
||||||
|
|
||||||
|
export type StoreCreateCartPaymentCollectionType = z.infer<
|
||||||
|
typeof StoreCreateCartPaymentCollection
|
||||||
|
>
|
||||||
|
export const StoreCreateCartPaymentCollection = z.object({}).strict()
|
||||||
@@ -50,6 +50,6 @@ export const defaultStoreRetrieveOrderFields = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const retrieveTransformQueryConfig = {
|
export const retrieveTransformQueryConfig = {
|
||||||
defaultFields: defaultStoreRetrieveOrderFields,
|
defaults: defaultStoreRetrieveOrderFields,
|
||||||
isList: false,
|
isList: false,
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,7 @@ export const defaultPaymentCollectionFields = [
|
|||||||
"id",
|
"id",
|
||||||
"currency_code",
|
"currency_code",
|
||||||
"amount",
|
"amount",
|
||||||
"payment_sessions",
|
"*payment_sessions",
|
||||||
"payment_sessions.id",
|
|
||||||
"payment_sessions.amount",
|
|
||||||
"payment_sessions.provider_id",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const retrievePaymentCollectionTransformQueryConfig = {
|
export const retrievePaymentCollectionTransformQueryConfig = {
|
||||||
|
|||||||
Reference in new issue
Block a user