From 35ee05d8e931674be32e37eb08bfd8d7a77d1027 Mon Sep 17 00:00:00 2001 From: Marija Robinson Date: Tue, 8 Oct 2024 16:33:31 -0500 Subject: [PATCH 1/4] feature(app): add precheckout webhook endpoint --- prepaymentwebhookfunctions.php | 76 +++++++++++++++++++ settings-page.php | 6 ++ templateredirect.php | 13 +++- .../foxyshop-prepayment-webhook-endpoint.php | 59 ++++++++++++++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 prepaymentwebhookfunctions.php create mode 100644 themefiles/foxyshop-prepayment-webhook-endpoint.php diff --git a/prepaymentwebhookfunctions.php b/prepaymentwebhookfunctions.php new file mode 100644 index 0000000..c526bdf --- /dev/null +++ b/prepaymentwebhookfunctions.php @@ -0,0 +1,76 @@ + true, + 'details' => '' + ); + + $log_file = './log.txt'; + $date = new DateTime(); + $date_string = $date->format('Y-m-d H:i:s'); + + $log_line = $date_string . ': ' . $cart_details['customer_ip'] . ' - '. $cart_details['_embedded']['fx:customer']['email'] . ' -- '; + + $no_stock_products = array(); + $limited_stock_products = array(); + + foreach($cart_details['_embedded']['fx:items'] as $item) { + $product_id = -1; + $code = $item['code']; + + $inv_code = "%" . $wpdb->esc_like($code) . "%"; + $sql = $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value LIKE %s", '_inventory_levels', $inv_code ); + $meta_list = $wpdb->get_results( $sql ); + + var_dump($meta_list); + + foreach ($meta_list as $meta) { + $product_id = $meta->post_id; + $val = unserialize($meta->meta_value); + if (!is_array($val)) $val = array(); + foreach ($val as $ivcode => $iv) { + if ($ivcode == $code) { + $inventory = $iv['count']; + if ($inventory === 0) { + array_push($no_stock_products, $item['name']); + } + else if ($item['quantity'] > $inventory) { + array_push($limited_stock_products, $item['name']); + } + break; + } + } + } + } + + $response_text = ""; + + if (count($no_stock_products) > 0) { + $response_text = "Sorry, we are currently out of stock of the following " . ((count($no_stock_products) > 1) ? "items" : "item") . ": " . join(", ", $no_stock_products) . ". Please remove " . ((count($no_stock_products) > 1) ? "them" : "it") . " from your cart and try again."; + } + + if (count($limited_stock_products) > 0) { + if ($response_text) { + $response_text .= "/n"; + } + $response_text .= "Sorry, we currently have only limited stock of the following " . ((count($limited_stock_products) > 1) ? "items" : "item") . ": " . join(", ", $limited_stock_products) . ". Please reduce the quantity of " . ((count($limited_stock_products) > 1) ? "them" : "it") . " in your cart and try again."; + } + + if ($response_text) { + $response['ok'] = false; + $response['details'] = $response_text; + } + + $log_line .= (string) $response['ok'] . "\n"; + $fp = fopen($log_file, 'a'); + fwrite($fp, $log_line); + + if ($local_test === false) { + header('Content-Type: application/json'); + } + return $response; +} +?> diff --git a/settings-page.php b/settings-page.php index e1ef89c..52e7992 100755 --- a/settings-page.php +++ b/settings-page.php @@ -247,6 +247,12 @@ function foxyshop_settings_page() { FoxyCart can be configured to send order information to a url on your website. If you want to use FoxyShop's datafeed and take advantage of inventory, user management and more, copy this url and enable the datafeed in your FoxyCart admin panel. +
+ + + + FoxyCart can be configured to send order information to a url on your website for a final check before completing the order. If you want to use FoxyShop's prepayment webhook and do a final check of inventory or for a custom prepayment check, copy this url and enable the prepayment webhook in your FoxyCart admin panel. +
diff --git a/templateredirect.php b/templateredirect.php index 47ad1a9..9f468ee 100755 --- a/templateredirect.php +++ b/templateredirect.php @@ -28,7 +28,7 @@ function foxyshop_theme_redirect() { $request_arr = explode("/",$wp->request); $request_start = $request_arr[0]; $request_end = end($request_arr); - $foxyshop_indicators = array(FOXYSHOP_PRODUCTS_SLUG, FOXYSHOP_PRODUCT_CATEGORY_SLUG, 'product-search', 'foxycart-datafeed-'.$foxyshop_settings['datafeed_url_key'], 'foxycart-sso-'.$foxyshop_settings['datafeed_url_key'], 'upload-'.$foxyshop_settings['datafeed_url_key']); + $foxyshop_indicators = array(FOXYSHOP_PRODUCTS_SLUG, FOXYSHOP_PRODUCT_CATEGORY_SLUG, 'product-search', 'foxycart-datafeed-'.$foxyshop_settings['datafeed_url_key'], 'foxycart-prepayment-webhook-'.$foxyshop_settings['datafeed_url_key'], 'foxycart-sso-'.$foxyshop_settings['datafeed_url_key'], 'upload-'.$foxyshop_settings['datafeed_url_key']); if (array_intersect($request_arr, $foxyshop_indicators)) { if (in_array(FOXYSHOP_PRODUCTS_SLUG, $request_arr) && $request_end != FOXYSHOP_PRODUCTS_SLUG) { $currentProduct = $request_end; @@ -57,6 +57,8 @@ function foxyshop_theme_redirect() { $currentPageName = 'product-search'; } elseif ($request_start == 'foxycart-datafeed-'.$foxyshop_settings['datafeed_url_key']) { $currentPageName = 'foxycart-datafeed-'.$foxyshop_settings['datafeed_url_key']; + } elseif ($request_start == 'foxycart-prepayment-webhook-'.$foxyshop_settings['datafeed_url_key']) { + $currentPageName = 'foxycart-prepayment-webhook-'.$foxyshop_settings['datafeed_url_key']; } elseif ($request_start == 'foxycart-sso-'.$foxyshop_settings['datafeed_url_key']) { $currentPageName = 'foxycart-sso-'.$foxyshop_settings['datafeed_url_key']; } elseif ($request_start == 'upload-'.$foxyshop_settings['datafeed_url_key']) { @@ -141,6 +143,15 @@ function foxyshop_theme_redirect() { include foxyshop_get_template_file('foxyshop-datafeed-endpoint.php'); die; + //FoxyCart Prepayment Webhook Endpoint + } elseif ($currentPageName == 'foxycart-prepayment-webhook-'.$foxyshop_settings['datafeed_url_key'] || $currentName == 'foxycart-prepayment-webhook-'.$foxyshop_settings['datafeed_url_key']) { + add_filter('body_class', 'foxyshop_body_class', 10, 2 ); + status_header(200); + if (!defined("IS_FOXYSHOP")) define("IS_FOXYSHOP", 1); + $wp_query->is_404 = false; + include foxyshop_get_template_file('foxyshop-prepayment-webhook-endpoint.php'); + die; + //FoxyCart SSO Endpoint } elseif ($currentPageName == 'foxycart-sso-'.$foxyshop_settings['datafeed_url_key'] || $currentName == 'foxycart-sso-'.$foxyshop_settings['datafeed_url_key']) { status_header(200); diff --git a/themefiles/foxyshop-prepayment-webhook-endpoint.php b/themefiles/foxyshop-prepayment-webhook-endpoint.php new file mode 100644 index 0000000..a733009 --- /dev/null +++ b/themefiles/foxyshop-prepayment-webhook-endpoint.php @@ -0,0 +1,59 @@ + From 33549ae1f9fd3d76d6c79a818d4b7da296123078 Mon Sep 17 00:00:00 2001 From: Marija Robinson Date: Tue, 8 Oct 2024 16:36:39 -0500 Subject: [PATCH 2/4] some code cleanup --- prepaymentwebhookfunctions.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/prepaymentwebhookfunctions.php b/prepaymentwebhookfunctions.php index c526bdf..c37c80c 100644 --- a/prepaymentwebhookfunctions.php +++ b/prepaymentwebhookfunctions.php @@ -21,11 +21,9 @@ function check_inventory($cart_details, $local_test, $wpdb) { $product_id = -1; $code = $item['code']; - $inv_code = "%" . $wpdb->esc_like($code) . "%"; - $sql = $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value LIKE %s", '_inventory_levels', $inv_code ); - $meta_list = $wpdb->get_results( $sql ); - - var_dump($meta_list); + $inv_code = "%" . $wpdb->esc_like($code) . "%"; + $sql = $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value LIKE %s", '_inventory_levels', $inv_code ); + $meta_list = $wpdb->get_results( $sql ); foreach ($meta_list as $meta) { $product_id = $meta->post_id; From 431e55cfca426784483de52834a53cc074f8d510 Mon Sep 17 00:00:00 2001 From: Marija Robinson Date: Tue, 8 Oct 2024 16:39:47 -0500 Subject: [PATCH 3/4] indent cleanup --- prepaymentwebhookfunctions.php | 12 ++++++------ themefiles/foxyshop-prepayment-webhook-endpoint.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/prepaymentwebhookfunctions.php b/prepaymentwebhookfunctions.php index c37c80c..64427bc 100644 --- a/prepaymentwebhookfunctions.php +++ b/prepaymentwebhookfunctions.php @@ -62,13 +62,13 @@ function check_inventory($cart_details, $local_test, $wpdb) { $response['details'] = $response_text; } - $log_line .= (string) $response['ok'] . "\n"; - $fp = fopen($log_file, 'a'); - fwrite($fp, $log_line); + $log_line .= (string) $response['ok'] . "\n"; + $fp = fopen($log_file, 'a'); + fwrite($fp, $log_line); - if ($local_test === false) { - header('Content-Type: application/json'); - } + if ($local_test === false) { + header('Content-Type: application/json'); + } return $response; } ?> diff --git a/themefiles/foxyshop-prepayment-webhook-endpoint.php b/themefiles/foxyshop-prepayment-webhook-endpoint.php index a733009..5947247 100644 --- a/themefiles/foxyshop-prepayment-webhook-endpoint.php +++ b/themefiles/foxyshop-prepayment-webhook-endpoint.php @@ -54,6 +54,6 @@ print json_encode(check_inventory($cart_details, $local_test, $wpdb)); // add custom checks here } else { - echo("No payload found"); + echo("No payload found"); } ?> From 49a830742e2626b089d497282a2238a2db3ca610 Mon Sep 17 00:00:00 2001 From: Marija Robinson Date: Tue, 8 Oct 2024 16:44:26 -0500 Subject: [PATCH 4/4] add test file --- Tests/prepaymentwebhook_test.json | 242 ++++++++++++++++++ .../foxyshop-prepayment-webhook-endpoint.php | 2 +- 2 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 Tests/prepaymentwebhook_test.json diff --git a/Tests/prepaymentwebhook_test.json b/Tests/prepaymentwebhook_test.json new file mode 100644 index 0000000..3e81ef8 --- /dev/null +++ b/Tests/prepaymentwebhook_test.json @@ -0,0 +1,242 @@ +{ + "_links": { + }, + "_embedded": { + "fx:items": [ + { + "_links": { + }, + "_embedded": { + "fx:item_options": [ + { + "_links": { + }, + "name": "size", + "value": "medium", + "price_mod": 0, + "weight_mod": 0, + "date_created": null, + "date_modified": null + }, + { + "_links": { + }, + "name": "color", + "value": "red", + "price_mod": 0, + "weight_mod": 0, + "date_created": null, + "date_modified": null + } + ], + "fx:item_category": { + "_links": { + }, + "admin_email_template_uri": "", + "customer_email_template_uri": "", + "code": "DEFAULT", + "name": "Default for all products", + "item_delivery_type": "flat_rate", + "max_downloads_per_customer": 3, + "max_downloads_time_period": 24, + "default_weight": 0, + "default_weight_unit": "LBS", + "default_length_unit": "IN", + "shipping_flat_rate_type": "per_item", + "shipping_flat_rate": 5, + "handling_fee_type": "none", + "handling_fee": 0, + "handling_fee_minimum": 0, + "handling_fee_percentage": 0, + "customs_value": 0, + "discount_type": "", + "discount_name": "", + "discount_details": "", + "send_customer_email": false, + "send_admin_email": false, + "admin_email": "", + "date_created": null, + "date_modified": null + } + }, + "item_category_uri": "https://api.foxycart.com/item_categories/100", + "name": "Example Product", + "price": 15.99, + "quantity": 1, + "quantity_min": 0, + "quantity_max": 0, + "weight": 0, + "code": "1234", + "parent_code": "", + "discount_name": "", + "discount_type": "", + "discount_details": "", + "subscription_frequency": "", + "subscription_start_date": null, + "subscription_next_transaction_date": null, + "subscription_end_date": null, + "is_future_line_item": false, + "shipto": "Me", + "url": "", + "image": "", + "length": 0, + "width": 0, + "height": 0, + "expires": 0, + "date_created": null, + "date_modified": "2017-07-20T04:13:08-0700" + }, + { + "_links": { + }, + "_embedded": { + "fx:item_category": { + "_links": { + }, + "admin_email_template_uri": "", + "customer_email_template_uri": "", + "code": "live", + "name": "Live Rates", + "item_delivery_type": "shipped", + "max_downloads_per_customer": 3, + "max_downloads_time_period": 24, + "default_weight": 5, + "default_weight_unit": "LBS", + "default_length_unit": "IN", + "shipping_flat_rate_type": "per_order", + "shipping_flat_rate": 1, + "handling_fee_type": "none", + "handling_fee": 0, + "handling_fee_minimum": 0, + "handling_fee_percentage": 0, + "customs_value": 0, + "discount_type": "", + "discount_name": "", + "discount_details": "", + "send_customer_email": false, + "send_admin_email": false, + "admin_email": "", + "date_created": null, + "date_modified": null + } + }, + "item_category_uri": "https://api.foxycart.com/item_categories/101", + "name": "Another Product", + "price": 20, + "quantity": 1, + "quantity_min": 0, + "quantity_max": 0, + "weight": 5, + "code": "foo321", + "parent_code": "", + "discount_name": "", + "discount_type": "", + "discount_details": "", + "subscription_frequency": "", + "subscription_start_date": null, + "subscription_next_transaction_date": null, + "subscription_end_date": null, + "is_future_line_item": false, + "shipto": "Me", + "url": "", + "image": "", + "length": 0, + "width": 0, + "height": 0, + "expires": 0, + "date_created": null, + "date_modified": "2017-07-20T04:14:03-0700" + } + ], + "fx:discounts": [ + { + "code": "coupon", + "amount": -3.6, + "name": "Default Discount", + "display": "-$3.60", + "is_taxable": false, + "is_future_discount": false + } + ], + "fx:custom_fields": [ + { + "name": "custom_note", + "value": "Happy Birthday!", + "is_hidden": 0 + }], + "fx:shipment": { + "address_name": "", + "first_name": "John", + "last_name": "Smith", + "company": "", + "address1": "Main Street", + "address2": "", + "city": "Saint Paul", + "region": "MN", + "postal_code": "55116", + "country": "US", + "origin_region": "TX", + "origin_postal_code": "77018", + "origin_country": "US", + "shipping_service_id": 0, + "shipping_service_description": "", + "is_residential": false, + "item_count": 2, + "total_weight": 5, + "total_customs_value": 0, + "total_handling_fee": 0, + "total_flat_rate_shipping": 5, + "total_item_price": 35.99, + "total_tax": 3.24, + "total_shipping": 0, + "total_price": 39.23 + }, + "fx:customer": { + "id": "1512345", + "first_name": "John", + "last_name": "Smith", + "email": "john@example.com", + "tax_id": "", + "is_anonymous": "0", + "_embedded": { + "fx:payments": [ + { + "cc_type": "plastic", + "cc_number_masked": "xxxx xxxx xxxx 4242", + "cc_exp_month": "10", + "cc_exp_year": "2020", + "purchase_order": null + } + ], + "fx:default_billing_address": { + "country": "US", + "region": "MN", + "city": "Saint Paul", + "postal_code": "55116", + "address1": "Main Street", + "address2": "", + "company": "", + "full_name": "John Smith", + "first_name": "John", + "last_name": "Smith", + "phone": "" + } + } + } + }, + "customer_uri": "", + "template_set_uri": "", + "language": "", + "locale_code": "en_US", + "customer_ip": "192.168.0.1", + "ip_country": "United States", + "session_name": "fcsid", + "session_id": "hvcv28l8md0qc8qt5rrjh4qo85", + "total_item_price": 35.99, + "total_tax": 3.24, + "total_shipping": 14.23, + "total_future_shipping": 0, + "total_order": 49.86, + "date_created": null, + "date_modified": "2017-07-20T04:12:25-0700" +} \ No newline at end of file diff --git a/themefiles/foxyshop-prepayment-webhook-endpoint.php b/themefiles/foxyshop-prepayment-webhook-endpoint.php index 5947247..cc4dffd 100644 --- a/themefiles/foxyshop-prepayment-webhook-endpoint.php +++ b/themefiles/foxyshop-prepayment-webhook-endpoint.php @@ -34,7 +34,7 @@ /* To test locally: - 1. Navigate to ..themes/Tests folder + 1. Navigate to ../Tests folder within the plugin 2. Copy the prepaymentwebhook_test.json file to your theme folder 3. Modify the file to fit your test case 4. Uncomment the following which will load the cart details from sample file