Add checkout to the channel merchant experience
The channel builds and operates the checkout page for its merchant. The merchant does not mount anything and never receives the channel key. The channel’s server creates the scoped session, and the page receives only its session identifier.
Mount checkout inside the channel-managed merchant page on an allowed origin, initialize it on every page load, and treat approval, cancellation, and error callbacks as interface signals. Keep merchant attribution with the session and reconcile final state on the server. Look up the checked browser contract in the shared maxanapay.js reference.
Point the page at the same base your server calls: the base URL issued with
your credential, the one step one
had you store from the activation screen. Pass it as apiBase. The one
shorthand is environment, which resolves the two published hosts on
Environments and nothing else: when your credential’s base is
the live host, environment: 'live' names it and apiBase is not needed. Every
other base — including the one a test credential is issued on, which is not a
host environment can name — is reached only through apiBase, so that is the
option to reach for while you build against a test credential. apiBase takes
precedence when both are supplied. Supplying neither throws config_error
before anything mounts, and a mount pointed at the wrong deployment fails on the
session instead of on the option, because the session it is looking for does not
exist there.
Mount the session your server created, and post the capture identifier back to your own server
// The channel's own page mounts the session its server created. The page
// receives the session identifier and nothing else; the channel key stays on
// the server.
const channelCheckoutScript = document.createElement('script');
channelCheckoutScript.src = 'https://js.maxanapay.com/v1/maxanapay.js';
channelCheckoutScript.addEventListener('load', () => {
const checkout = MaxanaPay.checkout({
sessionId: 'cs_00000000000000000000000000000000',
container: '#maxana-checkout',
// Which deployment this page calls: the base URL issued with your
// credential — the one on your activation screen, the same base your server
// calls. Substitute yours below. When that base is the published live host,
// `environment: 'live'` is the shorthand and apiBase is not needed;
// otherwise pass apiBase, which is the case while you build against a test
// credential.
apiBase: 'https://staging.api.maxanapay.com',
onApproved(payment) {
// payment.captureId is the value POST /api/payments/captures/{capture_id}/refund takes.
// It is null until payment.status is 'captured'.
void fetch('/internal/orders/order_1001/paid', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId: payment.orderId,
captureId: payment.captureId,
status: payment.status,
source: payment.source,
}),
});
},
onError(error) {
console.error(error.code, error.retryable);
},
});
window.addEventListener('pagehide', () => checkout.unmount());
});
document.head.append(channelCheckoutScript);The page above holds the session identifier and no credential. It has to be
served from one of the allowed_origins the session was created with, or the
SDK reports origin_not_allowed instead of mounting.
Where the capture identifier comes from
Section titled “Where the capture identifier comes from”onApproved receives one object, and it is the only place the browser learns
the identifier a refund needs. payment.captureId is that value: it is the
same provider_capture_id the transaction carries and the same capture_id
that POST /api/payments/captures/{capture_id}/refund takes in its path. It is
null until payment.status is captured, so read the status before storing
it.
The complete field list is on the shared maxanapay.js
reference. Treat what arrives there as
interface feedback and confirm the payment from payment.captured and the
transaction read; the callback tells you what to show the buyer, not what to
bill.
When the buyer approved and no capture followed
Section titled “When the buyer approved and no capture followed”An approved order that was never captured does not reach you through
onApproved. The SDK attempts the capture itself once the buyer authorizes, and
when PayPal returns the order still merely approved it reports this as an
onError with code capture_pending and stops before onApproved — so
onApproved only ever runs for a payment that was actually captured. The buyer
authorized at PayPal and the money was not taken; nothing is owed to you yet, so
do not fulfil on capture_pending.
The channel recovers that payment from its own server.
POST /api/payments/orders/{order_id}/capture accepts a channel key paired with
X-Merchant-Id naming the merchant the order belongs to, and captures the
approved order for that merchant and for no other. The same operation still
accepts a merchant’s own key, and still accepts no credential at all — the
buyer page’s form, which on top of the checkout headers needs the browser’s own
Origin header, without which it answers 403 and Browser Origin header is required for public checkout-session requests. The operation
reference lists Origin as optional because a call
carrying a merchant or channel key needs none; the credential-less call does.
Recovery is safe to retry. Re-capturing an order that is already captured
returns the existing capture — 200, no second charge — and a terminal order,
one that has failed, refunded, or voided, is refused 409. Because the channel
call is authenticated, a checkout session that has since expired does not block
it: session expiry refuses only the anonymous buyer path. Confirm what happened
from payment.captured and from GET /api/transactions for that merchant, and
treat the SDK callback as interface feedback rather than as the record. Letting
the buyer approve again also still works — a payment that was not taken does not
close the session — but the server-side capture is the recovery that does not
depend on the buyer coming back.
Current limits
Section titled “Current limits”The published maxanapay.js major channel mounts the PayPal wallet rail, and that is the whole of the channel integration. The channel builds and operates that checkout for its merchant; the merchant mounts nothing and holds no credential of its own.
The checked v1 SDK contract excludes card fields, 3-D Secure orchestration, Apple Pay, Google Pay, Pay Later messaging, App Switch, SDK-controlled shipping and billing-address collection, save-payment-method sessions, and sessions routed to Gr4vy or NMI. Pay Later buttons are separate from Pay Later messaging: an eligible button can appear, while promotional messaging remains excluded.
Continue to Refunds, statements, and fees.