WooCommerce REST API v2: How to process payment?

Kirby picture Kirby · Jun 18, 2015 · Viewed 7.1k times · Source

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)

Answer

Kirby picture Kirby · Jun 19, 2015

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 )
                );
        }
    }
}