silvershop-suite / order-confirmation
One status-aware post-payment confirmation page (success / pending / failed / cancelled) for SilverShop: unifies the guest and member landing, adds opaque token guest access to an order, and a dedicated failed-payment page with retry.
Package info
github.com/silvershop-suite/silvershop-order-confirmation
Type:silverstripe-vendormodule
pkg:composer/silvershop-suite/order-confirmation
Requires
- php: ^8.3
- silvershop/core: ^6
- silverstripe/framework: ^6.0
Requires (Dev)
Suggests
- silvershop-suite/invoicing: Adds an invoice-PDF link to the confirmation once the order is paid.
- silvershop-suite/order-admin: Modern CMS order-management screen for the orders customers land on.
- silvershop-suite/storefront: Ships the styled confirmation + failed-payment templates the module lights up (the module itself only ships functional fallbacks).
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-19 19:35:20 UTC
README
One status-aware post-payment confirmation page for SilverShop — the same thank-you page for guests and members — plus a dedicated failed-payment page with retry. No core fork: everything is wired through existing extension points.
Why
Out of the box, SilverShop sends guests and members to two different pages after paying
(checkout/order/{ID} vs account/order/{ID}), guest access to an order is session-only (so an emailed link or
a cross-device open silently 404s), a failed or cancelled payment just dumps the customer back on /checkout,
and there is no neutral "we're confirming your payment" state for asynchronous methods (iDEAL / Mollie). This
module fixes all of that.
What it does
- Unifies the landing. An
Orderextension implements the coreupdateLinkhook, soOrder::Link()— which is the payment success/return URL — is one guest-capablecheckout/complete/{ID}?t={token}route for everyone. The member order-history view ataccount/order/{ID}keeps working unchanged. - Token guest access. Adds an opaque
GuestTokentoOrder(32 hex chars, WooCommerce order-key style), generated as the order leaves the cart (and at payment initiation — see below). The confirmation and the receipt-email link then work without a session and across devices, alongside the existing session/member access. - Status-aware rendering. One page with success / pending / failed / cancelled variants, resolved from
existing primitives:
Order::IsPaid()→ success,Payable::HasPendingPayments()→ pending, otherwise failed/cancelled. Pending never claims success — asynchronous payments show a neutral "confirming" state (the redirect is a hint; the webhook is the truth) with a lightweightcheckout/status/{ID}JSON poll (and a<meta refresh>fallback) to re-check. - Failed-payment page with retry. An immediate error routes to
checkout/failed/{ID}?t={token}, which re-exposes core'sOrderActionsForm(pay again / choose another method) and guards against double-charging ("already paid"). The module setsOrder.place_before_payment: trueso the order is placed — and its Reference + GuestToken assigned — before the gateway is called; a declined/cancelled/abandoned attempt therefore leaves a recoverableUnpaidorder with a reference to show (and is the correct model for async methods, where the order isUnpaidwhile the webhook is in flight). Withplace_before_paymenton, an off-site gateway cancel on return lands on the unified complete page's failed/cancelled variant, which links on to this failed/retry page.
Deferred in v1: the guest account-creation offer (a prefilled register-from-order form) is left as a hidden
template slot with a hand-off TODO, because that registration flow will be owned by the planned
silvershop-suite/auth module. Guests still reach their order via the GuestToken link — only the up-sell is deferred.
Installation
composer require silvershop-suite/order-confirmation
Then run dev/build?flush=all.
Requires silvershop/core ^6 (CMS 6, PHP 8.3 / 8.4). Optional: silvershop-suite/invoicing (adds an invoice-PDF link
to the paid confirmation), silvershop-suite/storefront (ships the styled templates).
The one mandatory config (shipped for you)
SilverStripe 6 defaults Session.cookie_samesite to Strict, which drops the session cookie on the top-level
redirect back from an off-site payment gateway, breaking the cart and the guest order view. This module ships
the fix so every install gets it:
SilverStripe\Control\Session: cookie_samesite: 'Lax'
(Applied except under the test environment, so functional tests keep their own session defaults.)
Templates & theming
The module ships functional, unstyled default templates so it renders correctly headless:
templates/SilverShop/Page/Layout/CheckoutPage_complete.sstemplates/SilverShop/Page/Layout/CheckoutPage_failed.sstemplates/SilverShop/OrderConfirmation/Includes/WhatsNext.sstemplates/SilverShop/OrderConfirmation/Includes/CreateAccountOffer.ss
A theme overrides any of these by shipping the same path. The silvershop-suite/storefront theme supplies the
designed, branded versions (status hero, progress stepper, badge--status-pending, the account-creation offer).
How it hooks in (no core fork)
| Piece | Hook |
|---|---|
| Unified landing | Order::updateLink() (core calls $this->extend('updateLink', $link) in Order::Link()) |
| Reference + guest token at initiation | Order.place_before_payment: true + Order::onBeforeWrite() (token as the order leaves Cart) |
complete / failed / status actions |
ConfirmationControllerExtension on CheckoutPageController |
| Immediate-error routing to the failed page | CheckoutPageController::processPaymentResponse() (the documented PaymentForm hook) |
| Pending re-check | status JSON endpoint + a capped JS poller (and a no-JS <meta refresh> fallback) |
| Pending status badge | Order::getDisplayStatus() (display-only: Unpaid + pending payment → pending) |
Licence
BSD-3-Clause.
Credits
Built and maintained by Remy Vaartjes (remy@xd.nl) — part of the SilverShop-Suite.