OrderConfirmed

The following diagram shows the OrderConfirmed scenario:

  1. The OrderConfirmed function is triggered by HttpTrigger with a PUT request:
[FunctionName(FunctionNames.OrderConfirmedFunction)]
public static async Task<HttpResponseMessage> OrderConfirmed(
[HttpTrigger(AuthorizationLevel.Function, "put")] HttpRequestMessage req,
[OrchestrationClient] DurableOrchestrationClient starter,
ILogger log)
{
return await SendEventToOrderAsync(req, starter, Events.OrderPaid, log);
}

The payload accepted by the function looks like this:

{
"OrderId" : "78dc1f57-007d-4dd8-9e3b-dcbfbc6f62d0"
}
  1. The OrderConfirmed function calls an internal SendEventToOrderAsync method to send the Events.OrderPaid events to the orchestrator. Implementing the internal method means that you can use it also for the OrderCancelled scenario:
private static async Task<HttpResponseMessage> SendEventToOrderAsync(HttpRequestMessage req,
DurableOrchestrationClient starter,
string orderEvent,
ILogger log)
{
var jsonContent = await req.Content.ReadAsStringAsync();
OrderEventDto orderEventDto = JsonConvert.DeserializeObject<OrderEventDto>(jsonContent);
if (orderEventDto != null && !string.IsNullOrWhiteSpace(orderEventDto.OrderId))
{
await starter.RaiseEventAsync(orderEventDto.OrderId, orderEvent, null);
return starter.CreateCheckStatusResponse(req, orderEventDto.OrderId);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest) { ReasonPhrase = "Order not valid" };
}

The orchestrator function is waiting for the event (or for the OrderCancelled event or for the timeout):

[FunctionName(FunctionNames.OrderWorkflowFunction)]
public static async Task Run([OrchestrationTrigger] DurableOrchestrationContext context,
ILogger log)
{
// Add order code
if (addResult)
{
DateTime orderDeadline = context.CurrentUtcDateTime.AddMinutes(1);
var orderPaidEvent = context.WaitForExternalEvent(Events.OrderPaid);
var orderCancelledEvent = context.WaitForExternalEvent(Events.OrderCancelled);
var cancelTimer = context.CreateTimer(orderDeadline, CancellationToken.None);
var taskCompleted = await Task.WhenAny(orderPaidEvent, orderCancelledEvent, cancelTimer);
// Manage different scenarios based on what task is completed

}
}

As you can see in the orchestrator function, it creates three tasks: one for the external event Event.OrderPaid and one for the Events.OrderCancelled event and one task for the order canceled timer calling CreateTimer. Then, it waits for the completion of one of the tasks and performs the corresponding scenarios.

  1. When the orchestrator receives Events.OrderConfirmed, it calls the FinalizeOrder function activity to change the state of the order to Paid inside the storage table.
  2. After calling the FinalizeOrder function, the orchestrator calls the GenerateInvoice function to write the invoice file inside the storage blob.
  3. Finally, it calls the SendMail function to send an email to the customer (using the SendGrid service):
if (taskCompleted == orderCancelledEvent || taskCompleted == cancelTimer)
{
// Order Cancelled
}
else if (taskCompleted == orderPaidEvent)
{
order = await context.CallActivityAsync<Order>(FunctionNames.FinalizeOrderFunction,
new OrderStateChange()
{ NewOrderState = OrderStatus.Paid,
OrderId = order.Id });
await context.CallActivityAsync<string>(FunctionNames.GenerateInvoiceFunction, order);
}

if (order != null)
await context.CallActivityAsync<bool>(FunctionNames.SendMailFunction, order);

You can see the full code for the FinalizeOrder, GenerateInvoice, and SendMail functions in the GitHub repository containing the complete solution.