WooCommerce REST API v2: How to process payment?

13.5k views Asked by At

Using the WooCommerce REST API v2, I'm successfully creating an order in a pending, unpaid state.

I can see that I can set the order.payment_details.paid field to true which will create the order in a completed state and send out a completed order email, but it doesn't actually process the payment.

What is the proper way using the REST API v2 to create an order and have WooCommerce process the payment using the payment gateway?

Or do I need to add a plugin hook to the API on the server side? (I think so)

Here's what I've tried

curl -X POST https://example.com/wc-api/v2/orders \
    -u consumer_key:consumer_secret \
    -H "Content-Type: application/json" \
    -d '{
          "order": {
            "customer_id": 2,
            "payment_details": {
              "method_id": "da_big_bank",
              "method_title": "StackOverflow Money Laundering, Inc.",
              "paid":true
            },
            "line_items": [
              {
                "product_id": 341,
                "quantity": 1
              }
            ]
          }
        }'

which, as I said, generates an order in completed state, but doesn't actually process any money with my gateway (which is not "StackOverflow Money Laundering, Inc." and is a legitimate gateway that does work when using our WooCommerce site)

3

There are 3 answers

0
Kirby On BEST ANSWER

As helgatheviking concurred, there currently isn't a way to process payment of an order with the WooCommerce REST API.

I ended up writing a hook into the woocommerce_api_create_order filter that immediately processes the order for payment when the order is created. If the processing fails, then the errors are added to the order->post->post_excerpt field which makes it appear as order->note in the JSON response.

For this to work, I also had to extend the payment gateway so that its process_payment() method would accept a $user_id as an input. This is because it's coded out of the box to operate on the currently logged in user, which, in my case, and probably most cases, is the system user that the REST client logs in as, not the actual user making a purchase.

The other benefit of extending the gateway turned out to be that now errors can be returned rather than written to wc_add_notice(). Since this is a REST service, nothing ever sees the output of wc_add_notice()

add_filter('woocommerce_api_create_order', 'acme_on_api_create_order', 10, 3);

/**
 * When order is created in REST client, actually make them pay for it
 * @param int $id order id
 * @param array $data order data posted by client
 * @param WC_API_Orders $api not used
 * @return array the data passed back unaltered
 */
function acme_on_api_create_order($id, $data, $api) {
    if($data['payment_details']['method_id'] == 'acme_rest_gateway') {
        $order = wc_get_order($id);
        $order->calculate_totals();
        $acme_gateway = new WC_Acme_Gateway_For_Rest();
        $payment = $acme_gateway->process_payment($id, $data['customer_id']);
        if($payment["result"] == "success") {
            $order->update_status('completed');
        }
        else {
            $order->update_status("cancelled");
            wp_update_post(array(
                'ID' => $id,
                'post_excerpt' => json_encode($payment)
            ));
        }
    }
    return $data;
}

// Register the payment gateway
add_filter('woocommerce_payment_gateways', 'acme_add_payment_gateway_class');

function acme_add_payment_gateway_class($methods) {
    $methods[] = 'WC_Acme_Gateway_For_Rest';
    return $methods;
}

// Load the new payment gateway needed by REST client
add_action('after_setup_theme', 'acme_init_rest_gateway_class');

function acme_init_rest_gateway_class() {

    /**
     * Extend the payment gateway to work in the REST API context
     */
    class WC_Acme_Gateway_For_Rest extends WC_Acme_Gateway {

        /**
         * Constructor for the gateway.
         */
        public function __construct() {
            parent::__construct();
            $this->id = 'acme_rest_gateway';
        }

        /**
         * Process Payment. This is the same as the parent::process_payment($order_id) except that we're also passing
         * the user id rather than reading get_current_user_id().
         * And we're returning errors rather than writing them as notices
         * @param int $order_id the order id
         * @param int $user_id user id
         * @return array|null an array if success. otherwise returns nothing
         */
        function process_payment($order_id, $user_id) {
                $order = wc_get_order( $order_id );
            /* 
             * todo: code sending da moneez to da bank
             */
                return array(
                    'result'   => 'success',
                    'redirect' => $this->get_return_url( $order )
                );
        }
    }
}
2
Eder Ribeiro On

Thank you for the direction that you gave me.

I made some changes and simplified steps.

As follows:

add_filter('woocommerce_api_order_response', 'intercept_api_response', 1, 4);
/**
* Here, intercept api's response to include the url of payment
**/
function intercept_api_response($order_data, $order)
{
    $order_data['payment_url'] = $order->payment_url;

    return $order_data;
}

add_filter('woocommerce_api_create_order', 'intercept_on_api_create_order', 10, 3);


function intercept_on_api_create_order($id, $data, $api)
{
    if (in_array($data['payment_details']['method_id'], ['pagseguro', 'paypal'])) {
        $order = wc_get_order($id);
        $order->calculate_totals();

        if ($data['payment_details']['method_id'] == 'paypal') {
            $paypal = new WC_Gateway_Paypal();
            $payment = $paypal->process_payment($id);
        }
        update_post_meta($id, '_payment_url', $payment['redirect']);
    }
    return $payment;
}

I hope this may help someone else. It was hard work with lots of trial and error.

0
Sumit Wadhwa On

If you're NOT dealing with hosted payment gateways (ones that require users to redirect to their own domain to process payments like paypal), then you could just do it how WC does it, via ajax request:

// this is a function as a callback for a restful api - process_payment

// collect payment related info: billing, shipping info, including shipping_method & payment_method

// put all that info inside $_POST global var

// so that the nonce won't fail
$_REQUEST['_wpnonce'] =
        wp_create_nonce( 'woocommerce-process_checkout' );

// make it look like an ajax request
wc_maybe_define_constant('DOING_AJAX', 1);

add_filter('woocommerce_payment_successful_result', function($response) {
     // wp_send_json appropriate response
}

WC()->checkout()->process_checkout();

WC()->checkout()->process_checkout() will create an order for you give that WC()->cart is not empty.

You can also try this for hosted payment gateways, but it would return a redirect that you could open up in a webview of mobile application and collect payment.

https://gist.github.com/swport/afd9292412752df9e2e086ac38030e8f