Skip to main content

Hooks

Hooks are passed at creation (and again at restore, since they hold code). Each is an optional @Sendable async throws closure.

let checkout = try PurseHeadlessCheckout(
widgetData: widgetData,
hooks: PurseCheckoutHooks(
onBeforeValidate: { splits in try await backend.reserveStock(splits) }, // throwing aborts the submit
onAfterValidate: { try await backend.notifyValidateDone() }, // errors swallowed, reported
onRedirection: { action in // true = you open it
guard case let .get(url, _) = action, let url = URL(string: url) else { return false }
await openInSafariViewController(url)
return true
}
)
)
HookWhenIf it throws
onBeforeValidate([PaymentSplit])After the item payloads are built, right before POST /validate, Apple Pay and PayPal included. Skipped only when you call submitPayment(skipOnBeforeValidate: true) yourselfThe submit is aborted before anything is sent: PurseCheckoutError.hookFailed with ON_BEFORE_VALIDATE_HOOK_FAILED, split and collected data untouched, sessionState back to .ready
onAfterValidate()After /validate answers, before each plugin's postValidation and before any redirectSwallowed, reported as a HEADLESS_CHECKOUT_CLIENT_HOOK_ERROR monitoring event
onRedirection(RedirectAction)presentRedirectIfNeeded(for:using:) resolved a redirect. Return true to open it yourself, false to let the presenter open itNothing is opened: hookFailed with POST_VALIDATE_FAILED is thrown out of presentRedirectIfNeeded, and reaches a PaymentElementListener as .failed when the element drove the submit

onRedirection runs only where a redirect is actually presented: pass a presenter as paymentElement(for:listener:redirect:), or call presentRedirectIfNeeded(for:using:) yourself. An element built without one reports .completed with the /validate result and never consults the hook, even when the answer asked for a redirect.

Take a redirect over​

RedirectAction has three cases: .get(url:returnURLPrefix:) opens a URL, .postForm(url:formDataURLEncoded:returnURLPrefix:) posts fields to a URL, and .postHTML(html:returnURLPrefix:) carries a PSP-provided document that submits itself. SFSafariViewController and ASWebAuthenticationSession can only handle .get; return false for the other two so the SDK's web view handles them.

When you return true, confirming the outcome on return is yours: nothing reaches the listener as returnedFromRedirect, and an element-driven submit reports .completed with the /validate result it already had. When the shopper comes back, show the payment as pending and confirm its final status on your backend, from webhooks or Get a client session.

The default is safer

WebAuthRedirectPresenter sends a .get to ASWebAuthenticationSession (a real browser, which ACS providers that refuse embedded web views still render) and falls back to a WKWebView for the POST cases and whenever the browser cannot observe the return. Its RedirectPolicy refuses anything that is not https, optionally narrows the chain to hosts you list, and recognizes your return destination as a universal link or a custom scheme. Take a redirect over only when a PSP page will not work inside it.