Криптоплатежі у WordPress і WooCommerce через API
Готового плагіна Moonvex для WordPress і WooCommerce поки немає — підключення робиться власним кодом через API. Нижче — невеликий власний плагін: спосіб оплати WooCommerce, який створює платіж і веде клієнта на сторінку оплати, і REST-маршрут для webhook із перевіркою підпису.
Покроково
1.Отримайте ключ API і секрет webhook
У кабінеті Moonvex відкрийте «Інтеграція» → «Створити ключ» → тип «Секретний» (sk_live_…). Ключ показується один раз — збережіть його на сервері в MOONVEX_SECRET_KEY. Там само додайте webhook на https-адресу вашого сервера і збережіть секрет підпису в MOONVEX_WEBHOOK_SECRET. У WordPress задайте їх константами у wp-config.php — не в налаштуваннях плагіна в базі.
// wp-config.php — ключі поза базою даних і поза репозиторієм теми define('MOONVEX_SECRET_KEY', 'sk_live_...'); define('MOONVEX_WEBHOOK_SECRET', 'whsec_...');2.Додайте спосіб оплати WooCommerce
Клас WC_Gateway_Moonvex успадковує WC_Payment_Gateway. У process_payment він створює платіж через wp_remote_post і повертає redirect на paymentUrl. Ціна в USD передається як currency: USD з монетою оплати asset: USDT. Номер спроби зберігається в мета-полі замовлення і зростає лише після успішного створення.
<?php /** * Plugin Name: Moonvex Crypto Payments (custom) * Description: Власна інтеграція WooCommerce з API Moonvex */ // wp-content/plugins/moonvex-custom/moonvex-custom.php add_action('plugins_loaded', function () { if (!class_exists('WC_Payment_Gateway')) { return; } class WC_Gateway_Moonvex extends WC_Payment_Gateway { public function __construct() { $this->id = 'moonvex'; $this->method_title = 'Moonvex'; $this->method_description = 'Оплата в USDT через сторінку оплати Moonvex'; $this->has_fields = false; $this->init_form_fields(); $this->init_settings(); $this->title = $this->get_option('title', 'Криптовалюта (USDT)'); add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']); } public function process_payment($order_id) { $order = wc_get_order($order_id); if ($order->get_currency() !== 'USD') { wc_add_notice('Оплата криптовалютою доступна для цін у USD', 'error'); return ['result' => 'failure']; } // номер спроби зростає лише після успішного створення — повтор після таймауту ідемпотентний $attempt = (int) $order->get_meta('_moonvex_attempts') + 1; $res = wp_remote_post('https://api.moonvex.org/v1/payments', [ 'timeout' => 15, 'headers' => [ 'Authorization' => 'Bearer ' . MOONVEX_SECRET_KEY, 'Idempotency-Key' => 'wc-order-' . $order->get_id() . '-' . $attempt, 'Content-Type' => 'application/json', ], 'body' => wp_json_encode([ 'orderId' => (string) $order->get_id(), 'amount' => wc_format_decimal($order->get_total(), 2), 'currency' => 'USD', 'asset' => 'USDT', 'network' => 'TRON', 'customerEmail' => $order->get_billing_email(), ]), ]); $payment = is_wp_error($res) ? null : json_decode(wp_remote_retrieve_body($res), true); if (!$payment || empty($payment['paymentUrl'])) { wc_add_notice('Не вдалося створити платіж, спробуйте ще раз', 'error'); return ['result' => 'failure']; } $order->update_meta_data('_moonvex_attempts', $attempt); $order->update_meta_data('_moonvex_payment_id', $payment['id']); $order->update_status('pending', 'Moonvex: ' . $payment['id']); $order->save(); return ['result' => 'success', 'redirect' => $payment['paymentUrl']]; } } add_filter('woocommerce_payment_gateways', function ($gateways) { $gateways[] = 'WC_Gateway_Moonvex'; return $gateways; }); });3.Надішліть клієнта на paymentUrl
paymentUrl — готова сторінка оплати: точна сума з комісією, адреса, QR-код, офіційний контракт токена й таймер, статус оновлюється сам. Відкривайте її редиректом або в новій вкладці — вбудувати у фрейм не можна. Повернення клієнта на ваш сайт ще не оплата: замовлення позначається оплаченим лише за webhook або статусом із GET /v1/payments/:id. WooCommerce сам перенаправить покупця за redirect із process_payment.
4.Прийміть webhook і перевірте підпис
Moonvex надсилає POST із заголовком Moonvex-Signature: t=<unix-час>,v1=<підпис>. Підпис — HMAC-SHA256 секретом webhook від рядка «t.сире_тіло» у hex. Рахуйте його за байтами тіла до розбору JSON, порівнюйте за сталий час, відхиляйте події, старші за 5 хвилин. Після зміни секрету 24 години в заголовку два v1 — підходить будь-який. Маршрут реєструється через register_rest_route; адреса для кабінету — /wp-json/moonvex/v1/webhook. Сире тіло — $request->get_body().
// moonvex-custom.php (продовження) // URL для кабінету Moonvex: https://shop.example.com/wp-json/moonvex/v1/webhook add_action('rest_api_init', function () { register_rest_route('moonvex/v1', '/webhook', [ 'methods' => 'POST', 'permission_callback' => '__return_true', // доступ перевіряється підписом 'callback' => function (WP_REST_Request $request) { $raw = $request->get_body(); // сире тіло if (!moonvex_verify($raw, (string) $request->get_header('Moonvex-Signature'), MOONVEX_WEBHOOK_SECRET)) { return new WP_REST_Response('bad signature', 400); } moonvex_handle_event(json_decode($raw, true)); return new WP_REST_Response('ok', 200); }, ]); }); function moonvex_verify(string $raw, string $header, string $secret, int $tolerance = 300): bool { $t = null; $sigs = []; foreach (explode(',', $header) as $part) { [$k, $v] = array_pad(explode('=', $part, 2), 2, ''); if ($k === 't' && ctype_digit($v)) { $t = (int) $v; } if ($k === 'v1') { $sigs[] = $v; } } if (!$t || abs(time() - $t) > $tolerance) { return false; } $expected = hash_hmac('sha256', $t . '.' . $raw, $secret); foreach ($sigs as $s) { if (hash_equals($expected, $s)) { return true; } } return false; }5.Позначте замовлення оплаченим рівно один раз
Під час повторної доставки id події той самий, тому запишіть його в таблицю з унікальним ключем в одній транзакції з оновленням замовлення: дубль нічого не змінить. Видавайте товар за payment.confirmed і payment.overpaid, а не за payment.detected. У прикладі id події зберігається в мета-полі замовлення, а payment_complete() викликається лише якщо !$order->is_paid(). За payment.expired замовлення переходить у статус «Не вдалося».
// moonvex-custom.php (продовження) function moonvex_handle_event(array $event): void { $order = wc_get_order((int) ($event['data']['orderId'] ?? 0)); if (!$order || $order->get_payment_method() !== 'moonvex') { return; } $seen = '_moonvex_event_' . $event['id']; if ($order->get_meta($seen)) { return; // цю подію вже оброблено } switch ($event['type']) { case 'payment.confirmed': case 'payment.overpaid': if (!$order->is_paid()) { $order->payment_complete($event['data']['id']); // pay_… — номер транзакції замовлення } break; case 'payment.expired': if (!$order->is_paid()) { $order->update_status('failed', 'Moonvex: payment expired'); } break; } $order->update_meta_data($seen, time()); $order->save(); }6.Перевірте інтеграцію
Тестової мережі в Moonvex немає — усе працює в основних мережах. Кнопка «Надіслати тест» у розділі «Інтеграція» надішле подію webhook.test — так перевіряється підпис. Потім оплатіть невеликий рахунок, наприклад 1 USDT у TRON: це звичайний платіж, гроші надійдуть на баланс. Журнал доставок із кодами відповідей і кнопкою «Повторити» — там само. Увімкніть спосіб оплати в WooCommerce → Налаштування → Платежі.
Підводні камені
- Готового плагіна Moonvex для WooCommerce поки немає — код із гайду ваш, оновлюйте його разом із магазином.
- Блокове оформлення замовлення (Checkout block) показує лише шлюзи з інтеграцією для блоків. Для коду з гайду використовуйте класичний шорткод [woocommerce_checkout] або додайте реєстрацію через AbstractPaymentMethodType.
- Валюта магазину має бути USD (або монета): Moonvex приймає ціну в USD або в криптовалюті, інші фіатні валюти треба перерахувати самостійно.
- Плагіни безпеки та кешування іноді закривають /wp-json/ — відкрийте маршрут moonvex/v1/webhook для POST.