# Source: https://upstash.com/docs/workflow/examples/paymentRetry.md

> ## Documentation Index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment Retries

This example demonstrates a payment retry process using Upstash Workflow.
The following example handles retrying a payment, sending emails, and suspending accounts.

## Use Case

Our workflow will:

1. Attempt to process a payment
2. Retry the payment if it fails with a 24-hour delay
3. If the payment succeeds:
   * Unsuspend the user's account if it was suspended
   * Send an invoice email
4. If the payment fails after 3 retries:
   * Suspend the user's account

## Code Example

<CodeGroup>
  ```typescript api/workflow/route.ts theme={"system"}
  import { serve } from "@upstash/workflow/nextjs";

  type ChargeUserPayload = {
    email: string;
  };

  export const { POST } = serve<ChargeUserPayload>(async (context) => {
    const { email } = context.requestPayload;

    for (let i = 0; i < 3; i++) {
      // attempt to charge the user
      const result = await context.run("charge customer", async () => {
        try {
          return await chargeCustomer(i + 1),
        } catch (e) {
          console.error(e);
          return
        }
      });

      if (!result) {
        // Wait for a day
        await context.sleep("wait for retry", 24 * 60 * 60);
      } else {
        // Unsuspend User
        const isSuspended = await context.run("check suspension", async () => {
          return await checkSuspension(email);
        });
        if (isSuspended) {
          await context.run("unsuspend user", async () => {
            await unsuspendUser(email);
          });
        }

        // send invoice email
        await context.run("send invoice email", async () => {
          await sendEmail(
            email,
            `Payment successful. Invoice: ${result.invoiceId}, Total cost: $${result.totalCost}`
          );
        });

        // by returning, we end the workflow run
        return;
      }
    }

    // suspend user if the user isn't suspended
    const isSuspended = await context.run("check suspension", async () => {
      return await checkSuspension(email);
    });

    if (!isSuspended) {
      await context.run("suspend user", async () => {
        await suspendUser(email);
      });

      await context.run("send suspended email", async () => {
        await sendEmail(
          email,
          "Your account has been suspended due to payment failure. Please update your payment method."
        );
      });
    }
  });

  async function sendEmail(email: string, content: string) {
    // Implement the logic to send an email
    console.log("Sending email to", email, "with content:", content);
  }

  async function checkSuspension(email: string) {
    // Implement the logic to check if the user is suspended
    console.log("Checking suspension status for", email);
    return true;
  }

  async function suspendUser(email: string) {
    // Implement the logic to suspend the user
    console.log("Suspending the user", email);
  }

  async function unsuspendUser(email: string) {
    // Implement the logic to unsuspend the user
    console.log("Unsuspending the user", email);
  }

  async function chargeCustomer(attempt: number) {
    // Implement the logic to charge the customer
    console.log("Charging the customer");

    if (attempt <= 2) {
      throw new Error("Payment failed");
    }

    return {
      invoiceId: "INV123",
      totalCost: 100,
    } as const;
  }
  ```

  ```python main.py theme={"system"}
  from fastapi import FastAPI
  from typing import TypedDict, Optional
  from dataclasses import dataclass
  from upstash_workflow.fastapi import Serve
  from upstash_workflow import AsyncWorkflowContext

  app = FastAPI()
  serve = Serve(app)


  @dataclass
  class ChargeResult:
      invoice_id: str
      total_cost: float


  class ChargeUserPayload(TypedDict):
      email: str


  async def send_email(email: str, content: str) -> None:
      # Implement the logic to send an email
      print("Sending email to", email, "with content:", content)


  async def check_suspension(email: str) -> bool:
      # Implement the logic to check if the user is suspended
      print("Checking suspension status for", email)
      return True


  async def suspend_user(email: str) -> None:
      # Implement the logic to suspend the user
      print("Suspending the user", email)


  async def unsuspend_user(email: str) -> None:
      # Implement the logic to unsuspend the user
      print("Unsuspending the user", email)


  async def charge_customer(attempt: int) -> Optional[ChargeResult]:
      # Implement the logic to charge the customer
      print("Charging the customer")
      if attempt <= 2:
          raise Exception("Payment failed")
      return ChargeResult(invoice_id="INV123", total_cost=100)


  @serve.post("/payment-retries")
  async def payment_retries(context: AsyncWorkflowContext[ChargeUserPayload]) -> None:
      email = context.request_payload["email"]

      async def _check_suspension() -> bool:
          return await check_suspension(email)

      for i in range(3):
          # attempt to charge the user
          async def _charge_customer() -> Optional[ChargeResult]:
              try:
                  return await charge_customer(i + 1)
              except Exception as e:
                  print(f"Error: {e}")
                  return None

          result = await context.run("charge customer", _charge_customer)

          if not result:
              # Wait for a day
              await context.sleep("wait for retry", 24 * 60 * 60)
          else:
              # Unsuspend User
              is_suspended = await context.run("check suspension", _check_suspension)

              if is_suspended:

                  async def _unsuspend_user() -> None:
                      await unsuspend_user(email)

                  await context.run("unsuspend user", _unsuspend_user)

              # send invoice email
              async def _send_invoice_email() -> None:
                  await send_email(
                      email,
                      f"Payment successful. Invoice: {result.invoice_id}, Total cost: ${result.total_cost}",
                  )

              await context.run("send invoice email", _send_invoice_email)

              # by returning, we end the workflow run
              return

      # suspend user if the user isn't suspended
      is_suspended = await context.run("check suspension", _check_suspension)

      if not is_suspended:

          async def _suspend_user() -> None:
              await suspend_user(email)

          await context.run("suspend user", _suspend_user)

          async def _send_suspended_email() -> None:
              await send_email(
                  email,
                  "Your account has been suspended due to payment failure. Please update your payment method.",
              )

          await context.run("send suspended email", _send_suspended_email)

  ```
</CodeGroup>

## Code Breakdown

### 1. Charge Customer

We attempt to charge the customer:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  const result = await context.run("charge customer", async () => {
    try {
      return await chargeCustomer(i + 1),
    } catch (e) {
      console.error(e);
      return
    }
  });
  ```

  ```python Python theme={"system"}
  async def _charge_customer() -> Optional[ChargeResult]:
      try:
          return await charge_customer(i + 1)
      except Exception as e:
          print(f"Error: {e}")
          return None

  result = await context.run("charge customer", _charge_customer)

  ```
</CodeGroup>

<Note>
  If we haven't put a try-catch block here, the workflow would have retried the step.
  However, because we want to run custom logic when the payment fails, we catch the error here.
</Note>

### 2. Retry Payment

We try to charge the customer 3 times with a 24-hour delay between each attempt:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  for (let i = 0; i < 3; i++) {
    // attempt to charge the customer

    if (!result) {
      // Wait for a day
      await context.sleep("wait for retry", 24 * 60 * 60);
    } else {
      // Payment succeeded
      // Unsuspend user, send invoice email
      // end the workflow:
      return;
    }
  }
  ```

  ```python Python theme={"system"}
  for i in range(3):
      # attempt to charge the customer

      if not result:
          # Wait for a day
          await context.sleep("wait for retry", 24 * 60 * 60)
      else:
          # Payment succeeded
          # Unsuspend user, send invoice email
          # end the workflow:
          return

  ```
</CodeGroup>

### 3. If Payment Succeeds

#### 3.1. Unsuspend User

We check if the user is suspended and unsuspend them if they are:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  const isSuspended = await context.run("check suspension", async () => {
    return await checkSuspension(email);
  });
  if (isSuspended) {
    await context.run("unsuspend user", async () => {
      await unsuspendUser(email);
    });
  }
  ```

  ```python Python theme={"system"}
  async def _check_suspension() -> bool:
      return await check_suspension(email)

  is_suspended = await context.run("check suspension", _check_suspension)

  if is_suspended:

      async def _unsuspend_user() -> None:
          await unsuspend_user(email)

      await context.run("unsuspend user", _unsuspend_user)

  ```
</CodeGroup>

#### 3.2. Send Invoice Email

We send an invoice we got from the payment step to the user:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  await context.run("send invoice email", async () => {
    await sendEmail(
      email,
      `Payment successful. Invoice: ${result.invoiceId}, Total cost: $${result.totalCost}`
    );
  });
  ```

  ```python Python theme={"system"}
  async def _send_invoice_email() -> None:
      await send_email(
          email,
          f"Payment successful. Invoice: {result.invoice_id}, Total cost: ${result.total_cost}",
      )

  await context.run("send invoice email", _send_invoice_email)

  ```
</CodeGroup>

<Tip>
  One of the biggest advantages of using Upstash Workflow is that you have access to the result of the previous steps.
  This allows you to pass data between steps without having to store it in a database.
</Tip>

### 4. If Payment Fails After 3 Retries

#### 4.1. Suspend User

If the payment fails after 3 retries, we suspend the user and send them an email to notify them:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  const isSuspended = await context.run("check suspension", async () => {
    return await checkSuspension(email);
  });

  if (!isSuspended) {
    await context.run("suspend user", async () => {
      await suspendUser(email);
    });

    await context.run("send suspended email", async () => {
      await sendEmail(
        context.requestPayload.email,
        "Your account has been suspended due to payment failure. Please update your payment method."
      );
    });
  }
  ```

  ```python Python theme={"system"}
  async def _check_suspension() -> bool:
      return await check_suspension(email)

  is_suspended = await context.run("check suspension", _check_suspension)

  if not is_suspended:

      async def _suspend_user() -> None:
          await suspend_user(email)

      await context.run("suspend user", _suspend_user)

      async def _send_suspended_email() -> None:
          await send_email(
              email,
              "Your account has been suspended due to payment failure. Please update your payment method.",
          )

      await context.run("send suspended email", _send_suspended_email)

  ```
</CodeGroup>