diff --git a/includes/class-wc-gateway-fastspring-handler.php b/includes/class-wc-gateway-fastspring-handler.php index 526ead4..c2152a9 100644 --- a/includes/class-wc-gateway-fastspring-handler.php +++ b/includes/class-wc-gateway-fastspring-handler.php @@ -43,10 +43,11 @@ public function ajax_get_receipt() { $payload = json_decode(file_get_contents('php://input')); - $allowed = wp_verify_nonce( $payload->security, 'wc-fastspring-receipt' ); + $allowed = wp_verify_nonce($payload->security, 'wc-fastspring-receipt'); - if(! $allowed ) - wp_send_json_error('Access denied'); + if (!$allowed) { + wp_send_json_error('Access denied'); + } $order_id = absint(WC()->session->get('current_order')); @@ -54,24 +55,26 @@ public function ajax_get_receipt() { $data = ['order_id' => $order->get_id()]; // Check for double calls - $order_status = $order->get_status(); + $order_status = $order->get_status(); // Popup closed with payment if ($order && $payload->reference) { - + // Remove cart WC()->cart->empty_cart(); $order->set_transaction_id($payload->reference); - // We could habe a race condition where FS already called wenhook so lets not assume its pending - if($order_status != 'completed') + // We could habe a race condition where FS already called webhook so lets not assume its pending + if ($order_status != 'completed') { $order->update_status('pending', __('Order pending payment approval.', 'woocommerce')); + } + $data = ["redirect_url" => WC_Gateway_FastSpring_Handler::get_return_url($order), 'order_id' => $order_id]; - + wp_send_json($data); } else { wp_send_json_error('Order not found - Order ID was'); - } + } } @@ -88,13 +91,13 @@ static public function get_return_url($order = null) { } else { $return_url = wc_get_endpoint_url('order-received', '', wc_get_page_permalink('checkout')); } - + if (is_ssl() || get_option('woocommerce_force_ssl_checkout') == 'yes') { $return_url = str_replace('http:', 'https:', $return_url); } $filtered = apply_filters('woocommerce_get_return_url', $return_url, $order); - + return $filtered; } @@ -124,222 +127,232 @@ public function listen_webhook_request() { // Sample webhook data for order.completed /* - { events: [{ - "id": "J5MhLkZdQmGgPOI2Fb1Xhw", - "processed": false, - "created": 1505189268836, - "type": "order.completed", - "live": false, - "data": { - "order": "9qflPQrkR76oKDLObXguyg", - "id": "9qflPQrkR76oKDLObXguyg", - "reference": "SOF170912-7652-55291", - "buyerReference": null, - "completed": true, - "changed": 1505180769638, - "changedValue": 1505180769638, - "changedInSeconds": 1505180769, - "changedDisplay": "9/12/17", - "language": "en", - "live": false, - "currency": "USD", - "payoutCurrency": "USD", - "invoiceUrl": "https://software4recording.onfastspring.com/account/order/SOF170912-7652-55291/invoice", - "account": "NbNDzguWR5-Dv9Rlxg7xsw", - "total": 100.0, - "totalDisplay": "$100.00", - "totalInPayoutCurrency": 100.0, - "totalInPayoutCurrencyDisplay": "$100.00", - "tax": 0.0, - "taxDisplay": "$0.00", - "taxInPayoutCurrency": 0.0, - "taxInPayoutCurrencyDisplay": "$0.00", - "subtotal": 100.0, - "subtotalDisplay": "$100.00", - "subtotalInPayoutCurrency": 100.0, - "subtotalInPayoutCurrencyDisplay": "$100.00", - "discount": 0.0, - "discountDisplay": "$0.00", - "discountInPayoutCurrency": 0.0, - "discountInPayoutCurrencyDisplay": "$0.00", - "discountWithTax": 0.0, - "discountWithTaxDisplay": "$0.00", - "discountWithTaxInPayoutCurrency": 0.0, - "discountWithTaxInPayoutCurrencyDisplay": "$0.00", - "billDescriptor": "FS* software4record", - "payment": { - "type": "test", - "cardEnding": "4242" - }, - "customer": { - "first": "asd", - "last": "asdsadasda", - "email": "email@example.com", - "company": null, - "phone": "2222222222" - }, - "address": { - "city": "Madison", - "regionCode": "WI", - "regionDisplay": "Wisconsin", - "region": "Wisconsin", - "postalCode": "53702", - "country": "US", - "display": "Madison, Wisconsin, 53702, US" - }, - "notes": [], - "items": [{ - "product": "sample-stuff", - "quantity": 1, - "display": "1 Sample Stuff", - "sku": null, - "subtotal": 100.0, - "subtotalDisplay": "$100.00", - "subtotalInPayoutCurrency": 100.0, - "subtotalInPayoutCurrencyDisplay": "$100.00", - "discount": 0.0, - "discountDisplay": "$0.00", - "discountInPayoutCurrency": 0.0, - "discountInPayoutCurrencyDisplay": "$0.00", - "fulfillments": {} - }] - } -*/ - - // Sample data for returns - /* - { - "events": [ - { - "created": 1505765983299, - "data": { - "account": "NbNDzguWR5-Dv9Rlxg7xsw", - "changed": 1505765983207, - "changedDisplay": "9/18/17", - "changedInSeconds": 1505765983, - "changedValue": 1505765983207, - "completed": true, - "currency": "USD", - "customer": { - "company": null, - "email": "email@example.com", - "first": "asd", - "last": "asdsadasda", - "phone": "2222222222" - }, - "items": [ - { - "display": "2 Sample Virtual Thing", - "product": "sample-virtual-thing", - "quantity": 2, - "sku": null, - "subtotal": 300.0, - "subtotalDisplay": "$300.00", - "subtotalInPayoutCurrency": 300.0, - "subtotalInPayoutCurrencyDisplay": "$300.00" - } - ], + { events: [{ + "id": "J5MhLkZdQmGgPOI2Fb1Xhw", + "processed": false, + "created": 1505189268836, + "type": "order.completed", "live": false, - "note": "", - "original": { - "account": "NbNDzguWR5-Dv9Rlxg7xsw", + "data": { + "tags": { + "foo": "bar" // whatever you set for custom tags like object/array + }, + "order": "9qflPQrkR76oKDLObXguyg", + "id": "9qflPQrkR76oKDLObXguyg", + "reference": "SOF170912-7652-55291", + "buyerReference": null, + "completed": true, + "changed": 1505180769638, + "changedValue": 1505180769638, + "changedInSeconds": 1505180769, + "changedDisplay": "9/12/17", + "language": "en", + "live": false, "currency": "USD", - "id": "FWdPzftgReSxl2CswuQQ4w", - "notes": [], - "order": "FWdPzftgReSxl2CswuQQ4w", "payoutCurrency": "USD", - "reference": "SOF170918-7652-19314", - "subtotal": 300.0, - "subtotalDisplay": "$300.00", - "subtotalInPayoutCurrency": 300.0, - "subtotalInPayoutCurrencyDisplay": "$300.00", + "invoiceUrl": "https://software4recording.onfastspring.com/account/order/SOF170912-7652-55291/invoice", + "account": "NbNDzguWR5-Dv9Rlxg7xsw", + "total": 100.0, + "totalDisplay": "$100.00", + "totalInPayoutCurrency": 100.0, + "totalInPayoutCurrencyDisplay": "$100.00", "tax": 0.0, "taxDisplay": "$0.00", "taxInPayoutCurrency": 0.0, "taxInPayoutCurrencyDisplay": "$0.00", - "total": 300.0, - "totalDisplay": "$300.00", - "totalInPayoutCurrency": 300.0, - "totalInPayoutCurrencyDisplay": "$300.00" - }, - "payment": { - "cardEnding": "4242", - "type": "test" - }, - "payoutCurrency": "USD", - "reason": "Product Not As Expected", - "reference": "SOF170918-8960-65188X", - "return": "qWIaFaLlRgu6NYs7MgHkGA", - "subtotal": 300.0, - "subtotalDisplay": "$300.00", - "subtotalInPayoutCurrency": 300.0, - "subtotalInPayoutCurrencyDisplay": "$300.00", - "tax": 0.0, - "taxDisplay": "$0.00", - "taxInPayoutCurrency": 0.0, - "taxInPayoutCurrencyDisplay": "$0.00", - "totalRefundInPayoutCurrency": 300.0, - "totalReturn": 300.0, - "totalReturnDisplay": "$300.00", - "totalReturnInPayoutCurrency": 300.0, - "totalReturnInPayoutCurrencyDisplay": "$300.00" - }, - "id": "rYZAq4BvSyKmTOeQeSJQZQ", - "live": false, - "processed": false, - "type": "return.created" - } - ] -} - */ + "subtotal": 100.0, + "subtotalDisplay": "$100.00", + "subtotalInPayoutCurrency": 100.0, + "subtotalInPayoutCurrencyDisplay": "$100.00", + "discount": 0.0, + "discountDisplay": "$0.00", + "discountInPayoutCurrency": 0.0, + "discountInPayoutCurrencyDisplay": "$0.00", + "discountWithTax": 0.0, + "discountWithTaxDisplay": "$0.00", + "discountWithTaxInPayoutCurrency": 0.0, + "discountWithTaxInPayoutCurrencyDisplay": "$0.00", + "billDescriptor": "FS* software4record", + "payment": { + "type": "test", + "cardEnding": "4242" + }, + "customer": { + "first": "asd", + "last": "asdsadasda", + "email": "email@example.com", + "company": null, + "phone": "2222222222" + }, + "address": { + "city": "Madison", + "regionCode": "WI", + "regionDisplay": "Wisconsin", + "region": "Wisconsin", + "postalCode": "53702", + "country": "US", + "display": "Madison, Wisconsin, 53702, US" + }, + "notes": [], + "items": [{ + "product": "sample-stuff", + "quantity": 1, + "display": "1 Sample Stuff", + "sku": null, + "subtotal": 100.0, + "subtotalDisplay": "$100.00", + "subtotalInPayoutCurrency": 100.0, + "subtotalInPayoutCurrencyDisplay": "$100.00", + "discount": 0.0, + "discountDisplay": "$0.00", + "discountInPayoutCurrency": 0.0, + "discountInPayoutCurrencyDisplay": "$0.00", + "fulfillments": {} + }] + } +*/ + + // Sample data for returns + /* + { + "events": [ + { + "created": 1505765983299, + "data": { + "account": "NbNDzguWR5-Dv9Rlxg7xsw", + "changed": 1505765983207, + "changedDisplay": "9/18/17", + "changedInSeconds": 1505765983, + "changedValue": 1505765983207, + "completed": true, + "currency": "USD", + "customer": { + "company": null, + "email": "email@example.com", + "first": "asd", + "last": "asdsadasda", + "phone": "2222222222" + }, + "items": [ + { + "display": "2 Sample Virtual Thing", + "product": "sample-virtual-thing", + "quantity": 2, + "sku": null, + "subtotal": 300.0, + "subtotalDisplay": "$300.00", + "subtotalInPayoutCurrency": 300.0, + "subtotalInPayoutCurrencyDisplay": "$300.00" + } + ], + "live": false, + "note": "", + "original": { + "account": "NbNDzguWR5-Dv9Rlxg7xsw", + "currency": "USD", + "id": "FWdPzftgReSxl2CswuQQ4w", + "notes": [], + "order": "FWdPzftgReSxl2CswuQQ4w", + "payoutCurrency": "USD", + "reference": "SOF170918-7652-19314", + "subtotal": 300.0, + "subtotalDisplay": "$300.00", + "subtotalInPayoutCurrency": 300.0, + "subtotalInPayoutCurrencyDisplay": "$300.00", + "tax": 0.0, + "taxDisplay": "$0.00", + "taxInPayoutCurrency": 0.0, + "taxInPayoutCurrencyDisplay": "$0.00", + "total": 300.0, + "totalDisplay": "$300.00", + "totalInPayoutCurrency": 300.0, + "totalInPayoutCurrencyDisplay": "$300.00" + }, + "payment": { + "cardEnding": "4242", + "type": "test" + }, + "payoutCurrency": "USD", + "reason": "Product Not As Expected", + "reference": "SOF170918-8960-65188X", + "return": "qWIaFaLlRgu6NYs7MgHkGA", + "subtotal": 300.0, + "subtotalDisplay": "$300.00", + "subtotalInPayoutCurrency": 300.0, + "subtotalInPayoutCurrencyDisplay": "$300.00", + "tax": 0.0, + "taxDisplay": "$0.00", + "taxInPayoutCurrency": 0.0, + "taxInPayoutCurrencyDisplay": "$0.00", + "totalRefundInPayoutCurrency": 300.0, + "totalReturn": 300.0, + "totalReturnDisplay": "$300.00", + "totalReturnInPayoutCurrency": 300.0, + "totalReturnInPayoutCurrencyDisplay": "$300.00" + }, + "id": "rYZAq4BvSyKmTOeQeSJQZQ", + "live": false, + "processed": false, + "type": "return.created" + } + ] + } + */ foreach ($events as $event) { do_action('woocommerce_fastspring_handle_webhook_request', $event); } - } /** * Finds one WC order by FastSpring transaction ID * + * @deprecated We use tags now but this is a nice function so keep it + * * @throws Exception * * @param string $id FastSpring transaction ID * @return WC_Order WooCommerce order */ public function find_order_by_fastspring_id($id) { + $orders = $this->search_orders(["search_key" => "_transaction_id", "search_value" => $id]); + if (sizeof($orders) === 1) { + $order = wc_get_order($orders[0]->ID); + $this->log(sprintf('Order %s found with transaction ID %s', $order->get_id(), $id)); + return $order; + } - // $orders = $this->search_orders(["search_key" => "_transaction_id", "search_value" => $id]); - - // if (sizeof($orders) === 1) { + $this->log(sprintf('No order found with transaction ID %s', $id)); + throw new Exception(sprintf('Unable to locate order with FS transaction ID %s', $id)); + } - // $this->log(sprintf('Order found with transaction ID %s', $id)); - // return wc_get_order($orders[0]->ID); - // } + /** + * Finds one WC order by FastSpring custom tag + * + * @throws Exception + * + * @param string $id FastSpring transaction ID + * @return WC_Order WooCommerce order + */ + public function find_order_by_fastspring_tag($payload) { - // // let's try a fallback - // $orders = wc_get_orders(array( - // 'transaction_id' => $id - // )); + $id = @$payload->data->tags->store_order_id; + $this->log(sprintf('Order tag found for %s', $id)); - // if (sizeof($orders) > 0) { - // $this->log(sprintf('Order found on second try with transaction ID %s', $id)); - // return $orders[0]; - // } + if (!isset($id)) { + $this->log('No order ID found in webhook'); + throw new Exception('No order ID found in webhook'); + } - // let's try a fallback - $orders = wc_get_orders(array( - '_transaction_id' => $id - )); + $order = wc_get_order($id); - if (sizeof($orders) > 0) { - $this->log(sprintf('Order found with transaction ID %s', $id)); - return $orders[0]; + if (!$order) { + $this->log(sprintf('No order found with transaction ID %s', $id)); + throw new Exception(sprintf('Unable to locate order with FS transaction ID %s', $id)); } + return $order; - $this->log(sprintf('No order found with transaction ID %s', $id)); - throw new Exception(sprintf('Unable to locate order with FS transaction ID %s', $id)); } /** @@ -363,7 +376,6 @@ public function handle_webhook_request($payload) { $this->handle_webhook_request_order_refunded($payload); break; - default: $this->log(sprintf('No webhook handler found for %s', $payload->type)); break; @@ -381,22 +393,24 @@ public function handle_webhook_request($payload) { * @param array $payload Webhook data */ public function handle_webhook_request_order_completed($payload) { - $id = $payload->data->reference; - $order = $this->find_order_by_fastspring_id($id); - $this->log(sprintf('Marking order ID %s as complete', $order->get_id())); - $order->payment_complete(); - $order->add_order_note(sprintf(__('FastSpring payment approved (ID: %1$s)', 'woocommerce'), $id)); + + $order = $this->find_order_by_fastspring_tag($payload); + + if ($order->payment_complete( $payload->reference)) { + $this->log(sprintf('Marking order ID %s as complete', $order->get_id())); + $order->add_order_note(sprintf(__('FastSpring payment approved (ID: %1$s)', 'woocommerce'), $order->get_id())); + } else { + $this->log(sprintf('Failed marking order ID %s as complete', $order->get_id())); + } } - /** * Handles the order.failed webhook * * @param array $payload Webhook data */ public function handle_webhook_request_order_refunded($payload) { - $id = $payload->data->original->reference; - $order = $this->find_order_by_fastspring_id($id); + $order = $this->find_order_by_fastspring_tag($payload); $this->log(sprintf('Marking order ID %s as refunded', $order->get_id())); $order->update_status('refunded'); } diff --git a/includes/class-wc-gateway-fastspring.php b/includes/class-wc-gateway-fastspring.php index 3793f9f..e1c4e69 100644 --- a/includes/class-wc-gateway-fastspring.php +++ b/includes/class-wc-gateway-fastspring.php @@ -83,7 +83,6 @@ public function is_available() { public function init_form_fields() { $this->form_fields = include 'settings-fastspring.php'; } - /** * payment_scripts function. @@ -97,6 +96,7 @@ public function payment_scripts() { if (is_checkout()) { $load_scripts = true; } + if ($this->is_available()) { $load_scripts = true; } @@ -139,7 +139,6 @@ public function process_payment($order_id) { 'result' => 'success', 'redirect' => $order->get_checkout_payment_url(true), ); - } /** @@ -275,6 +274,15 @@ public function get_cart_customer_details() { ]; } + /** + * Returns order ID as tag array for FS reference + * + * @return array + */ + public function get_order_tags() { + return array( "store_order_id" => absint(WC()->session->get('order_awaiting_payment'))); + } + /** * Builds JSON payload * @@ -282,6 +290,7 @@ public function get_cart_customer_details() { */ public function get_json_payload() { return array( + 'tags' => $this->get_order_tags(), 'contact' => $this->get_cart_customer_details(), 'items' => $this->get_cart_items(), ); @@ -358,7 +367,7 @@ public function payment_fields() { public function payment_page($order_id) { $order = wc_get_order($order_id); - echo '

' . sprintf(__('Thank you for your order, please click the button below to pay using %s.', 'woocommerce'), $this->option('title')) . '

'; + // echo '

' . sprintf(__('Thank you for your order, please click the button below to pay using %s.', 'woocommerce'), $this->option('title')) . '

'; $json = $this->get_secure_json_payload(); diff --git a/woocommerce-gateway-fastspring.php b/woocommerce-gateway-fastspring.php index e268f5a..e04eeb1 100644 --- a/woocommerce-gateway-fastspring.php +++ b/woocommerce-gateway-fastspring.php @@ -4,11 +4,11 @@ * Description: Take credit card payments on your store using FastSpring. * Author: Enradia * Author URI: https://enradia.com/ - * Version: 1.0.2 + * Version: 1.0.3 * Requires at least: 4.4 * Tested up to: 4.8 * WC requires at least: 3.0 - * WC tested up to: 3.1 + * WC tested up to: 3.2 * Text Domain: woocommerce-gateway-fastspring * */ @@ -202,7 +202,7 @@ public function override_checkout_fields($fields) { * @return string */ function title_order_pending($title, $endpoint) { - return __("Your order is almost complete", 'woocommerce-gateway-fastspring'); + return __("Enter Payment Info on Next Page", 'woocommerce-gateway-fastspring'); } /**