Криптоплатежи в WooCommerce
Как добавить крипту как способ оплаты в WooCommerce: собственный класс шлюза, создание платежа Qvard, webhook, завершающий заказ, и подводные камни WooCommerce.

У WooCommerce есть чистая точка расширения для способов оплаты, а Qvard нужны от неё всего две вещи: запрос, создающий платёж, когда клиент оплачивает, и точка, завершающая заказ, когда платёж прошёл. Готового плагина Qvard пока нет, поэтому в материале по частям разобран собственный код и то, что в WooCommerce обычно идёт не так.
До начала
Нужен аккаунт Qvard с проектом, API-ключом и callback URL, сохранённым в проекте. Со стороны WooCommerce нужен способ добавить код, лучше небольшой собственный плагин или плагин для сниппетов, а не файл темы, который пропадёт при следующем обновлении.
Два решения заранее:
- Валюта. Qvard считает платежи в долларах США. Шлюз из этого материала принимает заказы в USD и отказывает в других валютах, это безопасное значение по умолчанию. Если магазин продаёт в другой валюте, переведите сумму заказа в доллары перед созданием платежа и скажите об этом на чекауте.
- Где хранить ключ. Задайте API-ключ константой в
wp-config.php(define( 'QVARD_API_KEY', '...' );), а не в настройках базы, откуда его могут прочитать выгрузки и другие плагины.
Если нужна лишь общая идея без класса WooCommerce, начните с материала криптоплатежи на WordPress.
Части интеграции и путь заказа
Интеграция состоит из трёх частей:
- Класс платёжного шлюза, который появляется на чекауте WooCommerce как «Оплатить криптой».
- Запрос к Qvard внутри шлюза, который создаёт платёж под заказ и возвращает адрес страницы оплаты.
- Точка webhook, которую Qvard вызывает, когда платёж завершён.
Заказ проходит так: клиент выбирает способ и нажимает оплату; шлюз создаёт платёж Qvard, переводит заказ в ожидание и перенаправляет клиента на страницу оплаты; клиент платит из кошелька; Qvard вызывает вашу точку; точка завершает заказ, а WooCommerce отправляет привычные письма и двигает его дальше. id платежа, который вы отправляете в Qvard, это номер заказа WooCommerce, по нему webhook снова находит заказ.
Класс шлюза
Класс наследует WC_Payment_Gateway. Конструктор задаёт id и название способа и загружает настройки, а работу делает process_payment. Он отказывает заказам не в USD, затем отправляет один POST на https://api.qvard.com/api/v1/payment с ключом в заголовке Authorization: Bearer и JSON: id (номер заказа строкой) и price (сумма в долларах США). Ответ 201 несёт redirectUrl.
При успехе шлюз сохраняет id платежа Qvard в заказе, ставит статус on-hold («ожидает оплаты»), очищает корзину и возвращает redirectUrl как адрес перенаправления. При ошибке он добавляет сообщение и возвращает failure, так что клиент остаётся на чекауте и может выбрать другой способ.
Повторная отправка того же id для того же способа возвращает уже созданный платёж, поэтому клиент, нажавший кнопку дважды, не создаёт два платежа.
<?php
// A minimal WooCommerce payment method that sends the customer to the Qvard payment page.
add_filter( 'woocommerce_payment_gateways', function ( $gateways ) {
$gateways[] = 'WC_Gateway_Qvard';
return $gateways;
} );
add_action( 'plugins_loaded', function () {
if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
return;
}
class WC_Gateway_Qvard extends WC_Payment_Gateway {
public function __construct() {
$this->id = 'qvard';
$this->method_title = 'Qvard (crypto)';
$this->method_description = 'Accept USDT and other crypto through Qvard.';
$this->has_fields = false;
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option( 'title', 'Pay with crypto' );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array( 'title' => 'Enable', 'type' => 'checkbox', 'default' => 'no' ),
'title' => array( 'title' => 'Title', 'type' => 'text', 'default' => 'Pay with crypto' ),
);
}
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
// Qvard prices payments in US dollars: refuse other shop currencies instead of charging a wrong amount.
if ( 'USD' !== $order->get_currency() ) {
wc_add_notice( 'Crypto payment is available for orders in USD only.', 'error' );
return array( 'result' => 'failure' );
}
$response = wp_remote_post( 'https://api.qvard.com/api/v1/payment', array(
'timeout' => 15,
'headers' => array( 'Authorization' => 'Bearer ' . QVARD_API_KEY, 'Content-Type' => 'application/json' ),
'body' => wp_json_encode( array( 'id' => (string) $order->get_id(), 'price' => (float) $order->get_total() ) ),
) );
if ( is_wp_error( $response ) || 201 !== wp_remote_retrieve_response_code( $response ) ) {
wc_add_notice( 'Could not start the payment. Please try again.', 'error' );
return array( 'result' => 'failure' );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $data['redirectUrl'] ) ) {
wc_add_notice( 'Could not start the payment. Please try again.', 'error' );
return array( 'result' => 'failure' );
}
$order->update_meta_data( '_qvard_payment_id', sanitize_text_field( $data['id'] ?? '' ) );
$order->update_status( 'on-hold', 'Waiting for the crypto payment.' );
WC()->cart->empty_cart();
return array( 'result' => 'success', 'redirect' => $data['redirectUrl'] );
}
}
} );Webhook, завершающий заказ
Зарегистрируйте REST-маршрут и сохраните его адрес в проекте Qvard как callback URL. Когда платёж завершён, Qvard отправляет POST с id и status подписанным заголовком X-Qvard-Signature. Обработчик из примера:
- проверяет подпись (HMAC-SHA256 вашим секретным ключом) через SDK Qvard и иначе отвечает
401; - находит заказ по
idи игнорирует несуществующие заказы и оплаченные не этим шлюзом, но всё равно отвечает 200, чтобы Qvard перестал повторять; - вызывает
payment_complete()только по статусуcompletedи только если заказ ещё не оплачен, поэтому безопасен при двойном запуске; - отвечает ровно HTTP 200. Любой другой статус, таймаут или редирект считаются неудачной доставкой и повторяются: пять попыток примерно за шесть минут.
Разрешите маршрут в плагинах безопасности и файрволе и следите, чтобы кэш не трогал /wp-json/. Общие правила в материале как защитить webhook криптоплатежей.
<?php
// Callback URL to save in the Qvard project: https://your-shop.com/wp-json/qvard/v1/webhook
add_action( 'rest_api_init', function () {
register_rest_route( 'qvard/v1', '/webhook', array(
'methods' => 'POST',
'permission_callback' => '__return_true', // authenticated below by the X-Qvard-Signature header
'callback' => function ( WP_REST_Request $request ) {
try {
$payload = ( new Qvard\Qvard( QVARD_API_KEY ) )->webhooks->constructEvent( $request->get_body(), $request->get_header( 'x-qvard-signature' ), QVARD_API_KEY );
} catch ( \Exception $e ) {
return new WP_REST_Response( null, 401 );
}
$order = wc_get_order( (int) ( $payload['id'] ?? 0 ) );
// Unknown order or not ours: nothing to retry, answer 200.
if ( ! $order || 'qvard' !== $order->get_payment_method() ) {
return new WP_REST_Response( null, 200 );
}
if ( 'completed' === ( $payload['status'] ?? '' ) && ! $order->is_paid() ) {
$order->payment_complete( (string) $order->get_meta( '_qvard_payment_id' ) );
$order->add_order_note( 'Qvard: payment completed.' );
}
return new WP_REST_Response( null, 200 ); // must be exactly 200
},
) );
} );Особенности WooCommerce, о которые спотыкаются
- Остатки. WooCommerce списывает остаток, когда заказ уходит в обработку или ожидание, поэтому неоплаченные криптозаказы могут держать товар. Отменяйте устаревшие заказы в ожидании через выбранное вами время своей запланированной задачей (настройка удержания остатков в WooCommerce действует на заказы «ожидает оплаты», а не на «на удержании»), иначе товар остаётся в резерве.
- Частичные оплаты. Если пришло меньше полной суммы, статус платежа
partial, а webhook о завершении не приходит. Заказ остаётся в ожидании; добавьте заметку, попросите клиента доплатить либо отмените и верните деньги. - Возвраты. Криптоплатёж не отозвать. Создайте запись возврата в WooCommerce для учёта, а деньги отправьте сами на адрес, который назовёт клиент.
- Письма о заказе. Письмо «в ожидании» уходит при оформлении; следите, чтобы его текст говорил клиенту, что оплата ещё не прошла и где платить.
- Возврат на сайт. Возврата по ссылке после оплаты пока нет, поэтому клиент возвращается в магазин сам. Стандартная страница заказа показывает реальный статус, и это правильно.
- Блочный чекаут. Блочному чекауту WooCommerce шлюз нужно регистрировать ещё и для блоков; проверьте способ именно в том чекауте, которым пользуетесь.
Проверьте до запуска
- Откройте демо-счёт, чтобы увидеть страницу оплаты, которую получит клиент.
- Оформите тестовый заказ с крипто-способом и проверьте, что вас перенаправило на страницу оплаты, а заказ в ожидании.
- Оплатите небольшую настоящую сумму и убедитесь, что webhook пришёл и заказ перешёл в обработку.
- Один раз заставьте точку вернуть ошибку, проверьте, что Qvard повторяет доставку и заказ не завершается дважды.
- Выведите небольшую сумму с баланса на кошелёк, чтобы убедиться, что весь путь работает.
Отдельного sandbox пока нет, поэтому берите небольшие реальные суммы.
Сколько это стоит и что готово
Абонентской платы за шлюз нет. Комиссия индивидуальна для вашего проекта и по странице тарифов начинается с 1%, а может быть снижена до 0,1%. Средства лежат на вашем балансе в Qvard, и вы выводите их на свой кошелёк за сетевую комиссию; вывода на банковский счёт в фиате нет.
Qvard работает через API, поэтому интеграция требует разработчика и тестирования; готового плагина WooCommerce нет, а платёжные ссылки и отдельный sandbox в разработке. Сам API описан в материале API для криптоплатежей, а сторона магазина в криптоплатежи для интернет-магазина.
FAQ
Есть ли плагин Qvard для WooCommerce?
Пока нет. Интеграция идёт через API: класс шлюза создаёт платёж, а webhook завершает заказ. Код из материала это отправная точка для разработчика.
Магазин работает в евро. Могут ли клиенты платить криптой?
Qvard считает платежи в долларах США. Переведите сумму заказа в доллары до создания платежа и покажите клиенту сумму в долларах на чекауте; шлюз из примера отказывает заказам не в USD как безопасное значение по умолчанию.
Когда заказ становится оплаченным?
Когда приходит webhook со статусом completed. Возвращение клиента в магазин не доказательство оплаты, а статус partial значит, что пришло меньше нужного.
Что если сайт не работал, когда платёж завершился?
Уведомление повторяется пять раз примерно за шесть минут. После этого отправьте его по платежу ещё раз из кабинета Qvard; обработчик безопасен при двойном запуске.
