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: 
Attempt to process a payment 
Retry the payment if it fails with a 24-hour delay 
If the payment succeeds:
Unsuspend the user’s account if it was suspended 
Send an invoice email 
 
 
If the payment fails after 3 retries:
Suspend the user’s account 
 
 
 
Code Example  
api/workflow/route.ts
main.py
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 ;  
}  
 
Code Breakdown  
1. Charge Customer  
We attempt to charge the customer: 
const  result  =  await  context . run ( "charge customer" ,  async  ()  =>  {  
  try  {  
    return  await  chargeCustomer ( i  +  1 ),  
  }  catch  ( e ) {  
    console . error ( e );  
    return  
  }  
});  
 
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. 
 
2. Retry Payment  
We try to charge the customer 3 times with a 24-hour delay between each attempt: 
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 ;  
  }  
}  
 
3. If Payment Succeeds  
3.1. Unsuspend User  
We check if the user is suspended and unsuspend them if they are: 
const  isSuspended  =  await  context . run ( "check suspension" ,  async  ()  =>  {  
  return  await  checkSuspension ( email );  
});  
if  ( isSuspended ) {  
  await  context . run ( "unsuspend user" ,  async  ()  =>  {  
    await  unsuspendUser ( email );  
  });  
}  
 
3.2. Send Invoice Email  
We send an invoice we got from the payment step to the user: 
await  context . run ( "send invoice email" ,  async  ()  =>  {  
  await  sendEmail (  
    email ,  
    `Payment successful. Invoice:  ${ result . invoiceId } , Total cost: $ ${ result . totalCost } `  
  );  
});  
 
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. 
 
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: 
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."  
    );  
  });  
}