Conversation
The v3 Store API direct-payment endpoint cannot carry a payment source, so M-Pesa was unsupported. Add a dedicated M-Pesa phone-number field for the M-Pesa method and send the number in the payment metadata bag, which the backend reads to build the source and trigger the STK push. M-Pesa confirmation is asynchronous, so route the M-Pesa checkout to a new awaiting-payment screen that polls the order's payment state and only advances to the thank-you page once payment is confirmed, instead of the moment the STK is sent. Adds getOrderPaymentStatus and createDirectPayment metadata test coverage.
In development the tenant lookup bypassed the \"use cache\" wrapper that production uses, so buildTenantConfigFromRecord ran new Date() in a bare server component. Under Next 16 Cache Components that throws during runtime prefetch, breaking every server-side fetch (markets, products), blank listings and 404 product pages. Always route resolveTenantConfigByHost through the cached resolver so the timestamp is read inside a cache boundary, matching production.
Renders the Lipa na M-Pesa wordmark beside the M-Pesa payment option in both the multi-method and single-method header rows, gated by isMpesaMethod. Uses next/image with the unoptimized prop because Next rejects SVG optimization (HTTP 400) and a vector icon gains nothing from it; the asset is served from public/payment-icons/mpesa.svg with its viewBox tightened to the wordmark bounds so it renders cleanly at small icon sizes.
The awaiting-payment poll awaited getOrderPaymentStatus with no error handling, so a single transient failure threw out of the loop and left the customer stuck on the spinner forever (the timeout check lives inside the dead loop). The fetch is now wrapped and a failure is treated as pending so polling continues until completion or timeout. normalizeKenyanPhone rejected numbers that carry both the country code and the trunk 0 (e.g. \"+254 0712 345 678\"), returning an empty string and blocking a valid Safaricom number. It now strips the redundant 0. The M-Pesa phone input set error styling but not aria-invalid, so assistive tech never announced the field as errored; it now reflects the error state. Adds unit coverage for the M-Pesa phone helpers, including the new country-code-plus-trunk-0 case.
On poll timeout the screen sent the customer to /order-placed, a success page that says 'Thanks for your order' and 'a confirmation email has been sent'. For an unconfirmed M-Pesa payment that is misleading, since the payment may still be pending or may never arrive. The timeout screen now states the truth: the order is placed and the M-Pesa payment can take a few minutes to confirm. The button is changed from a misleading 'Check order status' link to the success page into a plain 'Continue Shopping' link to the store home, which works for both guest and logged-in shoppers (the account order page is login-gated and would trap guests). Copy avoids promising a payment confirmation email because no such email is sent. Updated across all five locales."
The M-Pesa payment block and the awaiting-payment timeout screen used hand-rolled markup instead of the shared design system. The phone input carried a raw label, a hand-tuned hint paragraph, and an error paragraph in text-red-700, which is off the design palette. The timeout screen used a raw button styled with inline gray classes. Replace these with the existing base components so the checkout matches the convention already used by the account forms: - label, hint, and error now use Field, FieldLabel, FieldDescription, and FieldError. FieldError renders text-destructive with role="alert", so the error colour comes from the design tokens and is announced to assistive tech. - the timeout action now uses Button.
feat(checkout): M-Pesa (STK push) payment for the headless storefront
extractHomepageSource returned the whole tenant record before descending into design.layout.homepage, because the "looks like a homepage config" guard passes when version and sections are absent (always true for the store record). Published homepages were never read, so every shop fell back to the default. Resolve the nested homepage first, keep the whole-record check as a last resort. Also allowlist the local media host for next/image so the hero image renders in dev.
Replace the Spree fallback logo with the OLITT logo and change the footer to "Powered by OLITT" (links olitt.com).
Cover getHomepageConfig reading the tenant homepage nested at design.layout.homepage, cleared social proof not leaking through the default merge, section order preservation, and the default fallback when no homepage is present. Fails on the pre-fix resolver ordering.
Add rel="noopener noreferrer" to the OLITT footer link to match the file's other external links, insert an explicit space so "Powered by" renders separately from "OLITT" across all locales, and drop the redundant homepage-source self-check that returned source on both branches.
Fix/homepage config resolver
The branding default introduced an SVG logo that never painted: next/image returned 400 because dangerouslyAllowSVG was unset, and once SVGs are allowed it strips their intrinsic width/height while the header forced the img to height:auto, collapsing it to 0x0. Allow SVGs with a locked-down CSP and pin the logo to a fixed height so the browser derives width from the viewBox.
fix(branding): render the OLITT SVG logo in the header
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
There was a problem hiding this comment.
Pull request overview
This PR adds first-class M-Pesa support to the checkout flow (including phone capture + post-checkout polling), updates some tenant/homepage configuration handling, and refreshes OLITT branding assets/links and image handling.
Changes:
- Add M-Pesa utilities + UI (phone normalization/validation, metadata submission, and an “awaiting payment” polling page).
- Extend direct payments to accept metadata and add an order payment-status polling helper.
- Update OLITT branding (header logo, footer link), adjust homepage source extraction, and simplify tenant config caching behavior.
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/utils/mpesa.ts | Adds M-Pesa payment method detection and Kenyan phone normalization/validation helpers. |
| src/lib/utils/tests/mpesa.test.ts | Adds unit tests for M-Pesa helpers. |
| src/lib/tenant/olitt.ts | Routes tenant config resolution through the cached resolver unconditionally. |
| src/lib/homepage/index.ts | Adjusts homepage source extraction to prefer design.layout.homepage safely. |
| src/lib/homepage/index.test.ts | Adds coverage for homepage extraction/merging behavior. |
| src/lib/data/payment.ts | Adds direct-payment metadata support and introduces getOrderPaymentStatus for polling. |
| src/lib/data/tests/payment.test.ts | Adds tests for direct payment metadata and payment-status polling. |
| src/components/layout/Header.tsx | Switches default logo to olitt-logo.svg and tweaks rendered logo sizing. |
| src/components/layout/Footer.tsx | Updates “powered by” link to OLITT and adds safer external-link attributes. |
| src/components/checkout/PaymentSection.tsx | Adds M-Pesa UI (phone field + icon) and sends phone metadata for direct payments. |
| src/app/[country]/[locale]/(checkout)/checkout/[id]/CheckoutPageContent.tsx | Redirects direct payments that require confirmation to the awaiting-payment route. |
| src/app/[country]/[locale]/(checkout)/awaiting-payment/[id]/page.tsx | New client page that polls for payment completion/failure and redirects accordingly. |
| public/payment-icons/mpesa.svg | Adds M-Pesa payment icon asset. |
| public/olitt-logo.svg | Adds OLITT logo asset. |
| next.config.ts | Enables SVG support in next/image, adds image CSP, and allows loopback media origin. |
| messages/pl.json | Adds i18n strings for M-Pesa checkout/awaiting-payment UI. |
| messages/fr.json | Adds i18n strings for M-Pesa checkout/awaiting-payment UI. |
| messages/es.json | Adds i18n strings for M-Pesa checkout/awaiting-payment UI. |
| messages/en.json | Adds i18n strings for M-Pesa checkout/awaiting-payment UI. |
| messages/de.json | Adds i18n strings for M-Pesa checkout/awaiting-payment UI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
fix(images): allow s3.olitt.com host in next/image
No description provided.