Payment widget for subscriptions
The widget lets the payer take out a subscription on the partner’s page via SBP, Alfa Pay or SberPay, depending on the project settings and the subscription terms. T-Pay is temporarily disabled.
If the first period is paid at sign-up, the chosen method starts the first payment and saves the binding for later charges. If the first payment is deferred, only SBP is available in this version: the payer confirms a binding without a charge, and the platform later starts the first payment on schedule. Free binding must be enabled for the project and supported by the payment provider.
The success event reports that the initial step completed successfully: the first payment, or a binding without a charge when payment is deferred. It does not replace the server notifications about subscription activation and charges. Payment accounting on the partner side must rely on server notifications, even if the payer closed the page and never received success in the browser.
The widget works in the sandbox too — embed. is published on its domain and the token comes from AuthorizeUrl exactly as in production. The sandbox demo project has SBP, Alfa Pay and SberPay enabled, so the choice screen shows right away; other projects get only SBP by default, and the set is changed on request. In the sandbox a deferred first payment is switched on by a future BillingStartTimeUtc — the widget then offers SBP only. The test environment has no real banks: the Alfa Pay or SberPay button opens a bank app imitation with a confirm button, and the widget carries a simulation button — that is what drives the flow to success. A step-by-step walkthrough — Subscription payment widget in the sandbox.
6.1 How to embed
- Call
CreateSubscriptionfrom your own server. The response carriesAuthorizeUrl, which contains the token — 32 hexadecimal characters. That token is what the widget needs. - Load
embed.from the payment domain and calljs SbpWidget.mount()orSbpWidget.open().
<div id="pay"></div>
<script src="https://subs.digitalspb.com/Cards/Sbp/embed.js"></script>
<script>
const widget = SbpWidget.mount('#pay', {
token: 'TOKEN_FROM_CREATE_SUBSCRIPTION',
brand: {
name: 'CoinKeeper',
logo: 'https://partner.example/logo.png',
primary: '#1756ef',
background: '#ffffff',
textColor: '#182045'
},
onSuccess: function () { location.href = '/thanks'; },
onFail: function () {},
onExpired: function () {},
onError: function (error) { console.error(error.message); }
});
</script>Here TOKEN_FROM_CREATE_SUBSCRIPTION marks the place for the real token from the CreateSubscription response; it is not a valid token value. The request signature and the project password stay on the partner backend only.
mount() inserts an <iframe src=".../Cards/Sbp/widget.html?...">. The widget itself lives inside the iframe — the partner page’s styles and scripts do not affect it and vice versa — and runs same-origin with the payment domain, so its requests carry no CORS. It talks to the parent page over postMessage: iframe height auto-resize plus the events delivered to the mount() callbacks.
Modal window — SbpWidget.open(). The second display mode, with the same capabilities as mount(): the widget appears over the page instead of inside its layout. It takes the same parameters, just without the selector, plus an onClose callback. Only one screen is ever open — a new open closes the previous one. The window closes with the cross, the Esc key or a click on the backdrop; SbpWidget.close() closes it from code.
The SDK passes the token in the token query parameter of the iframe address: /Cards/Sbp/widget.html?token=…. The widget’s requests to the payment methods, payment creation and state check APIs carry that token in an Authorization: Bearer header. Treat the token as sensitive data: do not publish it or include it in open logs. Since it is present in the iframe URL, its absence from web server logs is not guaranteed.
If you embed the widget, do not open AuthorizeUrl. That is a separate payment flow on our page, and it conflicts with the widget over the same token.
Creating the subscription stays on the partner backend — both here and in the button flow below. The project password and the signature never reach the browser.
6.2 mount() and open() parameters
| Parameter | Required | Description |
|---|---|---|
token | Yes | The token from AuthorizeUrl of the CreateSubscription response. Used for the initial step with the chosen method and for the state check |
apiBase | No | Payment domain address. Defaults to the origin embed. was loaded from |
brand | No | Branding object (see below) |
poll | No | false disables automatic state polling; enabled by default |
methods | No | Method filter: an array of codes or a comma-separated string of codes. Codes: sbp, alfapay, sberpay. By default the server set is shown. The filter does not enable methods the server forbids. The order stays the server’s |
webview | No | Boolean. true turns WebView mode on, false turns it off; without the parameter the widget detects the mode automatically. In WebView mode the bank payment opens on a button press, without opening a new tab automatically |
onSuccess | No | The initial step completed successfully: the first payment, or a binding without a charge when payment is deferred. Does not replace server notifications |
onFail | No | The initial step ended in a decline |
onExpired | No | The waiting time for the initial step in the widget has run out. The event itself does not confirm that the payment was cancelled or that nothing was charged |
onError | No | A widget loading or runtime error. Receives an Error object; the message is in error.. Some events also carry error.; its presence is not guaranteed |
onClose | No | open() only: the modal window has been closed. Closing the window does not cancel the subscription or the payment (§6.11) |
The destroy() method on the mount() result removes the postMessage handler and deletes the iframe from the page.
Method filter:
SbpWidget.mount('#pay', {
token: 'TOKEN_FROM_CREATE_SUBSCRIPTION',
methods: ['sbp', 'sberpay'],
onError: function (error) {
console.error(error.message, error.code);
}
});The token value in the example marks the place for the real 32 hex characters from AuthorizeUrl; it is not a working token.
6.3 Appearance — the brand object
| Field | Description | Default |
|---|---|---|
name | Shop or product name | “Оплата” (Payment) |
logo | Logo URL — only http(s):// or data:image/ | none |
primary | Accent colour: hex, a named colour or rgb() / rgba(). Does not apply to the payment method buttons | #1a1f36 |
background | Widget background colour | #ffffff |
textColor | Text colour | Picked automatically from the background colour if the partner has not set textColor |
Values are validated on the widget side: an invalid colour is silently replaced with the default, and a logo with a foreign scheme is not accepted. The parameters arrive through the query string of a public widget page — without that check this would be a markup injection.
If you set your own textColor, you are responsible for the contrast between text and background. The QR code keeps a white background whatever the widget styling. The branded payment method buttons are not recoloured with brand..
Before payment starts, the widget shows the amount including VAT, the subscription period and the legal information configured by the platform.
6.4 Events
| Event | When it arrives |
|---|---|
success | The first payment, or a binding without a charge when payment is deferred, completed successfully. Does not replace server notifications |
fail | The initial step ended in a decline |
expired | The waiting time for the initial step has run out. Does not confirm that the payment was cancelled or that nothing was charged |
error | Widget failure, including a persistent state-polling failure |
resize | The content height changed; handled by the wrapper automatically |
onError receives an Error object. The text is in error.; some events also pass error.. The method_unavailable code means the methods filter contains none of the methods in the available server set. The event is not fatal: the widget keeps working and shows the available server methods. If the iframe failed to load or did not answer the SDK during the handshake, onError is called as well.
Do not create a new subscription automatically on every error: the previous payment operation may already have started and its result may not have arrived yet.
6.5 Ready-made SBP button — renderButton()
A layer on top of mount(): the SDK draws the branded SBP button itself, while creating the subscription stays on the partner side. Handy when you have no checkout of your own.
<div id="pay"></div>
<script src="https://subs.digitalspb.com/Cards/Sbp/embed.js"></script>
<script>
SbpWidget.renderButton('#pay', {
createInvoice: async function () { // on the partner backend: CreateSubscription
const r = await fetch('/api/create-subscription', { method: 'POST' });
return r.json(); // must return { token: '...' }
},
text: 'Subscribe',
brand: { name: 'CoinKeeper', primary: '#1756ef' },
onSuccess: function () { location.href = '/thanks'; },
onFail: function () {},
onExpired: function () {},
onError: function (e) {}
});
</script>| Parameter | Required | Description |
|---|---|---|
createInvoice | Yes | Async callback: creates the subscription on the partner backend and returns an object with a token field |
text | No | Button text; defaults to “Pay” |
brand | No | Widget branding (table above). It does not affect the button itself — that one is branded |
apiBase, methods, webview, poll, onSuccess, onFail, onExpired, onError | No | Same as for mount() |
The result has two methods: reset() puts the button back, and destroy() removes both the button and the widget from the page.
The button looks the same for every partner: dark blue #1D1346 background, the text on the left, the official SBP mark on the right without recolouring. Built to the NSPK brand book.
renderButton() creates a ready-made button that opens the widget once the token has been obtained. It is not a route for a separate button of a particular payment method. For the store’s own buttons that open a chosen method, use §6.12. The name or styling of the ready-made button does not limit the set of methods — methods does that, and with an empty intersection the widget shows the server set and calls onError with the method_unavailable code.
6.6 What the payer sees
Payment methods. The available methods are sbp — SBP, alfapay — Alfa Pay, sberpay — SberPay. T-Pay is temporarily disabled: it is not shown in the widget and is not accepted by the own-buttons route.
The set and order of methods are defined on the platform side and also depend on the subscription terms. When the first period is paid at sign-up, the enabled methods are available. When the first payment is deferred, only SBP is available in this version.
If one method is available, there is no separate choice screen. If there are several, the widget shows their branded buttons. Pressing a button starts the initial operation with the chosen method, and the payer then confirms it in the bank.
The partner can narrow the displayed set with the methods parameter. The widget uses the intersection of the filter with the available server methods and keeps the server order. Code case and surrounding spaces do not matter. The parameter does not enable forbidden methods and does not bypass the temporary T-Pay shutdown.
If the intersection is empty, the widget shows the server set and calls onError with the method_unavailable code. To change the enabled methods or their server order, contact your manager.
Changing the method. The method is chosen before the operation starts. Once it has started, the method cannot be changed for the same subscription: the server returns canChangeMethod=false and the widget hides method switching.
When the widget is opened again with the same token and several methods are available, the choice screen may appear again. To continue, choose the same method. Within the validity period the server reuses the operation already started; choosing a different method for it is rejected.
Another method requires a new subscription with a new token. Before creating it, check the result of the previous operation so as not to take out two subscriptions or make two initial payments.
The store’s own buttons. They use the /Cards/Sbp/pay route: SBP opens the payment screen without a method choice, Alfa Pay and SberPay redirect to the bank’s payment page. The format and limits are in §6.12.
The SBP screen is picked by the widget to suit the device:
| Device | What is shown |
|---|---|
| Desktop browser | A QR code, a countdown and a “waiting for payment” caption |
| Phone or a narrow window (touch input or width under 600 pixels) | A button that opens the banking app. If the bank supplied no deep link, a link to the SBP payment page is shown instead |
After confirming in the banking app the payer returns to the partner page; the widget keeps polling the state and shows the result itself.
6.7 Timings
| What | Value |
|---|---|
Waiting time for the initial operation (QR code, payment link, going to the bank); when it runs out, expired arrives | 20 minutes |
| State polling interval | every 5 seconds, the interval is set by the server |
| Initial step confirmation delay | up to a minute and a half |
The widget timer running out does not mean the bank payment was cancelled.
When the widget is opened again within the validity period, the operation already started with the same method is reused. If the data is saved, the server returns the previous QR code or link.
For a free SBP binding the server may request a new binding once the period has expired. For a paid first payment already started, no repeated authorization is sent — this applies to SBP, Alfa Pay and SberPay alike. A timeout or a missing link does not prove the payment was not made.
Before creating a new subscription, check the result of the previous operation from server data and notifications. Do not call CreateSubscription automatically after an error or when the timer runs out.
Reloading the partner page, if it calls CreateSubscription again, creates a new subscription with a new token — whatever the payment method. The previous operation is not cancelled: the SBP QR stays valid and a payment already started may complete, and their confirmation belongs to the previous subscription rather than the one on screen. During testing this is the main source of confusion.
6.8 First charge and trial period
When the first payment happens is defined by the terms of the created subscription, taking into account BillingStartTimeUtc and the project settings. BillingStartTimeUtc is passed in CreateSubscription in the yyyy-mm-dd hh:mm format, UTC.
| Subscription terms | Available methods | Initial step |
|---|---|---|
| The first period is paid at sign-up | The enabled SBP, Alfa Pay, SberPay | The first payment, saving the binding for later charges |
| The first payment is deferred | Only SBP in this version | A binding without a charge; the platform later starts the first payment on schedule |
A free trial period is the interval before the first scheduled payment, during which only a binding is made at sign-up. Its minimum length may be limited by the project settings.
A deferred payment needs free SBP binding support from the connected payment provider. BillingStartTimeUtc alone does not guarantee that this flow is available for the project. Alfa Pay and SberPay are not offered when the first payment is deferred.
The type of an operation already started does not change when the widget is reopened: opening a subscription with a deferred payment later does not turn the initial free binding into a paid first step.
6.9 Requirements on the partner side
CreateSubscriptionis called from your own server only: the project password and the signature must not reach the browser. The signature formula is in Request signature.- The addresses the partner calls the API from must be on our allowlist.
- The page hosting the widget must be served over
https, otherwise the browser will block the embedding.
6.10 Test procedure
First payment at sign-up — SBP, Alfa Pay or SberPay
- Create a separate subscription with the first period paid at sign-up.
- Open the widget and choose the method under test.
- For SBP, check the QR code or the jump to the bank; for Alfa Pay and SberPay, the redirect to the bank’s payment page.
- Confirm the payment in the bank.
- Check the widget result, the server confirmation of activation, the actual first charge and the saved binding.
- Make sure the first period is counted once and the next payment is scheduled correctly.
- Separately check a recurring charge and subscription cancellation.
Deferred first payment — SBP
- Create a separate subscription with a deferred first payment on a project that supports free binding.
- Make sure Alfa Pay and SberPay are not offered.
- Confirm the binding via SBP.
- Check that nothing was charged at sign-up, the widget result, the activation and the saved binding.
- Check the first charge at the scheduled time.
- Separately check subscription cancellation.
The store’s own buttons
- Create a separate subscription and get its token.
- Open the route from §6.12 with a specific
method. - Make sure SBP opens the payment screen without a method choice, while Alfa Pay and SberPay redirect to the bank.
- Confirm the operation and check its result from server data.
- Check reopening with the same method: it must not create a repeated paid first payment.
Use a separate subscription for each method. Before creating a new one, check the result of the previous operation. Opening a QR code or a bank page does not yet confirm the full payment cycle.
6.11 Closing the widget and cancelling the subscription
Closing the modal window with the cross, Escape or SbpWidget.close(), and removing the widget with destroy(), stop showing the interface. They do not cancel the subscription, do not revoke the binding and do not cancel a payment already sent. The onClose callback only reports that the window was closed.
To cancel a subscription use the server-side cancellation API — the CancelSubscription method in Subscription management methods. For YP, while processing the cancellation the platform revokes the saved payment token or binding at the provider before saving the local Cancelled status. If there is no binding, no external revocation is needed. If the revocation is not confirmed because of a provider or connection error, cancellation processing stops before the status changes.
An API request accepted for processing should not be taken as confirmation that the cancellation is complete on its own: check the outcome by the subscription state and server notifications. Cancelling a subscription is not a refund for payments made earlier.
6.12 The store’s own buttons — redirect route
The partner can place their own payment method buttons. The subscription is created on the partner backend with CreateSubscription; the links carry the token from AuthorizeUrl.
GET https://subs.digitalspb.com/Cards/Sbp/pay?token=TOKEN&method=sbp
GET https://subs.digitalspb.com/Cards/Sbp/pay?token=TOKEN&method=alfapay
GET https://subs.digitalspb.com/Cards/Sbp/pay?token=TOKEN&method=sberpay| Parameter | Required | Description |
|---|---|---|
token | Yes | 32 hexadecimal characters from AuthorizeUrl, without hyphens |
method | Yes | sbp, alfapay or sberpay; the method must be available for this subscription |
The route checks that the method is available and returns HTTP 302:
sbp— to the SBP widget screen with the method preselected, without a choice screen;alfapayandsberpay— to the bank’s HTTP(S) payment page.
For a bank method, calling the route starts the initial payment operation. It is not a diagnostic request and not a request for the list of methods. SBP starts the operation when the target screen loads.
<a href="https://subs.digitalspb.com/Cards/Sbp/pay?token=TOKEN&method=sbp">Pay with SBP</a>
<a href="https://subs.digitalspb.com/Cards/Sbp/pay?token=TOKEN&method=alfapay">Pay with Alfa Pay</a>
<a href="https://subs.digitalspb.com/Cards/Sbp/pay?token=TOKEN&method=sberpay">Pay with SberPay</a>Replace TOKEN in the example with a real token. When building the URL in code, use encodeURIComponent for the parameter values. The token is sensitive data and is present in the URL; its absence from logs is not guaranteed.
Do not open these links automatically, do not prefetch them and do not call them to check availability. The navigation must follow an explicit action by the payer. Get the list of methods with GET /Cards/Sbp/methods and an Authorization: Bearer TOKEN header; that request does not create a payment.
Once the operation has started, changing the method with the same token is forbidden. Reopening a paid operation must not create a new authorization. Another method needs a new token, but first check the outcome of the previous operation.
An invalid token or an unknown or unavailable method results in an HTTP 400 rejection. A payment provider failure may result in HTTP 500. The route is meant for browser navigation and returns an HTML page on rejection, not a JSON contract.
Going to the bank through this route does not create an SDK instance: for Alfa Pay and SberPay do not expect onSuccess / onFail callbacks from SbpWidget. Check the operation result by server notifications and the subscription state. Closing the page does not cancel the payment or the subscription.