Интеграция Paddle для SaaS-подписок
Paddle — платёжная платформа, которая выступает Merchant of Record (MoR): берёт на себя налоги (VAT, GST, sales tax), возвраты и комплаенс. Для SaaS-компаний вне США и ЕС это часто единственный способ легально продавать в 200+ странах без открытия юридического лица в каждой. Paddle сам рассчитывает и перечисляет местные налоги — разработчику не нужно думать об этом вообще.
Paddle Billing (v2) — более новый продукт, отличается от Paddle Classic API. Интеграция занимает 3–5 рабочих дней.
Paddle Billing vs Classic
Paddle Classic использует overlay checkout — JavaScript widget встраивается на страницу. Paddle Billing использует собственный checkout URL или inline checkout через iframe. Новые проекты стоит начинать с Billing, если Classic не нужен для совместимости.
Создание продуктов и цен
// Paddle Billing REST API
const product = await paddle.products.create({
name: 'Pro Plan',
tax_category: 'saas',
});
const price = await paddle.prices.create({
product_id: product.id,
description: 'Pro Monthly',
billing_cycle: { interval: 'month', frequency: 1 },
trial_period: { interval: 'day', frequency: 14 },
unit_price: { amount: '2900', currency_code: 'USD' },
});
Checkout на фронтенде
Paddle Billing предоставляет @paddle/paddle-js SDK:
import { initializePaddle } from '@paddle/paddle-js';
const paddle = await initializePaddle({
environment: 'production', // или 'sandbox'
token: 'live_...',
eventCallback(event) {
if (event.name === 'checkout.completed') {
const { transaction_id, customer } = event.data;
// Перенаправить или обновить UI
window.location.href = `/dashboard?checkout=success&txn=${transaction_id}`;
}
},
});
// Открыть checkout
paddle.Checkout.open({
items: [{ priceId: 'pri_...', quantity: 1 }],
customer: { email: currentUser.email },
customData: { user_id: currentUser.id },
successUrl: `${window.location.origin}/dashboard`,
});
Webhook-и и синхронизация
Paddle отправляет события подписки. Подписи верифицируются через ECDSA с публичным ключом из dashboard:
use Paddle\Webhooks\Verify;
public function handleWebhook(Request $request): Response
{
$verified = Verify::signature(
$request->getContent(),
$request->header('Paddle-Signature'),
config('services.paddle.webhook_secret')
);
if (!$verified) {
return response('Forbidden', 403);
}
$payload = $request->json()->all();
match ($payload['event_type']) {
'subscription.created' => $this->onSubscriptionCreated($payload['data']),
'subscription.updated' => $this->onSubscriptionUpdated($payload['data']),
'subscription.cancelled' => $this->onSubscriptionCancelled($payload['data']),
'transaction.completed' => $this->onTransactionCompleted($payload['data']),
'transaction.payment_failed' => $this->onPaymentFailed($payload['data']),
default => null,
};
return response('OK', 200);
}
private function onSubscriptionCreated(array $data): void
{
$userId = $data['custom_data']['user_id'];
$user = User::findOrFail($userId);
$user->update([
'paddle_subscription_id' => $data['id'],
'paddle_customer_id' => $data['customer_id'],
'subscription_status' => $data['status'],
'plan' => $data['items'][0]['price']['id'],
'trial_ends_at' => $data['current_billing_period']['starts_at'] ?? null,
'renews_at' => $data['next_billed_at'],
]);
}
Апгрейд и отмена
Paddle Billing позволяет обновлять подписку через API. Prorated billing рассчитывается автоматически:
// Апгрейд на новый plan
$paddleClient->subscriptions()->update($subscription_id, [
'items' => [[
'price_id' => 'pri_new_plan',
'quantity' => 1,
]],
'proration_billing_mode' => 'prorated_immediately',
]);
// Отмена в конце периода
$paddleClient->subscriptions()->cancel($subscription_id, [
'effective_from' => 'next_billing_period',
]);
Customer Portal
В отличие от Stripe, Paddle не имеет готового Customer Portal. Управление подпиской реализуется через Paddle Update Payment Method URL и собственные API вызовы. Для смены способа оплаты:
$updateUrl = $paddleClient->subscriptions()->getPaymentMethodUpdateTransaction($subscription_id);
// Редирект на $updateUrl->url
Налоги и валюты
Paddle автоматически определяет страну покупателя по IP и добавляет местный налог (НДС в ЕС, GST в Австралии и т.д.). Итоговая цена, которую видит пользователь, уже включает налог. Paddle перечисляет его в местные налоговые органы — никаких дополнительных действий со стороны разработчика не требуется.
Paddle поддерживает dynamic pricing: один и тот же product может отображаться в разных валютах в зависимости от страны пользователя.







