Skip to content

Commit

Permalink
feat: payment module optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
silenaker committed Sep 13, 2024
1 parent 32e9725 commit cd06ce4
Show file tree
Hide file tree
Showing 34 changed files with 1,551 additions and 1,133 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ build/**
**/stats
.favorites.json
.vscode
.history
1 change: 1 addition & 0 deletions packages/core/core-flows/src/cart/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./create-line-item-adjustments"
export * from "./create-line-items"
export * from "./create-payment-collection"
export * from "./create-shipping-method-adjustments"
export * from "./create-payment-collection"
export * from "./find-one-or-any-region"
export * from "./find-or-create-customer"
export * from "./find-sales-channel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const createOrUpdateOrderPaymentCollectionWorkflow = createWorkflow(
variables: {
filters: {
id: orderPaymentCollectionIds,
status: [PaymentCollectionStatus.NOT_PAID],
status: [PaymentCollectionStatus.PENDING],
},
},
list: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { removeRemoteLinkStep, useRemoteQueryStep } from "../../common"
export const throwUnlessStatusIsNotPaid = createStep(
"validate-payment-collection",
({ paymentCollection }: { paymentCollection: PaymentCollectionDTO }) => {
if (paymentCollection.status !== PaymentCollectionStatus.NOT_PAID) {
if (paymentCollection.status !== PaymentCollectionStatus.PENDING) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Can only delete payment collections where status is not_paid`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { createPaymentSessionsWorkflow } from "../../payment-collection"
export const throwUnlessPaymentCollectionNotPaid = createStep(
"validate-existing-payment-collection",
({ paymentCollection }: { paymentCollection: PaymentCollectionDTO }) => {
if (paymentCollection.status !== "not_paid") {
if (paymentCollection.status !== "pending") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Can only mark 'not_paid' payment collection as paid`
`Can only mark 'pending' payment collection as paid`
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export interface CreatePaymentSessionStepInput {
provider_id: string
amount: BigNumberInput
currency_code: string
provider_token?: string
context?: PaymentProviderContext
data?: Record<string, unknown>
}

export const createPaymentSessionStepId = "create-payment-session"
Expand All @@ -30,9 +30,9 @@ export const createPaymentSessionStep = createStep(
input.payment_collection_id,
{
provider_id: input.provider_id,
provider_token: input.provider_token,
currency_code: input.currency_code,
amount: input.amount,
data: input.data ?? {},
context: input.context,
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export const deletePaymentSessionsStep = createStep(
provider_id: paymentSession.provider_id,
currency_code: paymentSession.currency_code,
amount: paymentSession.amount,
data: paymentSession.data ?? {},
context: paymentSession.context,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { PaymentProviderContext, PaymentSessionDTO } from "@medusajs/types"
import {
BigNumberInput,
PaymentProviderContext,
PaymentSessionDTO,
} from "@medusajs/types"
import { MathBN } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../common"
import { createPaymentSessionStep } from "../steps"
import { deletePaymentSessionsWorkflow } from "./delete-payment-sessions"

export interface CreatePaymentSessionsWorkflowInput {
payment_collection_id: string
provider_id: string
provider_token?: string
data?: Record<string, unknown>
context?: PaymentProviderContext
amount?: BigNumberInput
}

export const createPaymentSessionsWorkflowId = "create-payment-sessions"
Expand All @@ -23,49 +28,41 @@ export const createPaymentSessionsWorkflowId = "create-payment-sessions"
*/
export const createPaymentSessionsWorkflow = createWorkflow(
createPaymentSessionsWorkflowId,
(input: WorkflowData<CreatePaymentSessionsWorkflowInput>): WorkflowResponse<PaymentSessionDTO> => {
(
input: WorkflowData<CreatePaymentSessionsWorkflowInput>
): WorkflowResponse<PaymentSessionDTO> => {
const paymentCollection = useRemoteQueryStep({
entry_point: "payment_collection",
fields: ["id", "amount", "currency_code", "payment_sessions.*"],
fields: [
"id",
"raw_amount",
"raw_authorized_amount",
"currency_code",
"payment_sessions.*",
],
variables: { id: input.payment_collection_id },
list: false,
})

const paymentSessionInput = transform(
{ paymentCollection, input },
(data) => {
const balance = MathBN.sub(
data.paymentCollection.raw_amount,
data.paymentCollection.raw_authorized_amount || 0
)
return {
payment_collection_id: data.input.payment_collection_id,
provider_id: data.input.provider_id,
provider_token: data.input.provider_token,
data: data.input.data,
context: data.input.context,
amount: data.paymentCollection.amount,
amount: MathBN.min(data.input.amount || balance, balance),
currency_code: data.paymentCollection.currency_code,
}
}
)

const deletePaymentSessionInput = transform(
{ paymentCollection },
(data) => {
return {
ids:
data.paymentCollection?.payment_sessions?.map((ps) => ps.id) || [],
}
}
)

// Note: We are deleting an existing active session before creating a new one
// for a payment collection as we don't support split payments at the moment.
// When we are ready to accept split payments, this along with other workflows
// need to be handled correctly
const [created] = parallelize(
createPaymentSessionStep(paymentSessionInput),
deletePaymentSessionsWorkflow.runAsStep({
input: deletePaymentSessionInput,
})
)

return new WorkflowResponse(created)
return new WorkflowResponse(createPaymentSessionStep(paymentSessionInput))
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export const capturePaymentStep = createStep(
ModuleRegistrationName.PAYMENT
)

const payment = await paymentModule.capturePayment(input)
const payment = await paymentModule.capturePayment(input.payment_id, {
amount: input.amount,
captured_by: input.captured_by,
})

return new StepResponse(payment)
}
Expand Down
7 changes: 5 additions & 2 deletions packages/core/core-flows/src/payment/steps/refund-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StepResponse, createStep } from "@medusajs/workflows-sdk"

export type RefundPaymentStepInput = {
payment_id: string
created_by?: string
refunded_by?: string
amount?: BigNumberInput
}

Expand All @@ -19,7 +19,10 @@ export const refundPaymentStep = createStep(
ModuleRegistrationName.PAYMENT
)

const payment = await paymentModule.refundPayment(input)
const payment = await paymentModule.refundPayment(input.payment_id, {
amount: input.amount,
refunded_by: input.refunded_by,
})

return new StepResponse(payment)
}
Expand Down
100 changes: 4 additions & 96 deletions packages/core/core-flows/src/payment/workflows/refund-payment.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,13 @@
import { BigNumberInput, OrderDTO, PaymentDTO } from "@medusajs/types"
import { MathBN, MedusaError, PaymentEvents } from "@medusajs/utils"
import { BigNumberInput } from "@medusajs/types"
import { PaymentEvents } from "@medusajs/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
when,
} from "@medusajs/workflows-sdk"
import { emitEventStep, useRemoteQueryStep } from "../../common"
import { addOrderTransactionStep } from "../../order/steps/add-order-transaction"
import { emitEventStep } from "../../common"
import { refundPaymentStep } from "../steps/refund-payment"

/**
* This step validates that the refund is valid for the order
*/
export const validateRefundStep = createStep(
"validate-refund-step",
async function ({
order,
payment,
amount,
}: {
order: OrderDTO
payment: PaymentDTO
amount?: BigNumberInput
}) {
const pendingDifference = order.summary?.raw_pending_difference!

if (MathBN.gte(pendingDifference, 0)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order does not have an outstanding balance to refund`
)
}

const amountPending = MathBN.mult(pendingDifference, -1)
const amountToRefund = amount ?? payment.raw_amount ?? payment.amount

if (MathBN.gt(amountToRefund, amountPending)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot refund more than pending difference - ${amountPending}`
)
}
}
)

export const refundPaymentWorkflowId = "refund-payment-workflow"
/**
* This workflow refunds a payment.
Expand All @@ -60,60 +21,7 @@ export const refundPaymentWorkflow = createWorkflow(
amount?: BigNumberInput
}>
) => {
const payment = useRemoteQueryStep({
entry_point: "payment",
fields: [
"id",
"payment_collection_id",
"currency_code",
"amount",
"raw_amount",
],
variables: { id: input.payment_id },
list: false,
throw_if_key_not_found: true,
})

const orderPaymentCollection = useRemoteQueryStep({
entry_point: "order_payment_collection",
fields: ["order.id"],
variables: { payment_collection_id: payment.payment_collection_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "order-payment-collection" })

const order = useRemoteQueryStep({
entry_point: "order",
fields: ["id", "summary", "currency_code", "region_id"],
variables: { id: orderPaymentCollection.order.id },
throw_if_key_not_found: true,
list: false,
}).config({ name: "order" })

validateRefundStep({ order, payment, amount: input.amount })
refundPaymentStep(input)

when({ orderPaymentCollection }, ({ orderPaymentCollection }) => {
return !!orderPaymentCollection?.order?.id
}).then(() => {
const orderTransactionData = transform(
{ input, payment, orderPaymentCollection },
({ input, payment, orderPaymentCollection }) => {
return {
order_id: orderPaymentCollection.order.id,
amount: MathBN.mult(
input.amount ?? payment.raw_amount ?? payment.amount,
-1
),
currency_code: payment.currency_code ?? order.currency_code,
reference_id: payment.id,
reference: "refund",
}
}
)

addOrderTransactionStep(orderTransactionData)
})
const payment = refundPaymentStep(input)

emitEventStep({
eventName: PaymentEvents.REFUNDED,
Expand Down
16 changes: 16 additions & 0 deletions packages/core/js-sdk/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,22 @@ export class Store {
query,
})
},

addPaymentSession: async (
paymentCollectionId: string,
body: Record<string, any>,
query?: SelectParams,
headers?: ClientHeaders
) => {
return this.client.fetch<{
payment_collection: HttpTypes.StorePaymentCollection
}>(`/store/payment-collections/${paymentCollectionId}/payment-sessions`, {
method: "POST",
headers,
body,
query,
})
},
}

public order = {
Expand Down
13 changes: 9 additions & 4 deletions packages/core/types/src/http/payment/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { BigNumberValue } from "../../totals"
* The payment collection's status.
*/
export type BasePaymentCollectionStatus =
| "not_paid"
| "awaiting"
| "pending"
| "paid"
| "partially_paid"
| "authorized"
| "partially_authorized"
| "canceled"
| "refunded"
| "partially_refunded"

/**
*
Expand All @@ -18,10 +20,13 @@ export type BasePaymentCollectionStatus =
export type BasePaymentSessionStatus =
| "authorized"
| "captured"
| "partially_captured"
| "refunded"
| "partially_refunded"
| "pending"
| "requires_more"
| "error"
| "canceled"
| "processing"

export interface BasePaymentProvider {
id: string
Expand Down
Loading

0 comments on commit cd06ce4

Please sign in to comment.