This example demonstrates a payment retry process using Upstash Workflow.
The following example handles retrying a payment, sending emails, and suspending accounts.
import{ serve }from"@upstash/workflow/nextjs";typeChargeUserPayload={ email:string;};exportconst{POST}=serve<ChargeUserPayload>(async(context)=>{const{ email }= context.requestPayload;for(let i =0; i <3; i++){// attempt to charge the userconst result =await context.run("charge customer",async()=>{try{returnawaitchargeCustomer(i +1),}catch(e){console.error(e);return}});if(!result){// Wait for a dayawait context.sleep("wait for retry",24*60*60);}else{// Unsuspend Userconst isSuspended =await context.run("check suspension",async()=>{returnawaitcheckSuspension(email);});if(isSuspended){await context.run("unsuspend user",async()=>{awaitunsuspendUser(email);});}// send invoice emailawait context.run("send invoice email",async()=>{awaitsendEmail( email,`Payment successfull. Incoice: ${result.invoiceId}, Total cost: $${result.totalCost}`);});// by retuning, we end the workflow runreturn;}}// suspend user if the user isn't suspendedconst isSuspended =await context.run("check suspension",async()=>{returnawaitcheckSuspension(email);});if(!isSuspended){await context.run("suspend user",async()=>{awaitsuspendUser(email);});await context.run("send suspended email",async()=>{awaitsendEmail( email,"Your account has been suspended due to payment failure. Please update your payment method.");});}});asyncfunctionsendEmail(email:string, content:string){// Implement the logic to send an emailconsole.log("Sending email to", email,"with content:", content);}asyncfunctioncheckSuspension(email:string){// Implement the logic to check if the user is suspendedconsole.log("Checking suspension status for", email);returntrue;}asyncfunctionsuspendUser(email:string){// Implement the logic to suspend the userconsole.log("Suspending the user", email);}asyncfunctionunsuspendUser(email:string){// Implement the logic to unsuspend the userconsole.log("Unsuspending the user", email);}asyncfunctionchargeCustomer(attempt:number){// Implement the logic to charge the customerconsole.log("Charging the customer");if(attempt <=2){thrownewError("Payment failed");}return{ invoiceId:"INV123", totalCost:100,}asconst;}
const result =await context.run("charge customer",async()=>{try{returnawaitchargeCustomer(i +1),}catch(e){console.error(e);return}});
If we haven’t put a try-catch block here, the workflow would still have retried the step.
Hovewer, because we want to run custom logic when the payment fails, we catch the error here.
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 customerif(!result){// Wait for a dayawait context.sleep("wait for retry",24*60*60);}else{// Payment succeeded// Unsuspend user, send invoice email// end the workflow:return;}}
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.
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()=>{returnawaitcheckSuspension(email);});if(!isSuspended){await context.run("suspend user",async()=>{awaitsuspendUser(email);});await context.run("send suspended email",async()=>{awaitsendEmail( context.requestPayload.email,"Your account has been suspended due to payment failure. Please update your payment method.");});}