What you'll build

In this part you'll add the checkout step. When a customer types pay, the bot:

  1. Asks for their name and delivery address
  2. Generates a Paystack payment link for the cart total
  3. Sends the link in the WhatsApp chat
  4. After payment, Paystack calls a webhook so you can send a confirmation message back to the customer

Add the checkout flow to the session

BlocWeave prompt

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
BlocWeave prompt

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. Call POST https://api.paystack.co/transaction/initialize with Authorization: Bearer PAYSTACK_SECRET_KEY. Body: { email, amount: Math.round(amountGhs * 100), reference, metadata }. Return data.authorization_url. Throw a descriptive error if the Paystack call fails.

Add checkout to the handler

BlocWeave prompt

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.

BlocWeave prompt

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:

  1. Build a cart (menu → category → item → add)
  2. Type pay
  3. Enter your name and address
  4. Click the Paystack link
  5. Pay with test card 4084 0841 1881 8882, CVV 408, any future expiry, OTP 123456
  6. 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.