Skip to content

Dev#11

Merged
willymwai merged 16 commits into
mainfrom
dev
Jul 3, 2026
Merged

Dev#11
willymwai merged 16 commits into
mainfrom
dev

Conversation

@Elias-3817

Copy link
Copy Markdown

No description provided.

Elias-3817 and others added 14 commits June 29, 2026 16:36
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.
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
Copilot AI review requested due to automatic review settings July 3, 2026 04:22
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/lib/data/payment.ts
Comment thread src/lib/data/payment.ts
Comment thread src/lib/data/__tests__/payment.test.ts
Comment thread src/lib/data/__tests__/payment.test.ts
Comment thread src/lib/data/__tests__/payment.test.ts
Comment thread src/lib/data/__tests__/payment.test.ts
Comment thread next.config.ts
Comment thread next.config.ts
@willymwai willymwai merged commit 54b1d43 into main Jul 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants