Справочник полей подписки и пользователя
Сводка типичных ошибок интеграции Merchant API и правильное использование полей. Основано на опыте production-интеграций.
Идентификаторы подписки
В ответах API у подписки два разных поля:
| Поле | Тип | Назначение |
|---|---|---|
subscriptionId | string (sub20260703…) | Используйте в API — update_submit, cancel, detail и т.д. |
id | number | Внутренний числовой id записи в БД; не передавайте как subscriptionId |
const { subscription } = await merchant.getSubscriptionUserSubscriptionDetail({
externalUserId: 'myapp-team-42',
productId: 0,
})
// ✓ правильно
const subscriptionId = subscription!.subscriptionId!
// ✗ ошибка «sub not found»
// await merchant.subscriptionUpdateSubmit({ subscriptionId: String(subscription!.id), ... })productId и planId
planId— id тарифного плана (например,251= free,252= paid).productId— id продукта в каталоге. Системный продукт по умолчанию:0.
productId: 0 — валидное значение. Не путайте с planId.
// ✓ для quota/resolve и user_subscription_detail с дефолтным продуктом
await merchant.getQuotaResolve({ productId: 0, planId: 251 })
// ✗ в JS/TS: if (!productId) пропустит 0
if (productId != null) { /* … */ }returnUrl и cancelUrl
Поля помечены в OpenAPI как optional, но условно обязательны, когда операция создаёт счёт на оплату.
| Операция | Когда нужен returnUrl |
|---|---|
subscription/create_submit | totalAmount > 0 (платный план) |
subscription/update_submit | Апгрейд с немедленным эффектом и totalAmount > 0 в preview |
Рекомендуемый поток:
- Вызовите
subscription/update_preview. - Если
totalAmount > 0— передайтеreturnUrl(иcancelUrl) вupdate_submit. - При
paid === falseперенаправьте пользователя наlinkиз ответа. - После возврата обновите подписку и обработайте вебхуки.
Ответ при необходимости оплаты:
{
"paid": false,
"link": "https://payment-gateway.example/…",
"subscriptionPendingUpdate": { … }
}Если returnUrl не передан при обязательности, API может вернуть общую ошибку Server Error с requestId — сверяйте preview заранее.
В SDK есть проверка до вызова API:
import { assertReturnUrlForPaidOperation } from '@wilix/billbill-client-js'
assertReturnUrlForPaidOperation(preview.totalAmount, { returnUrl })gatewayId при создании подписки
gatewayId обязателен для subscription/create_submit, в том числе для бесплатных (0 ₽) планов. Без него — BaseGateway invalid.
Укажите id активного шлюза из админки (раздел «Платёжные шлюзы») или из merchant.get() / списка gateways.
Email и externalUserId
Один аккаунт на организацию
Типичный B2B/SaaS-паттерн:
externalUserId— стабильный ключ (myapp-team-{teamId}) для API и вебхуков.email— контакт для счетов и чеков (email владельца команды или billing-контакт).
BillBill требует уникальный email на учётную запись пользователя.
Смена billing-email
| Метод | Меняет email в BillBill |
|---|---|
userChangeEmail | Да — используйте для обновления контакта для счетов |
userUpdate | Нет — не меняет сохранённый email для биллинга |
await merchant.userChangeEmail({
externalUserId: 'myapp-team-42',
newEmail: 'new-billing@customer.example.com',
})При нескольких командах у одного человека допустимы plus-адреса (owner+team42@example.com).
Матрица сценариев (create / update)
| Endpoint | Сценарий | Ключевые поля |
|---|---|---|
subscription/create_submit | Free (totalAmount = 0) | planId, userId или email+externalUserId, gatewayId |
subscription/create_submit | Paid | + returnUrl, рекомендуется cancelUrl |
subscription/update_submit | Downgrade / без доплаты | subscriptionId, newPlanId, quantity |
subscription/update_submit | Upgrade с оплатой | + returnUrl, рекомендуется cancelUrl |