Tích hợp

Thanh toán crypto trên WooCommerce

Cách thêm crypto làm phương thức thanh toán của WooCommerce: lớp cổng thanh toán tùy chỉnh, tạo thanh toán Qvard, webhook hoàn tất đơn hàng và những cạm bẫy riêng của WooCommerce.

WooCommerce có điểm mở rộng gọn gàng cho các phương thức thanh toán, và Qvard chỉ cần hai thứ từ nó: một lệnh gọi tạo thanh toán khi khách trả tiền và một endpoint hoàn tất đơn hàng khi thanh toán xong. Chưa có plugin Qvard dựng sẵn, nên bài này trình bày mã tùy chỉnh từng phần, cùng những chi tiết của WooCommerce thường gây lỗi.

Trước khi bắt đầu

Bạn cần một tài khoản Qvard có dự án, khóa API và callback URL đã lưu trong dự án. Về phía WooCommerce, bạn cần cách thêm mã, lý tưởng là một plugin tùy chỉnh nhỏ hoặc plugin code-snippets, chứ không phải tệp theme sẽ mất trong lần cập nhật theme sau.

Hai quyết định ngay từ đầu:

  • Tiền tệ. Qvard tính giá thanh toán bằng đô la Mỹ. Cổng trong bài này nhận đơn bằng USD và từ chối tiền tệ khác, đó là mặc định an toàn. Nếu cửa hàng bán bằng tiền tệ khác, hãy quy đổi tổng đơn sang đô la trước khi tạo thanh toán và nói rõ ở bước thanh toán.
  • Khóa để ở đâu. Định nghĩa khóa API làm hằng số trong wp-config.php (define( 'QVARD_API_KEY', '...' );), không để trong cài đặt cơ sở dữ liệu nơi bản xuất và plugin khác có thể đọc.

Nếu bạn chỉ cần ý tưởng cơ bản mà không có lớp WooCommerce, hãy bắt đầu với thanh toán crypto trên WordPress.

Các phần và cách một đơn hàng di chuyển

Ba phần tạo nên việc tích hợp:

  1. Một lớp cổng thanh toán xuất hiện ở bước thanh toán của WooCommerce là «Thanh toán bằng crypto».
  2. Một lệnh gọi tới Qvard bên trong cổng, tạo thanh toán cho đơn và trả về địa chỉ trang thanh toán được lưu trữ.
  3. Một endpoint webhook mà Qvard gọi khi thanh toán hoàn tất.

Đơn hàng di chuyển như sau: khách chọn phương thức và bấm trả tiền; cổng tạo thanh toán Qvard, đặt đơn ở trạng thái chờ và chuyển khách tới trang thanh toán; khách trả từ ví; Qvard gọi endpoint của bạn; endpoint hoàn tất đơn và WooCommerce gửi các email quen thuộc rồi đẩy đơn đi tiếp. id thanh toán bạn gửi tới Qvard là số đơn hàng WooCommerce, nhờ đó webhook tìm lại được đơn.

Lớp cổng thanh toán

Lớp này mở rộng WC_Payment_Gateway. Hàm tạo đặt id và tiêu đề phương thức rồi nạp cài đặt, còn process_payment làm phần việc chính. Nó từ chối đơn không phải USD, rồi gửi một POST tới https://api.qvard.com/api/v1/payment với khóa của bạn trong header Authorization: Bearer và một JSON: id (số đơn hàng dạng chuỗi) và price (tổng bằng đô la Mỹ). Phản hồi 201 mang theo redirectUrl.

Khi thành công, cổng lưu id thanh toán Qvard vào đơn, đặt trạng thái on-hold («chờ thanh toán»), làm trống giỏ hàng và trả redirectUrl làm địa chỉ chuyển hướng. Khi thất bại, nó thêm thông báo lỗi và trả failure, để khách ở lại bước thanh toán và có thể chọn phương thức khác.

Gửi cùng id cho cùng phương thức thanh toán trả về khoản thanh toán hiện có, nên khách bấm nút hai lần không tạo hai khoản.

PHPqvard-gateway.php
<?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 hoàn tất đơn hàng

Đăng ký một route REST và lưu địa chỉ của nó trong dự án Qvard làm callback URL. Khi một thanh toán hoàn tất, Qvard gửi POST kèm id và status, có chữ ký trong header X-Qvard-Signature. Trình xử lý trong ví dụ:

  • xác minh chữ ký (HMAC-SHA256 bằng khóa bí mật của bạn) bằng SDK của Qvard và trả 401 nếu không khớp;
  • tìm đơn theo id và bỏ qua đơn không tồn tại hoặc không thanh toán bằng cổng này, vẫn trả 200 để Qvard ngừng thử lại;
  • gọi payment_complete() chỉ khi trạng thái là completed và chỉ nếu đơn chưa thanh toán, nhờ đó an toàn khi chạy hai lần;
  • trả lời đúng HTTP 200. Mọi trạng thái khác, quá thời gian hay chuyển hướng đều tính là gửi thất bại và được thử lại, năm lần trong khoảng sáu phút.

Cho phép route trong mọi plugin bảo mật hoặc tường lửa và đảm bảo bộ nhớ đệm không đụng tới /wp-json/. Các quy tắc rộng hơn nằm trong cách bảo mật webhook thanh toán crypto.

PHPqvard-webhook.php
<?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
        },
    ) );
} );

Những chi tiết WooCommerce hay gây lỗi

  • Hàng tồn. WooCommerce trừ tồn kho khi đơn chuyển sang đang xử lý hoặc chờ, nên các đơn crypto chưa thanh toán có thể giữ hàng. Hãy hủy các đơn chờ quá hạn sau khoảng thời gian bạn chọn bằng tác vụ định kỳ của riêng bạn (cài đặt giữ hàng của WooCommerce áp dụng cho đơn chờ thanh toán, không phải đơn đang chờ xử lý); nếu không, hàng vẫn bị giữ.
  • Thanh toán một phần. Nếu số tiền đến ít hơn đủ, trạng thái thanh toán là partial và webhook hoàn tất không được gửi. Đơn vẫn ở trạng thái chờ; hãy thêm ghi chú, yêu cầu khách trả phần còn lại hoặc hủy và hoàn tiền.
  • Hoàn tiền. Thanh toán crypto không thể rút lại. Tạo bản ghi hoàn tiền trong WooCommerce cho sổ sách, nhưng tự gửi tiền tới địa chỉ khách cung cấp.
  • Email đơn hàng. Email «đang chờ» được gửi khi đặt đơn; đảm bảo nội dung cho khách biết thanh toán đang chờ và trả ở đâu.
  • Quay lại cửa hàng. Chưa có URL quay lại sau khi thanh toán, nên khách tự quay lại cửa hàng của bạn. Trang đơn hàng chuẩn hiển thị trạng thái thực, như vậy là đúng.
  • Thanh toán dạng khối. Trang thanh toán dạng khối của WooCommerce cần cổng được đăng ký cho các khối nữa; hãy thử phương thức này trên trang thanh toán bạn thực sự dùng.

Thử nghiệm trước khi chạy thật

  1. Mở một hóa đơn demo để thấy trang thanh toán khách của bạn sẽ nhận.
  2. Đặt một đơn thử với phương thức crypto và kiểm tra bạn được chuyển tới trang thanh toán và đơn ở trạng thái chờ.
  3. Trả một khoản tiền thật nhỏ và xác nhận webhook đến và đơn chuyển sang đang xử lý.
  4. Cho endpoint lỗi một lần, xác nhận Qvard thử lại và đơn không bị hoàn tất hai lần.
  5. Rút một khoản nhỏ từ số dư về ví để biết toàn bộ đường đi hoạt động.

Chưa có sandbox riêng, nên hãy dùng số tiền thật nhỏ.

Chi phí và những gì đã sẵn sàng

Không có phí đăng ký cho cổng thanh toán. Hoa hồng riêng cho dự án của bạn và theo trang bảng giá bắt đầu từ 1%, có thể giảm xuống 0,1%. Tiền nằm trong số dư Qvard của bạn và bạn rút về ví của mình với phí mạng; rút về tài khoản ngân hàng bằng tiền pháp định không khả dụng.

Qvard hoạt động qua API, nên việc tích hợp cần lập trình viên và kiểm thử; chưa có plugin WooCommerce dựng sẵn, liên kết thanh toán và sandbox riêng đang được phát triển. Bản thân API được mô tả trong API thanh toán crypto cho nhà phát triển, và phía cửa hàng trong thanh toán crypto cho thương mại điện tử.

FAQ

Có plugin Qvard cho WooCommerce không?

Chưa có. Việc tích hợp qua API: một lớp cổng tạo thanh toán và một webhook hoàn tất đơn. Mã trong bài là điểm xuất phát cho lập trình viên.

Cửa hàng của tôi dùng euro. Khách vẫn trả bằng crypto được không?

Qvard tính giá thanh toán bằng đô la Mỹ. Hãy quy đổi tổng đơn sang đô la trước khi tạo thanh toán và cho khách biết số tiền bằng đô la ở bước thanh toán; cổng trong ví dụ từ chối đơn không phải USD như mặc định an toàn.

Khi nào đơn được coi là đã thanh toán?

Khi webhook đến với trạng thái completed. Khách quay lại cửa hàng không phải bằng chứng thanh toán, và trạng thái partial nghĩa là số tiền đến ít hơn yêu cầu.

Nếu trang web ngừng khi thanh toán hoàn tất thì sao?

Thông báo được thử lại năm lần trong khoảng sáu phút. Sau đó hãy gửi lại cho khoản thanh toán đó từ bảng điều khiển Qvard; trình xử lý an toàn khi chạy hai lần.

Bài viết liên quan

Bài viết liên quan