Brewing Coffee : Setting Up Payment with Temporal Activities

AniruddhaAniruddha
3 min read

In the last post, we created a basic Temporal Workflow to handle coffee orders. However, it didn’t actually do anything yet!

In this post, we’ll start adding functionality by creating the Payment Service — a dedicated micro-service that will calculate billing amounts for coffee orders.

Overview

Our payment-service will:

  • Receive a coffee order.

  • Calculate the total bill.

  • Return the billing amount back to the workflow.

For simplicity -

  • Each coffee costs $2.00 (we represent it as 200 cents internally).

  • We’ll communicate all amounts in integer cents to avoid floating point precision issues.

Step 1 : Setting-up Payment Service

Let’s create the basic file structure for payment-service:

best-coffee-shop> mkdir payment-service
best-coffee-shop> cd payment-service
best-coffee-shop/payment-service> touch activity.py main.py

Step 2: Writing the Billing Activity

our payment service will have

  • acitivity.py : contains our activity logic

  • main.py : register activity worker

file payment-service/activity.py

from temporalio import activity
from dataclasses import dataclass

@dataclass
class CoffeeOrder:
    customer_name: str
    order_number: str
    quantity: int = 1

@dataclass
class OrderBill:
    order_number: str
    amount: int

COFFEE_UNIT_PRICE = 200

@activity.defn(name='BillCalculationActivity')
async def calculate_payment(order: CoffeeOrder) -> OrderBill:
    bill_amount = order.quantity * COFFEE_UNIT_PRICE

    return OrderBill(
        order_number=order.order_number,
        amount=bill_amount
    )
  • This defines an Activity named "BillCalculationActivity". Our workflow will reference this name to invoke the right activity.

  • It receives a CoffeeOrder and returns an OrderBill with the total amount.

    Note : In real applications, it’s better to share constants like activity names across projects using a common package. For python check out this StackOverflow discussion and explore tools like Poetry for dependency management.

file payment-service/main.py

import asyncio
from temporalio.worker import Worker
from temporalio.client import Client
from activity import calculate_payment
from temporal_config import TEMPORAL_ADDRESS, TASK_QUEUE

async def main():
    client = await Client.connect(TEMPORAL_ADDRESS)
    worker = Worker(
        client,
        task_queue=TASK_QUEUE,
        activities=[calculate_payment],
    )
    await worker.run()

if __name__ == "__main__":
    asyncio.run(main())

This worker connects to Temporal and listens for BillCalculationActivity execution requests.

Step 3 : Move Configuration to separate file

We will move our temporal configuration to separate file.

file : payment-service/temporal_config.py

TEMPORAL_ADDRESS = 'temporal:7233'
TASK_QUEUE = 'payment-service'

file : orchestrat-service/temporal_config.py

TEMPORAL_ADDRESS = "temporal:7233"
TASK_QUEUE = "best-coffee-orders"

Update imports in orchestrator-service/main.py

Step 4 : Update Workflow

Update the Workflow to invoke the payment calculation:

File: orchestrator-service/workflow.py

from temporalio import workflow
from dataclasses import dataclass
from datetime import timedelta
from temporal_config import TASK_QUEUE

@dataclass
class CoffeeOrder:
    customer_name: str
    order_number: str
    quantity: int = 1

@workflow.defn(name="CoffeeOrderWorkflow")
class CoffeeOrderWorkflow:
    @workflow.run
    async def run(self, order: CoffeeOrder):
        workflow.logger.info(f"CoffeeOrderWorkflow: New order received {order}")
        bill = await workflow.execute_activity(
            "BillCalculationActivity",
            order,
            task_queue=TASK_QUEUE,
            start_to_close_timeout=timedelta(seconds=15),
        )
        return bill

Now Workflow will start invoking payment-activity.

Step 5 : Update docker-compose.yml

Append docker-compose.yml with following

payment-service:
  container_name: payment-service
  image: python:3
  depends_on:
    - temporal
  volumes:
    - ./payment-service:/app
    - ./requirements.txt:/requirements.txt
    - ./init.sh:/init.sh
  entrypoint: sh -c "chmod -R 755 /app && chmod -R 755 /init.sh && sh /init.sh"
  networks:
    - best-coffee-network

Step 6 : Try-it Out

Clean up old containers (optional):

best-coffee-shop> docker-compose down
best-coffee-shop> docker system prune -a -f

Run

best-coffee-shop> docker-compose up

Visit http://localhost:8080 (Temporal UI) Click Start Workflow → by providing following

Workflow ID     = best-coffee-1
Task Queue      = best-coffee-orders
Workflow Type   = CoffeeOrderWorkflow
Data            = {"customer_name": "Aniruddha", "order_number": "best-coffee-2", "quantity": 1}
Encoding        = json/plain

You should see the workflow executing the BillCalculationActivity.

Click on the activity to inspect:

  • Input: Coffee order details

  • Output: Calculated bill (e.g., $2.00)

You can also switch to the JSON tab to see how Temporal handles the workflow execution behind the scenes.

How Temporal Handles Workflow Execution

Every statement inside a workflow becomes a task for Temporal:

  • workflow.logger.info(...) → generates a WorkflowTask

    • Events: WorkflowTaskScheduled → WorkflowTaskStarted → WorkflowTaskCompleted
  • await workflow.execute_activity(...) → generates an ActivityTask

    • Events: ActivityTaskScheduled → ActivityTaskStarted → ActivityTaskCompleted

Also notice:

  • workerVersion fields differ between Workflow Tasks and Activity Tasks.

  • Different workers execute workflows and activities!

Code for this post

You can find the code up to this 👉: Code repo

0
Subscribe to my newsletter

Read articles from Aniruddha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aniruddha
Aniruddha