-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(order): add processing payment child workflow for creating orders
- Loading branch information
1 parent
a487bc6
commit 2a79bb5
Showing
26 changed files
with
569 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { continueAsNew, workflowInfo } from "@temporalio/workflow"; | ||
|
||
const MAX_NUMBER_OF_EVENTS = 10000; | ||
|
||
export async function longRunningWorkflow(n: number): Promise<void> { | ||
// Long-duration workflow | ||
while (workflowInfo().historyLength < MAX_NUMBER_OF_EVENTS) { | ||
//... | ||
} | ||
|
||
await continueAsNew<typeof longRunningWorkflow>(n + 1); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,58 @@ | ||
import { continueAsNew, sleep, workflowInfo } from "@temporalio/workflow"; | ||
/* eslint-disable @nx/enforce-module-boundaries */ | ||
import { | ||
allHandlersFinished, | ||
ChildWorkflowHandle, | ||
condition, | ||
setHandler, | ||
startChild, | ||
} from '@temporalio/workflow'; | ||
|
||
const MAX_NUMBER_OF_EVENTS = 10000; | ||
import { | ||
OrderProcessPaymentStatus, | ||
OrderWorkflowData, | ||
OrderWorkflowState, | ||
OrderWorkflowStatus, | ||
getOrderStateQuery, | ||
getWorkflowIdByPaymentOrder, | ||
} from '../../../../libs/backend/core/src/lib/order/workflow.utils'; | ||
import { | ||
cancelWorkflowSignal, | ||
} from '../../../../libs/backend/core/src/lib/workflows'; | ||
import { processPayment } from './process-payment.workflow'; | ||
|
||
export async function createOrder(email?: string): Promise<void> { | ||
|
||
// Long-duration workflow | ||
while (workflowInfo().historyLength < MAX_NUMBER_OF_EVENTS) { | ||
await sleep(1000); | ||
} | ||
const initialState: OrderWorkflowState = { | ||
status: OrderWorkflowStatus.PENDING, | ||
orderId: 0, | ||
}; | ||
|
||
export async function createOrder( | ||
data: OrderWorkflowData, | ||
state = initialState | ||
): Promise<void> { | ||
let processPaymentWorkflow: ChildWorkflowHandle<typeof processPayment>; | ||
// Attach queries, signals and updates | ||
setHandler(getOrderStateQuery, () => state); | ||
setHandler( | ||
cancelWorkflowSignal, | ||
() => processPaymentWorkflow?.signal(cancelWorkflowSignal) | ||
); | ||
// TODO: Create the order in the database | ||
|
||
await continueAsNew<typeof createOrder>(email); | ||
} | ||
state.status = OrderWorkflowStatus.PROCESSING_PAYMENT; | ||
processPaymentWorkflow = await startChild(processPayment, { | ||
args: [data], | ||
workflowId: getWorkflowIdByPaymentOrder(state.orderId), | ||
}); | ||
const processPaymentResult = await processPaymentWorkflow.result(); | ||
if (processPaymentResult.status === OrderProcessPaymentStatus.SUCCESS) { | ||
state.status = OrderWorkflowStatus.PAYMENT_COMPLETED; | ||
} else { | ||
state.status = OrderWorkflowStatus.FAILED; | ||
return; | ||
} | ||
processPaymentWorkflow = undefined; | ||
//... | ||
state.status = OrderWorkflowStatus.COMPLETED; | ||
// Wait for all handlers to finish before workflow completion | ||
await condition(allHandlersFinished); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* eslint-disable @nx/enforce-module-boundaries */ | ||
import { | ||
log, | ||
condition, | ||
setHandler, | ||
allHandlersFinished, | ||
} from '@temporalio/workflow'; | ||
|
||
import { | ||
OrderWorkflowData, | ||
PROCESS_PAYMENT_TIMEOUT, | ||
OrderProcessPaymentState, | ||
OrderProcessPaymentStatus, | ||
paymentWebHookEventSignal, | ||
} from '../../../../libs/backend/core/src/lib/order'; | ||
import { | ||
cancelWorkflowSignal, | ||
} from '../../../../libs/backend/core/src/lib/workflows'; | ||
|
||
export const finalPaymentStatuses = [ | ||
OrderProcessPaymentStatus.SUCCESS, | ||
OrderProcessPaymentStatus.FAILURE, | ||
OrderProcessPaymentStatus.DECLINED, | ||
OrderProcessPaymentStatus.CANCELLED, | ||
]; | ||
|
||
const initiatedWebhookEvents = [ | ||
// Stripe | ||
'payment_intent.created', | ||
'payment_intent.processing', | ||
'payment_method.attached', | ||
] | ||
const confirmedWebhookEvents = [ | ||
// Stripe | ||
'checkout.session.completed', | ||
'checkout.session.async_payment_succeeded', | ||
'payment_intent.succeeded', | ||
]; | ||
const failedWebhookEvents = [ | ||
// Stripe | ||
'payment_intent.payment_failed', | ||
]; | ||
|
||
export async function processPayment( | ||
data: OrderWorkflowData, | ||
): Promise<OrderProcessPaymentState> { | ||
const state: OrderProcessPaymentState = { | ||
status: OrderProcessPaymentStatus.PENDING, | ||
}; | ||
log.info('Processing payment', { data }); | ||
|
||
// Attach queries, signals and updates | ||
setHandler(cancelWorkflowSignal, async () => { | ||
if (finalPaymentStatuses.includes(state.status)) { | ||
log.warn('Payment already completed, cannot cancel'); | ||
return; | ||
} | ||
log.warn('Cancelling payment'); | ||
state.status = OrderProcessPaymentStatus.CANCELLED; | ||
}); | ||
setHandler( | ||
paymentWebHookEventSignal, | ||
async (webhookEvent) => { | ||
if (initiatedWebhookEvents.includes(webhookEvent.type)) { | ||
state.status = OrderProcessPaymentStatus.INITIATED; | ||
} else if (confirmedWebhookEvents.includes(webhookEvent.type)) { | ||
state.status = OrderProcessPaymentStatus.SUCCESS; | ||
} else if (failedWebhookEvents.includes(webhookEvent.type)) { | ||
state.status = OrderProcessPaymentStatus.FAILURE; | ||
} | ||
}, | ||
); | ||
|
||
await condition( | ||
() => finalPaymentStatuses.includes(state.status), | ||
PROCESS_PAYMENT_TIMEOUT | ||
); | ||
// Wait for all handlers to finish before workflow completion | ||
await condition(allHandlersFinished); | ||
|
||
return state; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './workflow.utils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Enum representing supported payment providers. | ||
* | ||
* @enum {string} | ||
*/ | ||
export enum PaymentProvider { | ||
Stripe = 'Stripe', | ||
MercadoPago = 'MercadoPago', | ||
PayU = 'PayU', | ||
Wompi = 'Wompi', | ||
// Add more providers as needed | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { CreateOrderDto } from '@projectx/models'; | ||
import { defineQuery, defineSignal } from '@temporalio/workflow'; | ||
|
||
export type OrderWorkflowData = { | ||
email: string; | ||
order: CreateOrderDto; | ||
}; | ||
|
||
export const getWorkflowIdByPaymentOrder = (orderId: number) => { | ||
return `payment-${orderId}`; | ||
}; | ||
|
||
export enum OrderWorkflowStatus { | ||
PENDING = 'Pending', | ||
PROCESSING_PAYMENT = 'ProcessingPayment', | ||
PAYMENT_COMPLETED = 'PaymentCompleted', | ||
COMPLETED = 'Completed', | ||
FAILED = 'Failed', | ||
} | ||
export type OrderWorkflowState = { | ||
status: OrderWorkflowStatus; | ||
orderId?: number; | ||
}; | ||
|
||
export enum OrderProcessPaymentStatus { | ||
PENDING = 'Pending', | ||
INITIATED = 'Initiated', | ||
SUCCESS = 'Success', | ||
DECLINED = 'Declined', | ||
CANCELLED = 'Cancelled', | ||
FAILURE = 'Failure', | ||
} | ||
export type OrderProcessPaymentState = { | ||
status: OrderProcessPaymentStatus; | ||
}; | ||
|
||
export const PROCESS_PAYMENT_TIMEOUT = '20 minutes'; | ||
|
||
// DEFINE QUERIES | ||
export const getOrderStateQuery = | ||
defineQuery<OrderWorkflowState>('getOrderStateQuery'); | ||
|
||
/** | ||
* Represents a payment webhook event received from third-party payment providers | ||
* such as Stripe, MercadoPago, PayU, Wompi, etc. | ||
* | ||
* @property {string} id - Unique identifier for the webhook event. | ||
* @property {string} type - Type of the event (e.g., 'payment_intent.succeeded'). | ||
* @property {string} provider - Payment provider sending the webhook (e.g., 'Stripe', 'PayU'). | ||
* @property {Object} data - Payload containing event-specific data. | ||
*/ | ||
export type PaymentWebhookEvent = { | ||
id?: string; | ||
type: string; | ||
provider: 'Stripe' | 'MercadoPago' | 'PayU' | 'Wompi'; | ||
data: Record<string, unknown>; | ||
}; | ||
|
||
// DEFINE SIGNALS | ||
/** | ||
* Receive a payment webhook event, webhook events is particularly useful for listening to asynchronous events | ||
* such as when a customer’s bank confirms a payment, a customer disputes a charge, | ||
* a recurring payment succeeds, or when collecting subscription payments. | ||
*/ | ||
export const paymentWebHookEventSignal = defineSignal<[PaymentWebhookEvent]>( | ||
'paymentWebHookSignal' | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
export * from './user.decorator'; | ||
export * from './user.interface'; | ||
export * from './user.workflow'; | ||
export * from './workflow.utils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.