Криптоплатежи в 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.