Embedded checkout
Embed the experience, not the risk.
Create the checkout session on your server, then mount the returned client secret with PerionPayments.js. Your page keeps order context while PerionPayments renders the payment experience.
<div id="perion-dropin"></div>
<script src="https://js.perionpayments.com/v1.0.7/perion.js"
integrity="sha384-RFfFMBEiCDyN107kYU9dwGyDxVeedsE0pWo0doQBIadHgnVu2FUrq7QKd+Xf388K"
crossorigin="anonymous"></script>
<script>
const dropin = PerionPayments(checkoutSession.client_secret)
.dropin({ mount: "#perion-dropin", locale: "en" });
dropin.on("change", ({ status }) => showProgress(status));
dropin.on("complete", () => checkOrderOnYourServer());
// Browser events are for navigation only. Confirm the payment on your server.
</script>Pin the exact version with its integrity value, as above.
Version 1.0.6 stays published and supported, unchanged (sha384-GU4Kn1YbHSCW13ScKLeE79aUiuLbaFlNo11UFCqV0Qv07FEY/7vMrF7Ur2MW48//). To move to 1.0.7, change src and integrity together.
Use the module build in projects with native ES modules
<div id="perion-dropin"></div>
<script type="module">
import PerionPayments from "https://js.perionpayments.com/v1.0.7/perion.es.js";
const dropin = PerionPayments(checkoutSession.client_secret)
.dropin({ mount: "#perion-dropin", locale: "en" });
dropin.on("complete", () => checkOrderOnYourServer());
</script>Card methods leave your page.
When the payer chooses Visa & Mastercard or ZimSwitch, or the checkout is limited to one of them, PerionPayments.js 1.0.6 and later move your whole page to the card acquirer's secure page, which PerionPayments opens for you. No drop-in event fires first, so keep the order on your server. The payer comes back to your return_url, paid or not, or to your cancel_url if they leave without paying (your return_url when you set no cancel_url). Make that page safe to visit more than once. Earlier SDK versions show a Continue button in the frame instead.
“Complete” still means “check on your server.”
Fulfill from a signed webhook or a server-side read. The browser event should only take your customer to a return page.
Drop-in events
Listen with dropin.on(event, listener) and stop with dropin.off. Every event is a hint for your page; none of them proves payment.
| Event | When | What you do |
|---|---|---|
ready{ session_id } | The secure frame connected and checkout is open. | Show checkout as ready. |
change{ status, processing_reason } | The payment status changed, for example to processing. | Show progress. Never fulfill from it. |
requires_action{ action_type } | The payer must act, for example approve_on_device. | Keep the drop-in mounted while they approve. |
complete{ session_id } | The frame saw the payment succeed. | Take the payer to your return page and check the order on your server. |
error{ code, message, recoverable } | The frame could not load or connect. | Keep the same checkout session; see the recovery table below. |
SDK lifecycle and recovery
The SDK reserves space for checkout and connects to the secure frame. Treat checkout as open only after the ready event. Calling destroy() removes the frame but never cancels a payment that has already started, so reopen the same checkout session instead of creating another.
Configuration mistakes are thrown by PerionPayments() or dropin() as an error with a code, and nothing is mounted. If the frame never becomes ready, your error callback receives code, message, and recoverable, and a panel takes the frame's place. That frame never showed a payment form, so from 1.0.7 the panel tells the payer no payment was started from it, and that a payment they approved earlier isn't affected. Versions 1.0.4 to 1.0.6 say the connection doesn't tell them whether they were charged.
| Condition | What you do | What the payer sees |
|---|---|---|
Frame did not become readyframe_timeoutframe_load_failed | Keep the same checkout session. Destroy and remount it, or let the payer reconnect; never create a new payment. | “The payment form didn't load. No payment was started from it,” with Try again, which reconnects the same checkout. Version 1.0.6: “This payment could not be loaded,” with Reconnect original checkout. |
Protocol mismatchprotocol_version_mismatch | Load the current pinned PerionPayments.js release. | “The payment form can't open on this page. No payment was started from it,” with Reload page. Version 1.0.6: “This payment cannot be opened right now,” with Reload store page. |
Invalid client secret or origininvalid_client_secretinvalid_checkout_origin | Fix your configuration: pass the session's client_secret exactly as your server received it, and leave checkoutOrigin unset or make it an HTTPS origin. | Nothing is mounted. Show your own message that payment is unavailable. |
Duplicate or non-empty mountsession_already_mountedmount_not_empty | Mount each checkout session once, into its own empty element. | No duplicate checkout appears. |
Missing mountmount_not_found | Create the mount element before you call dropin(). | Nothing is mounted. Keep your page's own way back to the order. |
Unsupported localeunsupported_locale | Use the en locale. Other languages wait for reviewed payment copy. | Nothing is mounted, rather than unreviewed payment copy. |