What you'll build
In this part you'll add the checkout step. When a customer types pay, the bot:
- Asks for their name and delivery address
- Generates a Paystack payment link for the cart total
- Sends the link in the WhatsApp chat
- After payment, Paystack calls a webhook so you can send a confirmation message back to the customer
Add the checkout flow to the session
Update src/bot/session.ts to add checkout fields to SessionState. Add to SessionState: { step: "main" | "category" | "item" | "checkout-name" | "checkout-address" | "checkout-paying"; customerName?: string; deliveryAddress?: string } No other changes needed — the new steps will be handled in the webhook handler.
Build the Paystack payment service
Add your Paystack test key to .env:
PAYSTACK_SECRET_KEY=sk_test_your_key_here
Create src/services/paystack.ts for the Quick Bites bot.
Export createPaymentLink({ email, amountGhs, reference, metadata }: { email: string; amountGhs: number; reference: string; metadata: Record<string, string> }): Promise
Add checkout to the handler
Update src/bot/handler.ts to handle the checkout flow. Add "pay" as a global command: if the cart is empty, reply "Your cart is empty. Type menu to browse." Otherwise set step to "checkout-name" and reply "To complete your order, please enter your name:" step === "checkout-name": - Store the incoming message as customerName in the session - Set step to "checkout-address" - Reply "Thanks, [name]! Please enter your delivery address:" step === "checkout-address": - Store as deliveryAddress - Set step to "checkout-paying" - Generate a reference: "QB-" + Date.now() - Use the sender's WhatsApp number as a proxy email: from.replace("whatsapp:+", "") + "@whatsapp.quickbites.com" (Paystack requires a valid email format) - Call createPaymentLink({ email, amountGhs: cartTotal(from), reference, metadata: { phone: from, name: customerName, address: deliveryAddress } }) - Reply "Great! Your total is GHS [total]. Click below to pay securely:\n\n[payment link]\n\nThis link expires in 30 minutes. Reply cancel to start over." - Handle errors: if createPaymentLink throws, reply "Sorry, we couldn't generate a payment link. Please try again or call us directly." Add "cancel" as a global command: clearCart(from), reset step to "main", reply "Order cancelled. Type menu to start a new order."
Add the payment webhook
When payment completes, Paystack calls your webhook. You can use this to send a WhatsApp confirmation.
Add a Paystack webhook endpoint to src/index.ts (POST /webhook/paystack). 1. Verify the x-paystack-signature header against HMAC-SHA512 of the raw body using PAYSTACK_SECRET_KEY. Return 401 if invalid. 2. Parse the event. If event.event === "charge.success": - Extract metadata.phone (the customer's WhatsApp number) - Extract metadata.name - Format a receipt: "✅ Payment confirmed!\n\nHi [name], we received your payment of GHS [amount/100].\nYour order is being prepared.\n\nRef: [reference]\n\nThank you for ordering from Quick Bites! 🍔" - Send the receipt via Twilio to metadata.phone - Reset that phone's session: clearCart(phone), setSession(phone, { step: "main", cart: [] }) 3. Return 200. Mount BEFORE express.json() using express.raw({ type: "application/json" }) so the raw body is available for signature verification.
Update Twilio sandbox settings
Add the payment webhook URL to Twilio's sandbox settings (for logging — Twilio doesn't need to know about Paystack webhooks):
In Paystack dashboard → Settings → Webhooks, add:
https://your-ngrok-url.ngrok-free.app/webhook/paystack
Test the full checkout
On your phone:
- Build a cart (menu → category → item → add)
- Type
pay - Enter your name and address
- Click the Paystack link
- Pay with test card
4084 0841 1881 8882, CVV408, any future expiry, OTP123456 - You should receive a WhatsApp confirmation message within a few seconds
Phone number as email: Using the WhatsApp number as a proxy email is fine for a bot flow where you control the email format. For a real production launch, add a step that asks the customer for their email address — this also gives you a proper email receipt from Paystack.
Project structure after Part 3
src/
bot/
handler.ts
menu.ts
session.ts
services/
paystack.ts
index.ts
What's next
In Part 4 we deploy the bot to Cloudflare Workers — a serverless runtime that runs globally at the edge, with zero server management and a generous free tier.
BlocWeave