Embed the SubscriptionFlow payment widget directly on your own page, next to your own form and your own submit button. Your form collects the customer details; our widget handles only the payment. When your customer clicks your button, you push the form data into the widget and trigger payment — and you get back a success/error result so your own page decides what happens next.
This replaces the old two-page flow (collect details on page 1 → redirect to the checkout with query params on page 2). Everything now happens on one page.
Works today for Hosted Payment Page (HPP) checkout links.
1. How it works
- You load a tiny script (
embed.js) and mount the widget into a<div>. - The widget is your normal checkout link, loaded in payment-only mode (no Subscribe button of ours is shown).
- On your button click: you call
widget.setFields(...)thenawait widget.submit(). You receive a result:
{ status: 'success' | 'error', … }. Card data is entered inside our iframe and never touches your page — your PCI scope is unchanged.
2. Quick start
<!-- 1. A container for the widget -->
<div id="sf-checkout"></div>
<!-- 2. Your own form + your own button -->
<form id="my-form">
<input name="first_name" value="Ada">
<input name="email" value="ada@example.com">
<!-- … your fields … -->
<button type="submit">Pay & Subscribe</button>
</form>
<!-- 3. The SDK (served from your SubscriptionFlow domain) -->
<script src="https://YOUR-SF-DOMAIN/modules/paymentsflow/js/embed.js"></script>
<script>
// Mount the widget. Use your normal HPP checkout link.
const widget = SubscriptionFlow.mount('#sf-checkout', {
checkoutUrl: 'https://YOUR-SF-DOMAIN/en/hosted-page/subscribe/{PLAN}/product/{PRODUCT}'
});
document.getElementById('my-form').addEventListener('submit', async (e) => {
e.preventDefault(); // stop your form from submitting yet
const f = e.target;
// Push your collected data into the widget (see field reference below).
widget.setFields({
ai_firstName: f.first_name.value,
ai_email: f.email.value,
ai_billing_country: 'GB',
// … any other fields …
});
// Trigger payment and wait for the outcome.
const result = await widget.submit();
if (result.status === 'success') {
// Payment done. Run your automation / redirect / submit your form.
f.submit(); // or window.location = '/thank-you'
} else {
// Payment failed — keep the customer on the page to fix & retry.
showMyError(result.message);
}
});
</script>The SDK automatically appends payment_widget=true&embedded=true to your checkoutUrl, so you pass your plain checkout link.
3. JavaScript API
SubscriptionFlow.mount(selector, options) → widget
Option | Type | Description |
|---|---|---|
| checkoutUrl | string (required) | Your HPP checkout link. |
Returns a widget you interact with below. |
widget.setFields(fields) → widget
Pushes customer/cart data into the widget. Accepts the flat field keys in §4.
- Send the complete set of fields you want applied. Calls are cumulative (the widget keeps the latest value of every key you've sent), so it's safe to call more than once, but the simplest pattern is one call right before
submit().
widget.submit() → Promise<Result>
Triggers the payment using whatever the customer entered in the widget (card, etc.), with the fields you pushed. Resolves with a Result (below) on success or error.
- Calling
submit()again while one is in flight returns the same promise (no double-charge). After a result resolves, you can call it again to retry.
widget.on(event, callback) / widget.off(event, callback)
| Event | Fires when | Callback arg |
|---|---|---|
ready | Widget mounted & handshake done | — |
success | Payment completed | Result |
error | Payment failed / blocked | Result |
fieldsApplied | A setFields was applied | — |
resize | Widget height changed | { height } |
on(...) is an alternative to awaiting submit() — use whichever you prefer.
The Result object
{
status: 'success' | 'error',
message: 'human-readable message',
code: 'HPP_PAYMENT_SUCCESS' | 'HPP_FAILURE' | 'client_validation',
redirectUrl: 'https://your-domain.com/thank-you',
ids: {
subscription: 'SUBSCRIPTION_ID',
invoice: 'INVOICE_ID'
}
}4. Field reference (setFields keys)
These are the same keys used by Hosted Payment Page (HPP) query parameters. If you are already generating checkout URLs with parameters such as ?ai_firstName=John, you can reuse the exact same field names with widget.setFields().
Customer & Billing Fields
| Field |
|---|
ai_firstName |
ai_lastName |
ai_email |
ai_phone |
ai_notes |
ai_billing_name |
ai_billing_email |
ai_billing_address1 |
ai_billing_address2 |
ai_billing_address3 |
ai_billing_city |
ai_billing_state |
ai_billing_country |
ai_billing_zip |
ai_custom_fields_{Field} |
Shipping Fields
| Field |
|---|
ai_shipping_name |
ai_shipping_email |
ai_shipping_address1 |
ai_shipping_address2 |
ai_shipping_address3 |
ai_shipping_city |
ai_shipping_state |
ai_shipping_country |
ai_shipping_zip |
shipping_amount |
shipping_method |
Gift Subscription Fields
| Field |
|---|
gift_firstName |
gift_lastName |
gift_email |
gift_company |
gift_personalPhone |
gift_workPhone |
gift_addrLine1 |
gift_addrLine2 |
gift_addrLine3 |
gift_city |
gift_state |
gift_country |
gift_zip |
gift_custom_fields_{Field} |
Cart & Pricing Fields
These fields recalculate pricing and totals.
| Field |
|---|
additional_plans |
bundle_items |
charge_name |
charge_desc |
charge_prices |
currency |
coupon_code |
Subscription & Meta Fields
| Field |
|---|
sc_start_date |
sc_autoRenew |
assigned_to |
created_by |
auto_check_policy |
hide_currency |
Reserved Fields
The following parameters are automatically managed by the SDK and must not be supplied through setFields().
| Field |
|---|
payment_widget |
embedded |
5. Success & error handling
- Success — the widget completes the payment (creating the customer, subscription, invoice, etc.) and emits
success. You decide what happens next: submit your form, run your automation, or navigate (you may useresult.redirectUrlto send the customer to our thank-you page, but you are not required to). - Error — the widget keeps the form visible so the customer can correct the problem and try again. Card-specific errors (declined, invalid card) are shown inside the widget; you also receive
result.messageto display in your own UI. Do not submit your form on error.
const result = await widget.submit();
if (result.status === 'success') {
// Payment completed successfully.
myForm.submit();
} else {
// Payment failed.
setError(result.message);
}6. Showing a loading state
A submission can take a few seconds (gateway confirmation, 3-D Secure, etc.). submit() does not resolve until the outcome is known, so show your own spinner around the await:
setBusy(true); const result = await widget.submit(); setBusy(false);
7. Notes & limitations
- Backward compatible. Existing links that use only
?payment_widget=true(with our Subscribe button, no SDK) behave exactly as before — this feature is purely additive. - Card / 3-D Secure. Card entry and any 3-D Secure step happen inside our iframe, driven by the customer's click on your button. This works across your domain and ours.
- Ad blockers. Some blockers stop Stripe's telemetry beacon (
r.stripe.com/b). That's analytics only and does not affect payments. - One widget per page. Mount a single widget instance per checkout.
8. Full example
<div id="sf-checkout"></div>
<form id="my-form">
<input name="first_name" placeholder="First name" required>
<input name="last_name" placeholder="Last name" required>
<input name="email" placeholder="Email" required>
<input name="country" placeholder="Country (e.g. GB)" required>
<button type="submit" id="pay-btn">Pay & Subscribe</button>
<p id="pay-error" style="color:#b91c1c"></p>
</form>
<script src="https://YOUR-SF-DOMAIN/modules/paymentsflow/js/embed.js"></script>
<script>
const widget = SubscriptionFlow.mount('#sf-checkout', {
checkoutUrl: 'https://YOUR-SF-DOMAIN/en/hosted-page/subscribe/PLAN_ID/product/PRODUCT_ID'
});
widget.on('ready', () => console.log('widget ready'));
const form = document.getElementById('my-form');
const btn = document.getElementById('pay-btn');
const err = document.getElementById('pay-error');
form.addEventListener('submit', async (e) => {
e.preventDefault();
err.textContent = '';
btn.disabled = true;
btn.textContent = 'Processing…';
widget.setFields({
ai_firstName: form.first_name.value,
ai_lastName: form.last_name.value,
ai_email: form.email.value,
ai_billing_country: form.country.value,
});
const result = await widget.submit();
btn.disabled = false;
btn.textContent = 'Pay & Subscribe';
if (result.status === 'success') {
window.location = '/thank-you'; // your success handling
} else {
err.textContent = result.message || 'Payment failed. Please check your details.';
}
});
</script>
Comments
0 comments
Please sign in to leave a comment.