Створення власних способів оплати
Цей посібник пояснює, як створити власні інтеграції способів оплати в sCommerce. Ви дізнаєтесь, як розширити базову функціональність платежів для інтеграції з будь-яким платіжним шлюзом або реалізації власної логіки оплати.
Огляд
sCommerce надає гнучку архітектуру платежів, яка дозволяє:
- Інтегруватись з зовнішніми платіжними шлюзами (Stripe, PayPal, LiqPay тощо)
- Реалізовувати власну логіку оплати
- Налаштовувати облікові дані та параметри через панель адміністратора
- Підтримувати кілька режимів роботи (тест/виробництво)
- Обробляти валідацію та обробку платежів
- Відображати власні кнопки та форми оплати
Архітектура способів оплати
Основні компоненти
PaymentMethodInterface- Визначає обов'язкові методи для всіх платіжних інтеграційBasePaymentMethod- Абстрактний базовий клас, що надає загальну функціональністьsPaymentMethod- Модель бази даних для зберігання конфігурації платежівsCheckout- Сервіс для реєстрації та використання способів оплати
Життєвий цикл способу оплати
Реєстрація → Налаштування → Валідація → Обробка → Завершення
Створення способу оплати
Крок 1: Створення класу способу оплати
Всі способи оплати повинні розширювати BasePaymentMethod та реалізовувати PaymentMethodInterface.
Приклад: Проста оплата готівкою
<?php namespace Seiger\sCommerce\Payment;
use Seiger\sCommerce\Payment\BasePaymentMethod;
/**
* Спосіб оплати готівкою
*
* Простий спосіб оплати для платежів готівкою (наприклад, накладений платіж)
*/
class CashPayment extends BasePaymentMethod
{
/**
* Отримати унікальну назву способу оплати.
*
* Використовується як ідентифікатор у системі.
*
* @return string
*/
public function getName(): string
{
return 'cash';
}
/**
* Отримати тип відображення для панелі адміністратора.
*
* Показується в списку способів оплати в панелі адміністратора.
*
* @return string
*/
public function getType(): string
{
$title = __('sCommerce::global.cash');
$title = str_contains($title, '::') ? 'Готівка' : $title;
return "<b>" . $title . "</b> (cash)";
}
/**
* Валідувати дані платежу.
*
* Повернути true, якщо дані платежу валідні.
*
* @param array $data
* @return bool
*/
public function validatePayment(array $data): bool
{
return true; // Валідація не потрібна для готівки
}
/**
* Визначити поля облікових даних для панелі адміні стратора.
*
* Повернути порожній масив, якщо облікові дані не потрібні.
*
* @return array
*/
public function defineCredentials(): array
{
return [];
}
/**
* Визначити поля налаштувань для панелі адміністратора.
*
* Ці поля будуть відображатися в конфігурації способу оплати.
*
* @return array
*/
public function defineSettings(): array
{
return [
'message' => [
'label' => __('sCommerce::global.message'),
'fields' => [
'info' => [
'type' => 'text',
'label' => '',
'name' => 'info',
'value' => $this->getSettings()['info'] ?? '',
'placeholder' => __('sCommerce::global.info_message'),
],
],
],
];
}
/**
* Відобразити HTML кнопки оплати.
*
* Повернути HTML для кнопки оплати, яка буде відображатися на фронтенді.
*
* @param int|string|array $data ID замовлення, ключ замовлення або масив даних замовлення
* @return string
*/
public function payButton(int|string|array $data): string
{
return ''; // Кнопка не потрібна для оплати готівкою
}
/**
* Обробити платіж.
*
* Цей метод викликається під час обробки платежу.
* Поверніть true для успіху, false для помилки або масив з додатковими даними (наприклад, redirect URL).
*
* @param array $data
* @return bool|array
*/
public function processPayment(array $data): bool|array
{
return true; // Припустимо, що платіж успішний
}
}
Крок 2: Розширений спосіб оплати з інтеграцією шлюзу
Приклад: Інтеграція платіжного шлюзу Stripe
<?php namespace YourNamespace\Payment;
use Seiger\sCommerce\Payment\BasePaymentMethod;
use Stripe\StripeClient;
use Seiger\sCommerce\Models\sOrder;
/**
* Інтеграція платіжного шлюзу Stripe
*/
class StripePayment extends BasePaymentMethod
{
private StripeClient $stripe;
public function __construct(string $identifier = '')
{
parent::__construct($identifier);
// Ініціалізувати клієнт Stripe з обліковими даними
$this->stripe = new StripeClient($this->credentials['secret_key'] ?? '');
}
public function getName(): string
{
return 'stripe';
}
public function getType(): string
{
return "<b>Stripe</b> (stripe)";
}
/**
* Визначити облікові дані, які будуть зберігатися безпечно.
* Зазвичай це API ключі та секрети.
*/
public function defineCredentials(): array
{
return [
'api_keys' => [
'label' => __('sCommerce::global.api_keys'),
'fields' => [
'publishable_key' => [
'type' => 'text',
'label' => __('sCommerce::global.publishable_key'),
'name' => 'publishable_key',
'value' => $this->credentials['publishable_key'] ?? '',
'placeholder' => 'pk_test_...',
],
'secret_key' => [
'type' => 'password',
'label' => __('sCommerce::global.secret_key'),
'name' => 'secret_key',
'value' => $this->credentials['secret_key'] ?? '',
'placeholder' => 'sk_test_...',
],
'webhook_secret' => [
'type' => 'password',
'label' => __('sCommerce::global.webhook_secret'),
'name' => 'webhook_secret',
'value' => $this->credentials['webhook_secret'] ?? '',
'placeholder' => 'whsec_...',
],
],
],
];
}
/**
* Визначити налаштування способу оплати.
* Це налаштовувані опції для способу оплати.
*/
public function defineSettings(): array
{
return [
'general' => [
'label' => __('sCommerce::global.general_settings'),
'fields' => [
'capture_method' => [
'type' => 'select',
'label' => __('sCommerce::global.capture_method'),
'name' => 'capture_method',
'value' => $this->getSettings()['capture_method'] ?? 'automatic',
'options' => [
'automatic' => __('sCommerce::global.automatic'),
'manual' => __('sCommerce::global.manual'),
],
],
'save_cards' => [
'type' => 'checkbox',
'label' => __('sCommerce::global.save_cards'),
'name' => 'save_cards',
'value' => $this->getSettings()['save_cards'] ?? 0,
],
'description' => [
'type' => 'textarea',
'label' => __('sCommerce::global.statement_descriptor'),
'name' => 'statement_descriptor',
'value' => $this->getSettings()['statement_descriptor'] ?? '',
'placeholder' => 'Покупка в моєму магазині',
],
],
],
];
}
/**
* Визначити доступні режими для цього способу оплати.
* Загальні режими - 'test' та 'production'.
*/
public function defineAvailableModes(): array
{
return [
'test' => __('sCommerce::global.test_mode'),
'production' => __('sCommerce::global.production_mode'),
];
}
public function validatePayment(array $data): bool
{
// Валідувати обов'язкові поля
if (empty($data['payment_method_id'])) {
return false;
}
if (empty($data['order_id'])) {
return false;
}
return true;
}
public function payButton(int|string|array $data): string
{
// Завантажити дані замовлення
if (is_int($data)) {
$order = sOrder::find($data);
} elseif (is_string($data)) {
$order = sOrder::whereKey($data)->first();
} else {
$order = (object) $data;
}
if (!$order) {
return '';
}
$publishableKey = $this->credentials['publishable_key'] ?? '';
$orderId = $order->id ?? 0;
$amount = $order->total ?? 0;
$currency = $order->currency ?? 'uah';
// Повернути HTML з інтеграцією Stripe Elements
return <<<HTML
<div id="stripe-payment-form-{$orderId}">
<div id="card-element"></div>
<div id="card-errors" role="alert"></div>
<button id="stripe-submit-btn" type="button" class="btn btn-primary">
Сплатити {$amount} {$currency}
</button>
</div>
<script src="https://js.stripe.com/v3/"></script>
<script>
(function() {
const stripe = Stripe('{$publishableKey}');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
document.getElementById('stripe-submit-btn').addEventListener('click', async () => {
const {paymentMethod, error} = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
document.getElementById('card-errors').textContent = error.message;
return;
}
// Надіслати платіж на ваш сервер
const response = await fetch('/checkout/pay/stripe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
payment_method_id: paymentMethod.id,
order_id: {$orderId}
})
});
const result = await response.json();
if (result.success) {
window.location.href = result.redirect_url;
} else {
document.getElementById('card-errors').textContent = result.message;
}
});
})();
</script>
HTML;
}
public function processPayment(array $data): array|bool
{
try {
// Завантажити замовлення
$order = sOrder::find($data['order_id'] ?? 0);
if (!$order) {
return [
'success' => false,
'message' => 'Замовлення не знайдено',
];
}
// Створити Payment Intent
$paymentIntent = $this->stripe->paymentIntents->create([
'amount' => $this->convertToCents($order->total),
'currency' => strtolower($order->currency ?? 'uah'),
'payment_method' => $data['payment_method_id'] ?? '',
'confirm' => true,
'metadata' => [
'order_id' => $order->id,
'customer_email' => $order->customer_email ?? '',
],
'statement_descriptor' => $this->getSettings()['statement_descriptor'] ?? null,
]);
// Перевірити статус платежу
if ($paymentIntent->status === 'succeeded') {
// Оновити статус замовлення
$order->update([
'status' => 'paid',
'transaction_id' => $paymentIntent->id,
'paid_at' => now(),
]);
return [
'success' => true,
'redirect_url' => route('order.success', ['order' => $order->id]),
'transaction_id' => $paymentIntent->id,
];
}
if ($paymentIntent->status === 'requires_action') {
return [
'success' => false,
'requires_action' => true,
'client_secret' => $paymentIntent->client_secret,
];
}
return [
'success' => false,
'message' => 'Платіж не вдався',
];
} catch (\Exception $e) {
\Log::error('Помилка платежу Stripe', [
'error' => $e->getMessage(),
'order_id' => $data['order_id'] ?? null,
]);
return [
'success' => false,
'message' => $e->getMessage(),
];
}
}
/**
* Конвертувати суму в копійки (для Stripe API)
*/
private function convertToCents(float $amount): int
{
return (int) round($amount * 100);
}
}
Крок 3: Реєстрація способу оплати
Способи оплати автоматично виявляються та реєструються, коли вони реалізують PaymentMethodInterface та зберігаються в базі даних.
Реєстрація в Service Provider (опціонально):
<?php namespace YourNamespace\Providers;
use Illuminate\Support\ServiceProvider;
use Seiger\sCommerce\Facades\sCheckout;
use YourNamespace\Payment\StripePayment;
class PaymentServiceProvider extends ServiceProvider
{
public function boot()
{
// Способи оплати автоматично виявляються з бази даних
// Ручна реєстрація не потрібна в більшості випадків
// Для ручної реєстрації:
// sCheckout::registerPaymentMethod(new StripePayment());
}
}
Реєстрація в базі даних:
Способи оплати реєструються в таблиці s_payment_methods. sCommerce автоматично сканує класи платежів та дозволяє активувати їх через панель адміністратора.
Довідка інтерфейсу способу оплати
Обов'язкові методи
getName(): string
Повертає унікальний ідентифікатор способу оплати.
public function getName(): string
{
return 'stripe'; // Має бути унікальним
}
getType(): string
Повертає назву відображення в панелі адміністратора.
public function getType(): string
{
return "<b>Stripe</b> (stripe)";
}
getIdentifier(): string
Повертає комбіновану назву та ідентифікатор (автоматично обробляється BasePaymentMethod).
validatePayment(array $data): bool
Валідує дані платежу перед обробкою.
public function validatePayment(array $data): bool
{
return !empty($data['payment_method_id']) && !empty($data['order_id']);
}
processPayment(array $data): array|bool
Обробляє платіж. Поверніть true/false або масив з деталями.
public function processPayment(array $data): array|bool
{
return [
'success' => true,
'transaction_id' => '...',
'redirect_url' => '...',
];
}
payButton(int|string|array $data): string
Відображає HTML для кнопки/форми оплати.
public function payButton(int|string|array $data): string
{
return '<button>Сплатити зараз</button>';
}
defineCredentials(): array
Визначає поля облікових даних (API ключі, секрети) для налаштування в адмінці.
public function defineCredentials(): array
{
return [
'api_keys' => [
'label' => 'API Ключі',
'fields' => [
'api_key' => [
'type' => 'password',
'label' => 'API Ключ',
'name' => 'api_key',
'value' => $this->credentials['api_key'] ?? '',
],
],
],
];
}
defineSettings(): array
Визначає поля налаштувань для конфігурації в адмінці.
public function defineSettings(): array
{
return [
'options' => [
'label' => 'Опції',
'fields' => [
'auto_capture' => [
'type' => 'checkbox',
'label' => 'Автозахоплення',
'name' => 'auto_capture',
'value' => $this->getSettings()['auto_capture'] ?? 1,
],
],
],
];
}
defineAvailableModes(): array
Визначає доступні режими (тест/виробництво).
public function defineAvailableModes(): array
{
return [
'test' => 'Тестовий режим',
'production' => 'Режим виробництва',
];
}