From 2d43368be90bcf1bbd3eb4600965e2aaa87519b7 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Mon, 21 Nov 2016 14:16:56 -0800 Subject: [PATCH 01/41] Apple Pay: WIP --- ...-sv-wc-payment-gateway-apple-pay-admin.php | 214 ++++++ ...-payment-gateway-apple-pay-api-request.php | 82 +++ ...payment-gateway-apple-pay-api-response.php | 59 ++ ...ss-sv-wc-payment-gateway-apple-pay-api.php | 219 +++++++ ...-wc-payment-gateway-apple-pay-frontend.php | 565 ++++++++++++++++ .../class-sv-wc-payment-gateway-apple-pay.php | 614 ++++++++++++++++++ .../sv-wc-payment-gateway-apple-pay.coffee | 321 +++++++++ .../sv-wc-payment-gateway-apple-pay.min.js | 298 +++++++++ .../class-sv-wc-payment-gateway-plugin.php | 58 ++ .../class-sv-wc-payment-gateway.php | 55 ++ 10 files changed, 2485 insertions(+) create mode 100644 woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php create mode 100644 woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php create mode 100644 woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php create mode 100644 woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php create mode 100644 woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php create mode 100644 woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php create mode 100644 woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee create mode 100644 woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php new file mode 100644 index 000000000..fb9353480 --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php @@ -0,0 +1,214 @@ +handler = $handler; + + // add Apple Pay to the checkout settings sections + add_filter( 'woocommerce_get_sections_checkout', array( $this, 'add_settings_section' ), 99 ); + + // output the settings + add_action( 'woocommerce_settings_checkout', array( $this, 'add_settings' ) ); + + // save the settings + add_action( 'woocommerce_settings_save_checkout', array( $this, 'save_settings' ) ); + } + + + /** + * Adds Apple Pay to the checkout settings sections. + * + * @since 4.6.0-dev + * @param array $sections the existing sections + * @return array + */ + public function add_settings_section( $sections ) { + + $sections['apple-pay'] = __( 'Apple Pay', 'woocommerce-plugin-framework' ); + + return $sections; + } + + + /** + * Gets all of the combined settings. + * + * @since 1.0.0 + * @return array $settings The combined settings. + */ + public function get_settings() { + + $settings = array( + + array( + 'title' => __( 'Apple Pay', 'woocommerce-plugin-framework' ), + 'type' => 'title', + ), + + array( + 'id' => 'sv_wc_apple_pay_enabled', + 'title' => __( 'Enable / Disable', 'woocommerce-plugin-framework' ), + 'desc' => __( 'Accept Apple Pay', 'woocommerce-plugin-framework' ), + 'type' => 'checkbox', + 'default' => 'no', + 'checkboxgroup' => 'start', + 'show_if_checked' => 'option', + ), + + array( + 'id' => 'sv_wc_apple_pay_checkout', + 'desc' => __( 'At checkout', 'woocommerce-plugin-framework' ), + 'type' => 'checkbox', + 'default' => 'yes', + 'checkboxgroup' => '', + 'show_if_checked' => 'yes', + ), + + array( + 'id' => 'sv_wc_apple_pay_cart', + 'desc' => __( 'On the Cart page', 'woocommerce-plugin-framework' ), + 'type' => 'checkbox', + 'default' => 'no', + 'checkboxgroup' => '', + 'show_if_checked' => 'yes', + ), + + array( + 'id' => 'sv_wc_apple_pay_single_product', + 'desc' => __( 'On single product pages', 'woocommerce-plugin-framework' ), + 'type' => 'checkbox', + 'default' => 'no', + 'checkboxgroup' => '', + 'show_if_checked' => 'yes', + ), + + array( + 'type' => 'sectionend', + ), + + array( + 'title' => __( 'Connection Settings', 'woocommerce-plugin-framework' ), + 'type' => 'title', + ), + + array( + 'id' => 'sv_wc_apple_pay_merchant_id', + 'title' => __( 'Apple Merchant ID', 'woocommerce-plugin-framework' ), + 'type' => 'text', + ), + + array( + 'id' => 'sv_wc_apple_pay_cert_path', + 'title' => __( 'Certificate Path', 'woocommerce-plugin-framework' ), + 'type' => 'text', + ), + + array( + 'id' => 'sv_wc_apple_pay_payment_gateway', + 'title' => __( 'Processing Gateway', 'woocommerce-plugin-framework' ), + 'type' => 'select', + 'options' => $this->get_gateway_options(), + ), + + array( + 'type' => 'sectionend', + ), + ); + + /** + * Filter the combined settings. + * + * @since 1.0.0 + * @param array $settings The combined settings. + */ + return apply_filters( 'woocommerce_get_settings_apple_pay', $settings ); + } + + + /** + * Replace core Tax settings with our own when the AvaTax section is being viewed. + * + * @since 1.0.0 + * @return array + */ + public function add_settings() { + global $current_section; + + if ( 'apple-pay' === $current_section ) { + WC_Admin_Settings::output_fields( $this->get_settings() ); + } + } + + + /** + * Save the settings. + * + * @since 1.0.0 + * @global string $current_section The current settings section. + */ + public function save_settings() { + + global $current_section; + + // Output the general settings + if ( 'apple-pay' == $current_section ) { + + WC_Admin_Settings::save_fields( $this->get_settings() ); + } + } + + + protected function get_gateway_options() { + + $gateways = $this->handler->get_supporting_gateways(); + + foreach ( $gateways as $id => $gateway ) { + $gateways[ $id ] = $gateway->get_method_title(); + } + + return $gateways; + } + + +} diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php new file mode 100644 index 000000000..da39facd8 --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php @@ -0,0 +1,82 @@ +gateway = $gateway; + } + + + /** + * Sets the data for merchant validation. + * + * @since 4.6.0-dev + * @param string $merchant_id the merchant ID to validate + * @param string $domain_name the verified domain name + * @param string $display_name the merchant display name + */ + public function set_merchant_data( $merchant_id, $domain_name, $display_name ) { + + $data = array( + 'merchantIdentifier' => $merchant_id, + 'domainName' => 'applepay-skyverge.fwd.wf', // TODO: remove hardcode + 'displayName' => $display_name, + ); + + /** + * Filters the data for merchant validation. + * + * @since 4.6.0-dev + * @param array $data { + * The merchant data. + * + * @var string $merchantIdentifier the merchant ID + * @var string $domainName the verified domain name + * @var string $displayName the merchant display name + * } + * @param \SV_WC_Payment_Gateway_Apple_Pay_API_Request the request object + */ + $this->data = apply_filters( 'sv_wc_apple_pay_api_merchant_data', $data, $this ); + } +} diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php new file mode 100644 index 000000000..b2c2a4360 --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php @@ -0,0 +1,59 @@ +statusCode; + } + + + public function get_status_message() { + + return $this->statusMessage; + } + + + /** + * Gets the validated merchant session. + * + * @since 4.6.0-dev + * @return array + */ + public function get_merchant_session() { + + return $this->raw_response_json; + } + + +} diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php new file mode 100644 index 000000000..688fe59ed --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php @@ -0,0 +1,219 @@ +gateway = $gateway; + + $this->request_uri = 'https://apple-pay-gateway-cert.apple.com/paymentservices/startSession'; // TODO: production URL + + $this->set_request_content_type_header( 'application/json' ); + $this->set_request_accept_header( 'application/json' ); + + $this->set_response_handler( 'SV_WC_Payment_Gateway_Apple_Pay_API_Response' ); + } + + + /** + * Validates the Apple Pay merchant. + * + * @since 4.6.0-dev + * @param string $url the validation URL + * @param string $merchant_id the merchant ID to validate + * @param string $domain_name the verified domain name + * @param string $display_name the merchant display name + * @return \SV_WC_Payment_Gateway_Apple_Pay_API_Response the response object + */ + public function validate_merchant( $url, $merchant_id, $domain_name, $display_name ) { + + $this->request_uri = $url; + + $request = $this->get_new_request(); + + $request->set_merchant_data( $merchant_id, $domain_name, $display_name ); + + return $this->perform_request( $request ); + } + + + /** + * Set the PEM file required for authentication with the Global Gateway API + * + * @since 4.0.0 + * @param resource $curl_handle + */ + public function set_cert_file( $curl_handle ) { + + if ( ! $curl_handle ) { + return; + } + + curl_setopt( $curl_handle, CURLOPT_SSLCERT, get_option( 'sv_wc_apple_pay_cert_path' ) ); + } + + + /** + * Perform the remote request. + * + * WP 4.6 decided to make adding our own `curl_setopt` impossible, so we have to build a custom + * request in those cases. + * + * @since 4.1.4 + * @param string $request_uri the request URL + * @param string $request_args the request args as used by `wp_safe_remote_request()` + * @return array|WP_Error + */ + protected function do_remote_request( $request_uri, $request_args ) { + + // create a custom request for WP 4.6+ + if ( version_compare( get_bloginfo( 'version' ), '4.6', '>=' ) ) { + + $headers = $request_args['headers']; + $type = $request_args['method']; + $data = $request_args['body']; + + $options = array( + 'timeout' => $request_args['timeout'], + 'useragent' => $request_args['user-agent'], + 'blocking' => $request_args['blocking'], + 'follow_redirects' => false, + 'verify' => ABSPATH . WPINC . '/certificates/ca-bundle.crt', + 'hooks' => new Requests_Hooks(), + ); + + // set PEM file cert for requests + $options['hooks']->register( 'curl.before_send', array( $this, 'set_cert_file' ) ); + + // documented by WP in wp-includes/class-wp-http.php + $options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'] ); + + try { + + $response = Requests::request( $request_uri, $headers, $data, $type, $options ); + + // convert the response into an array + $http_response = new WP_HTTP_Requests_Response( $response ); + $response = $http_response->to_array(); + + // add the original object to the array + $response['http_response'] = $http_response; + + } catch ( Requests_Exception $e ) { + + $response = new WP_Error( 'http_request_failed', $e->getMessage() ); + } + + // otherwise, do a good old-fashioned request + } else { + + // set PEM file cert for requests + add_action( 'http_api_curl', array( $this, 'set_cert_file' ) ); + + $response = wp_safe_remote_request( $request_uri, $request_args ); + } + + return $response; + } + + + /** Validation methods ****************************************************/ + + + protected function do_pre_parse_response_validation() { + + + } + + + protected function do_post_parse_response_validation() { + + $response = $this->get_response(); + + if ( $response->get_status_code() && 200 !== $response->get_status_code() ) { + throw new SV_WC_API_Exception( $response->get_status_message() ); + } + } + + + /** Helper methods ********************************************************/ + + + /** + * Gets a new request object. + * + * @since 4.6.0-dev + * @param array $type Optional. The desired request type + * @return \SV_WC_Payment_Gateway_Apple_Pay_API_Request the request object + */ + protected function get_new_request( $type = array() ) { + + return new SV_WC_Payment_Gateway_Apple_Pay_API_Request( $this->get_gateway() ); + } + + + /** + * Gets the gateway instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway + */ + protected function get_gateway() { + + return $this->gateway; + } + + + /** + * Gets the plugin instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway_Plugin + */ + protected function get_plugin() { + + return $this->get_gateway()->get_plugin(); + } + + +} diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php new file mode 100644 index 000000000..3c65e0a9d --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -0,0 +1,565 @@ +plugin = $plugin; + + $this->handler = $handler; + + $this->gateway = $this->get_handler()->get_processing_gateway(); + + add_action( 'wp', array( $this, 'init' ) ); + + #add_action( 'wp_ajax_sv_wc_apple_pay_get_product_payment_request', array( $this, 'get_product_payment_request' ) ); + #add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_product_payment_request', array( $this, 'get_product_payment_request' ) ); + + add_action( 'wp_ajax_sv_wc_apple_pay_get_cart_payment_request', array( $this, 'get_cart_payment_request' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_cart_payment_request', array( $this, 'get_cart_payment_request' ) ); + + add_action( 'wp_ajax_sv_wc_apple_pay_get_checkout_payment_request', array( $this, 'get_checkout_payment_request' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_checkout_payment_request', array( $this, 'get_checkout_payment_request' ) ); + } + + + /** + * Initializes the scripts and hooks. + * + * @since 4.6.0-dev + */ + public function init() { + + if ( ! $this->get_handler()->is_available() ) { + return; + } + + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + + if ( is_product() ) { + #$this->init_product(); + } else if ( is_cart() && 'yes' === get_option( 'sv_wc_apple_pay_cart' ) ) { + $this->init_cart(); + } else if ( is_checkout() && 'yes' === get_option( 'sv_wc_apple_pay_checkout' ) ) { + $this->init_checkout(); + } + } + + + /** + * Enqueues the scripts. + * + * @since 4.6.0-dev + */ + public function enqueue_scripts() { + + wp_enqueue_script( 'sv-wc-apple-pay', $this->get_plugin()->get_payment_gateway_framework_assets_url() . '/js/frontend/sv-wc-payment-gateway-apple-pay.min.js', array( 'jquery' ), $this->get_plugin()->get_version(), true ); + + /** + * Filters the Apple Pay JS handler params. + * + * @since 4.6.0-dev + * @param array $params the JS params + */ + $params = apply_filters( 'sv_wc_apple_pay_js_handler_params', array( + 'gateway_id' => $this->get_gateway()->get_id(), + 'gateway_id_dasherized' => $this->get_gateway()->get_id_dasherized(), + 'merchant_id' => $this->get_handler()->get_merchant_id(), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'validate_nonce' => wp_create_nonce( 'sv_wc_apple_pay_validate_merchant' ), + 'process_nonce' => wp_create_nonce( 'sv_wc_apple_pay_process_payment' ), + ) ); + + wp_localize_script( 'sv-wc-apple-pay', 'sv_wc_apple_pay_params', $params ); + } + + + /** + * Renders an Apple Pay button. + * + * @since 4.6.0-dev + */ + public function render_button() { + + ?> + + + + + + 'sv_wc_apple_pay_get_product_payment_request', + 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_product_payment_request' ), + 'product_id' => $product->is_type( 'variable' ) ? 0 : $product->get_id(), + ) ); + + wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Product_Handler(%s);', json_encode( $args ) ) ); + + add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_button' ) ); + } + + + /** Cart functionality ****************************************************/ + + + /** + * Initializes Apple Pay on the cart page. + * + * @since 4.6.0-dev + */ + public function init_cart() { + + /** + * Filters the Apple Pay cart handler args. + * + * @since 4.6.0-dev + * @param array $args + */ + $args = apply_filters( 'sv_wc_apple_pay_cart_handler_args', array( + 'request_action' => 'sv_wc_apple_pay_get_cart_payment_request', + 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_cart_payment_request' ) + ) ); + + wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Cart_Handler(%s);', json_encode( $args ) ) ); + + add_action( 'woocommerce_proceed_to_checkout', array( $this, 'render_button' ) ); + } + + + /** + * Gets a payment request for the current cart. + * + * @since 4.6.0-dev + */ + public function get_cart_payment_request() { + + check_ajax_referer( 'sv_wc_apple_pay_get_cart_payment_request', 'nonce' ); + + try { + + $request = $this->build_cart_payment_request( WC()->cart ); + + wp_send_json( array( + 'result' => 'success', + 'request' => json_encode( $request ), + ) ); + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + wp_send_json( array( + 'result' => 'error', + 'message' => $e->getMessage(), + ) ); + } + } + + + /** + * Builds a payment request based on WC cart data. + * + * @since 4.6.0-dev + * @param \WC_Cart $cart the cart object + * @return array + */ + protected function build_cart_payment_request( WC_Cart $cart ) { + + // product line items + $line_items = array(); + + foreach ( $cart->get_cart() as $cart_item_key => $item ) { + + $line_items[] = array( + 'name' => $item['data']->get_title(), + 'quantity' => $item['quantity'], + 'amount' => $item['line_subtotal'], + ); + } + + // TODO: fees? + + // TODO: discounts? + + // order total + $total = array( + 'amount' => $cart->total, + ); + + // taxes + $args = array( + 'tax_total' => $cart->tax_total + $cart->shipping_tax_total, + ); + + if ( $cart->needs_shipping() ) { + + // shipping + $shipping_packages = WC()->shipping->get_packages(); + $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() ); + + // if shipping methods have already been chosen, simply add the total as a line item + // otherwise, we will build the list of options to choose via the Apple Pay card. + if ( ! empty( $chosen_shipping_methods ) ) { + + $args['shipping_total'] = $cart->shipping_total; + + } else if ( 1 === count( $shipping_packages ) ) { + + $package = current( $shipping_packages ); + + foreach ( $package['rates'] as $rate ) { + + $args['shipping_methods'][] = array( + 'label' => $rate->get_label(), + 'amount' => $this->format_price( $rate->cost ), + 'identifier' => $rate->id, + ); + } + + } else { + + throw new SV_WC_Payment_Gateway_Exception( __( 'No shipping totals available.', 'woocommerce-plugin-framework' ) ); + } + } + + $this->get_gateway()->add_debug_message( 'Generating Apple Pay Payment Request' ); + + // build it! + $request = $this->build_payment_request( $total, $line_items, $args ); + + /** + * Filters the Apple Pay cart JS payment request. + * + * @since 4.6.0-dev + * @param array $args the cart JS payment request + * @param \WC_Cart $cart the cart object + */ + return apply_filters( 'sv_wc_apple_pay_cart_payment_request', $request, $cart ); + } + + + /** Checkout functionality ************************************************/ + + + /** + * Initializes Apple Pay on the checkout page. + * + * @since 4.6.0-dev + */ + public function init_checkout() { + + /** + * Filters the Apple Pay checkout handler args. + * + * @since 4.6.0-dev + * @param array $args + */ + $args = apply_filters( 'sv_wc_apple_pay_checkout_handler_args', array( + 'request_action' => 'sv_wc_apple_pay_get_checkout_payment_request', + 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_checkout_payment_request' ) + ) ); + + wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Checkout_Handler(%s);', json_encode( $args ) ) ); + + add_action( 'woocommerce_review_order_before_payment', array( $this, 'render_button' ) ); + } + + + /** + * Gets a payment request for the checkout. + * + * @since 4.6.0-dev + */ + public function get_checkout_payment_request() { + + check_ajax_referer( 'sv_wc_apple_pay_get_checkout_payment_request', 'nonce' ); + + try { + + $request = $this->build_cart_payment_request( WC()->cart ); + + wp_send_json( array( + 'result' => 'success', + 'request' => json_encode( $request ), + ) ); + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + wp_send_json( array( + 'result' => 'error', + 'message' => $e->getMessage(), + ) ); + } + } + + + /** + * Builds an Apple Pay payment request. + * + * This contains all of the data necessary to complete a payment, including + * line items and shipping info. + * + * @since 4.6.0-dev + * @param array $total { + * The payment total. + * + * @type string $label the total label. Defaults to the site name + * @type string $amount the total payment amount + * } + * @param array $line_items { + * The order line items. + * + * @type string $name the line item name (usually a WC product title) + * @type string $amount the line subtotal + * } + * @param array $args { + * Optional. The payment request args. + * + * @type string $currency_code the payment currency code. Defaults to the shop currency. + * @type string $country_code the payment country code. Defaults to the shop base country. + * @type array $merchant_capabilities the merchant capabilities + * @type array $supported_networks the supported networks or card types + * } + * @return array + */ + protected function build_payment_request( $total, $line_items, $args = array() ) { + + $args = wp_parse_args( $args, array( + 'currency_code' => get_woocommerce_currency(), + 'country_code' => get_option( 'woocommerce_default_country' ), + 'merchant_capabilities' => $this->get_handler()->get_capabilities(), + 'supported_networks' => $this->get_handler()->get_supported_networks(), + ) ); + + // set the base required defaults + $request = array( + 'currencyCode' => $args['currency_code'], + 'countryCode' => substr( $args['country_code'], 0, 2 ), + 'merchantCapabilities' => $args['merchant_capabilities'], + 'supportedNetworks' => $args['supported_networks'], + 'requiredBillingContactFields' => array( 'postalAddress' ), + 'requiredShippingContactFields' => array( + 'phone', + 'email', + 'name', + ), + ); + + // if a shipping address is required + if ( ! empty( $args['shipping_methods'] ) || ! empty( $args['shipping_total'] ) ) { + $request['requiredShippingContactFields'][] = 'postalAddress'; + } + + foreach ( $line_items as $key => $item ) { + + $label = $item['name']; + + // add the item quantity if more than one + if ( ! empty( $item['quantity'] ) && 1 < $item['quantity'] ) { + $label .= ' (x' . (int) $item['quantity'] . ')'; + } + + $line_items[ $key ] = array( + 'type' => 'final', + 'label' => $label, + 'amount' => $this->format_price( $item['amount'] ), + ); + } + + // taxes + if ( ! empty( $args['tax_total'] ) ) { + + $line_items['taxes'] = array( + 'type' => 'final', + 'label' => __( 'Taxes', 'woocommerce-plugin-framework' ), + 'amount' => $this->format_price( $args['tax_total'] ), + ); + } + + // shipping + if ( ! empty( $args['shipping_methods'] ) ) { + + $request['shippingMethods'] = $args['shipping_methods']; + + } else if ( ! empty( $args['shipping_total'] ) ) { + + $line_items['shipping'] = array( + 'type' => 'final', + 'label' => __( 'Shipping', 'woocommerce-plugin-framework' ), + 'amount' => $this->format_price( $args['shipping_total'] ), + ); + } + + $request['lineItems'] = array_values( $line_items ); + + $request['total'] = wp_parse_args( $total, array( + 'label' => get_bloginfo( 'name', 'display' ), + 'amount' => 0, + ) ); + + $request['total']['amount'] = $this->format_price( $request['total']['amount'] ); + + // log the payment request + $this->get_gateway()->add_debug_message( "Apple Pay Payment Request:\n" . print_r( $request, true ) ); + + return $request; + } + + + /** + * Formats a total price for use with Apple Pay JS. + * + * @since 4.6.0-dev + * @param string|float $price the price to format + * @return string + */ + protected function format_price( $price ) { + + return wc_format_decimal( $price, 2 ); + } + + + /** + * Gets the JS handler args. + * + * @since 4.6.0-dev + * @return array { + * The handler arguments. + * + * @type string $gateway_id the processing gateway's ID + * @type string $gateway_id_dasherized the processing gateway's dasherized ID + * @type string $merchant_id the Apple merchant ID + * } + */ + protected function get_js_args() { + + /** + * Filters the Apple Pay JS handler args. + * + * @since 4.6.0-dev + * @param array $args the JS handler args + * @param \SV_WC_Payment_Gateway $gateway the processing gateway instance + */ + return apply_filters( 'sv_wc_apple_pay_js_args', array(), $this->get_gateway() ); + } + + + /** + * Gets the gateway instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway + */ + protected function get_gateway() { + + return $this->gateway; + } + + + /** + * Gets the gateway plugin instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway_Plugin + */ + protected function get_plugin() { + + return $this->plugin; + } + + /** + * Gets the Apple Pay handler instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway_Apple_Pay + */ + protected function get_handler() { + + return $this->handler; + } + + +} diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php new file mode 100644 index 000000000..41f389de4 --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -0,0 +1,614 @@ +plugin = $plugin; + + $this->init(); + + // validate a merchant via AJAX + add_action( 'wp_ajax_sv_wc_apple_pay_validate_merchant', array( $this, 'validate_merchant' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_validate_merchant', array( $this, 'validate_merchant' ) ); + + // process the payment via AJAX + add_action( 'wp_ajax_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); + + add_filter( 'wc_payment_gateway_' . $this->get_processing_gateway()->get_id() . '_get_order', array( $this, 'add_order_data' ) ); + } + + + /** + * Initializes the Apple Pay handlers. + * + * @since 4.6.0-dev + */ + protected function init() { + + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php'); + + if ( is_admin() && ! is_ajax() ) { + $this->admin = new SV_WC_Payment_Gateway_Apple_Pay_Admin( $this ); + } else { + $this->frontend = new SV_WC_Payment_Gateway_Apple_Pay_Frontend( $this->get_plugin(), $this ); + } + } + + + /** + * Validates a merchant via AJAX. + * + * @since 4.6.0-dev + */ + public function validate_merchant() { + + check_ajax_referer( 'sv_wc_apple_pay_validate_merchant', 'nonce' ); + + $merchant_id = SV_WC_Helper::get_post( 'merchant_id' ); + $url = SV_WC_Helper::get_post( 'url' ); + + try { + + $response = $this->get_api()->validate_merchant( $url, $merchant_id, home_url(), get_bloginfo( 'name' ) ); + + wp_send_json( array( + 'result' => 'success', + 'merchant_session' => $response->get_merchant_session(), + ) ); + + } catch ( SV_WC_API_Exception $e ) { + + $this->get_processing_gateway()->add_debug_message( 'Apple Pay API error. ' . $e->getMessage() ); + + wp_send_json( array( + 'result' => 'error', + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + ) ); + } + } + + + /** + * Processes the payment after the Apple Pay authorization. + * + * @since 4.6.0-dev + * @throws \SV_WC_Payment_Gateway_Exception + */ + public function process_payment() { + + $payment = json_decode( stripslashes( SV_WC_Helper::get_post( 'payment' ) ) ); + + try { + + if ( ! $payment ) { + throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment data recieved' ); // TODO + } + + $this->payment_data = $payment; + + // log the payment response + $this->get_processing_gateway()->add_debug_message( "Apple Pay Payment Response:\n" . print_r( $payment, true ) ); + + // create a new order + $order = $this->create_cart_order(); + + $billing_address = $shipping_address = array(); + + // set the billing address + if ( isset( $payment->billingContact ) ) { + + if ( ! empty( $payment->billingContact->givenName ) ) { + $billing_address['first_name'] = $payment->billingContact->givenName; + $billing_address['last_name'] = $payment->billingContact->familyName; + } + + if ( ! empty( $payment->billingContact->addressLines ) ) { + + $billing_address = array_merge( $billing_address, array( + 'address_1' => $payment->billingContact->addressLines[0], + 'address_2' => ! empty( $payment->billingContact->addressLines[1] ) ? $payment->billingContact->addressLines[1] : '', + 'city' => $payment->billingContact->locality, + 'state' => $payment->billingContact->administrativeArea, + 'postcode' => $payment->billingContact->postalCode, + 'country' => $payment->billingContact->countryCode, + ) ); + } + + // default the shipping address to the billing address + $shipping_address = $billing_address; + } + + // set the shipping address + if ( isset( $payment->shippingContact ) ) { + + if ( isset( $payment->shippingContact->givenName ) ) { + $shipping_address['first_name'] = $payment->shippingContact->givenName; + $shipping_address['last_name'] = $payment->shippingContact->familyName; + } + + if ( ! empty( $payment->shippingContact->addressLines ) ) { + $shipping_address = array_merge( $shipping_address, array( + 'address_1' => $payment->shippingContact->addressLines[0], + 'address_2' => ! empty( $payment->shippingContact->addressLines[1] ) ? $payment->shippingContact->addressLines[1] : '', + 'city' => $payment->shippingContact->locality, + 'state' => $payment->shippingContact->administrativeArea, + 'postcode' => $payment->shippingContact->postalCode, + 'country' => $payment->shippingContact->countryCode, + ) ); + } + + // set the billing email + if ( ! empty( $payment->shippingContact->emailAddress ) ) { + $billing_address['email'] = $payment->shippingContact->emailAddress; + } + + // set the billing phone number + if ( ! empty( $payment->shippingContact->phoneNumber ) ) { + $billing_address['phone'] = $payment->shippingContact->phoneNumber; + } + } + + $order->set_address( $billing_address, 'billing' ); + $order->set_address( $shipping_address, 'shipping' ); + + // process the payment via the gateway + $result = $this->get_processing_gateway()->process_payment( $order->id ); + + wp_send_json( $result ); + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + $this->get_processing_gateway()->add_debug_message( 'Apple Pay payment failed. ' . $e->getMessage() ); + + wp_send_json( array( + 'result' => 'error', + 'message' => $e->getMessage(), + ) ); + } + } + + + /** + * Allows the processing gateway to add Apple Pay details to the payment data. + * + * @since 4.6.0-dev + * @param \WC_Order $order the order object + * @return \WC_Order + */ + public function add_order_data( $order ) { + + $order = $this->get_processing_gateway()->add_apple_pay_order_data( $order, $this->payment_data ); + + return $order; + } + + + /** + * Creates an order from the current cart. + * + * @since 4.6.0-dev + * @throws \SV_WC_Plugin_Exception + */ + public function create_cart_order() { + + $items = array(); + + foreach ( WC()->cart->get_cart() as $cart_item_key => $item ) { + + $items[ $cart_item_key ] = array( + 'product' => $item['data'], + 'quantity' => $item['quantity'], + 'args' => array( + 'variation' => $item['variation'], + 'totals' => array( + 'subtotal' => $item['line_subtotal'], + 'subtotal_tax' => $item['line_subtotal_tax'], + 'total' => $item['line_total'], + 'tax' => $item['line_tax'], + 'tax_data' => $item['line_tax_data'] + ), + ), + 'values' => $item, + ); + } + + $args = array( + 'fees' => WC()->cart->get_fees(), + ); + + foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) { + + $args['taxes'][ $tax_rate_id ] = array( + 'amount' => WC()->cart->get_tax_amount( $tax_rate_id ), + 'shipping_amount' => WC()->cart->get_shipping_tax_amount( $tax_rate_id ), + ); + } + + foreach ( WC()->shipping->get_packages() as $key => $package ) { + + $args['packages'][ $key ] = array( + + ); + } + + foreach ( WC()->cart->get_coupons() as $code => $coupon ) { + + $args['coupons'][ $code ] = array( + 'amount' => WC()->cart->get_coupon_discount_amount( $code ), + 'tax_amount' => WC()->cart->get_coupon_discount_tax_amount( $code ), + ); + } + + $order = $this->create_order( $items, $args ); + + return $order; + } + + + /** + * Creates a new order from provided data. + * + * @since 4.6.0-dev + * @param array $args the order args + * @throws \SV_WC_Plugin_Exception + */ + public function create_order( $items, $args ) { + + $args = wp_parse_args( $args, array( + 'customer_id' => get_current_user_id(), + 'fees' => array(), + 'packages' => array(), + 'coupons' => array(), + 'billing_address' => array(), + 'shipping_address' => array(), + ) ); + + try { + + wc_transaction_query( 'start' ); + + $order_data = array( + 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), + 'customer_id' => $args['customer_id'], + 'created_via' => 'apple_pay', + ); + + $order = wc_create_order( $order_data ); + + if ( is_wp_error( $order ) ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); + } elseif ( false === $order ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); + } else { + + $order_id = $order->id; + + do_action( 'woocommerce_new_order', $order_id ); + } + + $order->set_payment_method( $this->get_processing_gateway()->get_id() ); + + foreach ( $items as $key => $item ) { + + $item_id = $order->add_product( $item['product'], $item['quantity'], $item['args'] ); + + if ( ! $item_id ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 525 ) ); + } + + do_action( 'woocommerce_add_order_item_meta', $item_id, $item['values'], $key ); + } + + foreach ( $args['fees'] as $key => $fee ) { + + $item_id = $order->add_fee( $fee ); + + if ( ! $item_id ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 526 ) ); + } + + do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $key ); + } + + foreach ( $args['packages'] as $key => $package ) { + + $shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); + + if ( isset( $package['rates'][ $shipping_methods[ $key ] ] ) ) { + + $item_id = $order->add_shipping( $package['rates'][ $shipping_methods[ $key ] ] ); + + if ( ! $item_id ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 527 ) ); + } + + do_action( 'woocommerce_add_shipping_order_item', $order_id, $item_id, $key ); + } + } + + foreach ( $args['coupons'] as $code => $coupon ) { + + if ( ! $order->add_coupon( $code, $coupon['amount'], $coupon['tax_amount'] ) ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 529 ) ); + } + } + + $order->set_address( $args['billing_address'], 'billing' ); + $order->set_address( $args['shipping_address'], 'shipping' ); + + $order->calculate_totals(); + + wc_transaction_query( 'commit' ); + + return $order; + + } catch ( SV_WC_Plugin_Exception $e ) { + + wc_transaction_query( 'rollback' ); + + throw $e; + } + } + + + /** + * Gets the Apple Pay API. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway_Apple_Pay_API + */ + protected function get_api() { + + if ( ! $this->api instanceof SV_WC_Payment_Gateway_Apple_Pay_API ) { + + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php'); + + $this->api = new SV_WC_Payment_Gateway_Apple_Pay_API( $this->get_processing_gateway() ); + } + + return $this->api; + } + + + /** + * Determines if Apple Pay is available. + * + * This does not indicate browser support or a user's ability, but rather + * that Apple Pay is properly configured and ready to be initiated by the + * Apple Pay JS. + * + * @since 4.6.0-dev + * @return bool + */ + public function is_available() { + + /** + * Filters whether Apple Pay should be made available to users. + * + * @since 4.6.0-dev + * @param bool $is_available + */ + return apply_filters( 'sv_wc_apple_pay_is_available', $this->is_configured() ); + } + + + /** + * Determines if Apple Pay settings are properly configured. + * + * @since 4.6.0-dev + * @return bool + */ + protected function is_configured() { + + $is_configured = $this->is_enabled() && $this->get_merchant_id() && $this->get_processing_gateway() && $this->get_processing_gateway()->is_enabled(); + + $is_configured = $is_configured && $this->get_cert_path() && is_readable( $this->get_cert_path() ); + + return $is_configured; + } + + + /** + * Determines if Apple Pay is enabled. + * + * @since 4.6.0-dev + * @return bool + */ + protected function is_enabled() { + + return 'yes' === get_option( 'sv_wc_apple_pay_enabled' ); + } + + + /** + * Gets the configured Apple merchant ID. + * + * @since 4.6.0-dev + * @return string + */ + public function get_merchant_id() { + + return get_option( 'sv_wc_apple_pay_merchant_id' ); + } + + + /** + * Gets the certificate file path. + * + * @since 4.6.0-dev + * @return string + */ + public function get_cert_path() { + + return get_option( 'sv_wc_apple_pay_cert_path' ); + } + + + /** + * Gets the gateway's Apple Pay capabilities. + * + * @since 4.6.0-dev + * @return array + */ + public function get_capabilities() { + + $valid_capabilities = array( + 'supports3DS', + 'supportsEMV', + 'supportsCredit', + 'supportsDebit', + ); + + $capabilities = array_intersect( $valid_capabilities, $this->get_processing_gateway()->get_apple_pay_capabilities() ); + + /** + * Filters the gateway's Apple Pay capabilities. + * + * @since 4.6.0-dev + * @param array $capabilities the gateway capabilities + * @param \SV_WC_Payment_Gateway_Apple_Pay $handler the Apple Pay handler + */ + return apply_filters( 'sv_wc_apple_pay_capabilities', array_values( $capabilities ), $this ); + } + + + /** + * Gets the supported networks for Apple Pay. + * + * @since 4.6.0-dev + * @return array + */ + public function get_supported_networks() { + + $accepted_card_types = array_map( 'SV_WC_Payment_Gateway_Helper::normalize_card_type', $this->get_processing_gateway()->get_card_types() ); + + $valid_networks = array( + SV_WC_Payment_Gateway_Helper::CARD_TYPE_AMEX => 'amex', + SV_WC_Payment_Gateway_Helper::CARD_TYPE_DISCOVER => 'discover', + SV_WC_Payment_Gateway_Helper::CARD_TYPE_MASTERCARD => 'masterCard', + SV_WC_Payment_Gateway_Helper::CARD_TYPE_VISA => 'visa', + 'privateLabel' => 'privateLabel', // ? + ); + + $networks = array_intersect_key( $valid_networks, array_flip( $accepted_card_types ) ); + + /** + * Filters the supported Apple Pay networks (card types). + * + * @since 4.6.0-dev + * @param array $networks the supported networks + * @param \SV_WC_Payment_Gateway_Apple_Pay $handler the Apple Pay handler + */ + return apply_filters( 'sv_wc_apple_pay_supported_networks', array_values( $networks ), $this ); + } + + + /** + * Gets the gateways that declare Apple Pay support. + * + * @since 4.6.0-dev + * @return array the supporting gateways as `$gateway_id => \SV_WC_Payment_Gateway` + */ + public function get_supporting_gateways() { + + $available_gateways = $this->get_plugin()->get_gateways(); + $supporting_gateways = array(); + + foreach ( $available_gateways as $key => $gateway ) { + + if ( $gateway->supports_apple_pay() ) { + $supporting_gateways[ $gateway->get_id() ] = $gateway; + } + } + + return $supporting_gateways; + } + + + /** + * Gets the gateway set to process Apple Pay transactions. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway|null + */ + public function get_processing_gateway() { + + $gateways = $this->get_supporting_gateways(); + + $gateway_id = get_option( 'sv_wc_apple_pay_payment_gateway' ); + + return isset( $gateways[ $gateway_id ] ) ? $gateways[ $gateway_id ] : null; + } + + + /** + * Gets the gateway plugin instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway_Plugin + */ + public function get_plugin() { + + return $this->plugin; + } + + +} diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee new file mode 100644 index 000000000..6bc0ed138 --- /dev/null +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee @@ -0,0 +1,321 @@ +### + WooCommerce Apple Pay Handler + Version 4.6.0-dev + + Copyright (c) 2016, SkyVerge, Inc. + Licensed under the GNU General Public License v3.0 + http://www.gnu.org/licenses/gpl-3.0.html +### + +jQuery( document ).ready ($) -> + + "use strict" + + # The WooCommerce Apple Pay handler base class. + # + # @since 4.6.0-dev + class window.SV_WC_Apple_Pay_Handler + + + # Constructs the handler. + # + # @since 3.9.2-1 + constructor: (args) -> + + @params = sv_wc_apple_pay_params + + @request_action = args.request_action + @request_nonce = args.request_nonce + + if this.is_available() + + this.init() + + + # Determines if Apple Pay is available. + # + # @since 3.9.2-1 + # @return bool + is_available: -> + + return false unless window.ApplePaySession + + ApplePaySession.canMakePaymentsWithActiveCard( @params.merchant_id ).then ( canMakePayments ) => + + return canMakePayments + + + init: -> + + this.block_ui() + + @buttons = $( '.sv-wc-apple-pay-button' ) + + this.get_payment_request().then ( response ) => + + @payment_request = $.parseJSON( response ) + + if @payment_request + + @buttons.show().prop( 'disabled', false ) + + this.unblock_ui() + + , ( response ) => + + console.log '[Apple Pay Error] ' + response + + this.unblock_ui() + + $( document.body ).on 'click', '.sv-wc-apple-pay-button:not([disabled])', ( e ) => + + e.preventDefault() + + this.block_ui() + + try + + @session = new ApplePaySession( 1, @payment_request ) + + @session.onvalidatemerchant = ( event ) => this.on_validate_merchant( event ) + + @session.onpaymentauthorized = ( event ) => this.on_process_authorization( event ) + + @session.oncancel = ( event ) => this.on_cancel_payment( event ) + + @session.begin() + + catch error + + @session.abort() + + this.fail_payment( error ) + + + # Gets the payment request via AJAX. + # + # @since 4.6.0-dev + get_payment_request: => new Promise ( resolve, reject ) => + + data = { + 'action': @request_action + 'nonce': @request_nonce + 'product_id': @product_id + } + + # retrieve a payment request object + $.post @params.ajax_url, data, ( response ) => + + if response.result is 'success' + resolve response.request + else + reject response.message + + + on_validate_merchant: ( event ) => + + this.validate_merchant( event.validationURL ).then ( merchant_session ) => + + merchant_session = $.parseJSON( merchant_session ) + + @session.completeMerchantValidation( merchant_session ) + + , ( error ) => + + @session.abort() + + this.fail_payment 'Merchant could no be validated. ' + error + + + # Validates the merchant data. + # + # @since 3.9.2-1 + # @return object + validate_merchant: ( url ) => new Promise ( resolve, reject ) => + + data = { + 'action': 'sv_wc_apple_pay_validate_merchant', + 'nonce': @params.validate_nonce, + 'merchant_id': @params.merchant_id, + 'url': url + } + + # retrieve a payment request object + $.post @params.ajax_url, data, ( response ) => + + if response.result is 'success' + resolve response.merchant_session + else + reject response.message + + + on_process_authorization: ( event ) => + + this.process_authorization( event.payment ).then ( response ) => + + this.set_payment_status( response.result ) + + this.complete_purchase( response ) + + , ( error ) => + + this.set_payment_status( false ) + + this.fail_payment 'Payment could no be processed. ' + error + + + # Processes the transaction data after the payment is authorized. + # + # @since 3.9.2-1 + process_authorization: ( payment ) => new Promise ( resolve, reject ) => + + data = { + action: 'sv_wc_apple_pay_process_payment', + nonce: @params.process_nonce, + payment: JSON.stringify( payment ) + } + + $.post @params.ajax_url, data, ( response ) => + + if response.result is 'success' + resolve response + else + reject response.message + + + on_cancel_payment: ( event ) => + + this.unblock_ui() + + + complete_purchase: ( response ) -> + + window.location = response.redirect + + + fail_payment: ( error ) -> + + console.log '[Apple Pay Error] ' + error + + this.unblock_ui() + + this.render_errors( ['An error occurred'] ) # localize + + + # Sets the Apple Pay payment status depending on the processing result. + # + # @since 3.9.2-1 + set_payment_status: ( result ) -> + + if result is 'success' + status = ApplePaySession.STATUS_SUCCESS + else + status = ApplePaySession.STATUS_FAILURE + + @session.completePayment( status ) + + + # Public: Render any new errors and bring them into the viewport + # + # Returns nothing. + render_errors: ( errors ) -> + + # hide and remove any previous errors + $( '.woocommerce-error, .woocommerce-message' ).remove() + + # add errors + @payment_form.prepend '' + + # unblock UI + @payment_form.removeClass( 'processing' ).unblock() + + # scroll to top + $( 'html, body' ).animate( { scrollTop: @payment_form.offset().top - 100 }, 1000 ) + + + # Blocks the payment form UI + # + # @since 4.6.0-dev + block_ui: -> @payment_form.addClass( 'processing' ).block( message: null, overlayCSS: background: '#fff',opacity: 0.6 ) + + + # Unblocks the payment form UI + # + # @since 4.6.0-dev + unblock_ui: -> @payment_form.removeClass( 'processing' ).unblock() + + + # The WooCommerce Apple Pay cart handler class. + # + # @since 4.6.0-dev + class window.SV_WC_Apple_Pay_Cart_Handler extends SV_WC_Apple_Pay_Handler + + + # Constructs the handler. + # + # @since 3.9.2-1 + constructor: (args) -> + + @payment_form = $( '.cart_totals' ) + + super(args) + + # re-init if the cart totals are updated + $( document.body ).on( 'updated_cart_totals', => this.init() ) + + + # The WooCommerce Apple Pay checkout handler class. + # + # @since 4.6.0-dev + class window.SV_WC_Apple_Pay_Checkout_Handler extends SV_WC_Apple_Pay_Handler + + + # Constructs the handler. + # + # @since 3.9.2-1 + constructor: (args) -> + + @payment_form = $( 'form.woocommerce-checkout' ) + + super(args) + + # re-init if the cart totals are updated + $( document.body ).on( 'update_checkout', => this.init() ) + + + # The WooCommerce Apple Pay product handler class. + # + # @since 4.6.0-dev + class window.SV_WC_Apple_Pay_Product_Handler extends SV_WC_Apple_Pay_Handler + + + # Constructs the handler. + # + # @since 3.9.2-1 + constructor: (args) -> + + @payment_form = $( 'form.cart' ) + + @product_id = args.product_id + + super(args) + + # re-init if the varation form is updated + $( document.body ).on( 'show_variation', ( event, variation, purchasable ) => this.init_variations( variation.variation_id, purchasable ) ) + + + init_variations: ( variation_id, purchasable ) => + + if variation_id and purchasable + @product_id = variation_id + else + @product_id = 0 + + this.init() + + + init: => + + return if @product_id is 0 + + super() diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js new file mode 100644 index 000000000..7ff95f3e9 --- /dev/null +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js @@ -0,0 +1,298 @@ + +/* + WooCommerce Apple Pay Handler + Version 4.6.0-dev + + Copyright (c) 2016, SkyVerge, Inc. + Licensed under the GNU General Public License v3.0 + http://www.gnu.org/licenses/gpl-3.0.html + */ + +(function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + jQuery(document).ready(function($) { + "use strict"; + window.SV_WC_Apple_Pay_Handler = (function() { + function SV_WC_Apple_Pay_Handler(args) { + this.on_cancel_payment = bind(this.on_cancel_payment, this); + this.process_authorization = bind(this.process_authorization, this); + this.on_process_authorization = bind(this.on_process_authorization, this); + this.validate_merchant = bind(this.validate_merchant, this); + this.on_validate_merchant = bind(this.on_validate_merchant, this); + this.get_payment_request = bind(this.get_payment_request, this); + this.params = sv_wc_apple_pay_params; + this.request_action = args.request_action; + this.request_nonce = args.request_nonce; + if (this.is_available()) { + this.init(); + } + } + + SV_WC_Apple_Pay_Handler.prototype.is_available = function() { + if (!window.ApplePaySession) { + return false; + } + return ApplePaySession.canMakePaymentsWithActiveCard(this.params.merchant_id).then((function(_this) { + return function(canMakePayments) { + return canMakePayments; + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.init = function() { + this.block_ui(); + this.buttons = $('.sv-wc-apple-pay-button'); + this.get_payment_request().then((function(_this) { + return function(response) { + _this.payment_request = $.parseJSON(response); + if (_this.payment_request) { + _this.buttons.show().prop('disabled', false); + return _this.unblock_ui(); + } + }; + })(this), (function(_this) { + return function(response) { + console.log('[Apple Pay Error] ' + response); + return _this.unblock_ui(); + }; + })(this)); + return $(document.body).on('click', '.sv-wc-apple-pay-button:not([disabled])', (function(_this) { + return function(e) { + var error; + e.preventDefault(); + _this.block_ui(); + try { + _this.session = new ApplePaySession(1, _this.payment_request); + _this.session.onvalidatemerchant = function(event) { + return _this.on_validate_merchant(event); + }; + _this.session.onpaymentauthorized = function(event) { + return _this.on_process_authorization(event); + }; + _this.session.oncancel = function(event) { + return _this.on_cancel_payment(event); + }; + return _this.session.begin(); + } catch (_error) { + error = _error; + _this.session.abort(); + return _this.fail_payment(error); + } + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.get_payment_request = function() { + return new Promise((function(_this) { + return function(resolve, reject) { + var data; + data = { + 'action': _this.request_action, + 'nonce': _this.request_nonce, + 'product_id': _this.product_id + }; + return $.post(_this.params.ajax_url, data, function(response) { + if (response.result === 'success') { + return resolve(response.request); + } else { + return reject(response.message); + } + }); + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.on_validate_merchant = function(event) { + return this.validate_merchant(event.validationURL).then((function(_this) { + return function(merchant_session) { + merchant_session = $.parseJSON(merchant_session); + return _this.session.completeMerchantValidation(merchant_session); + }; + })(this), (function(_this) { + return function(error) { + _this.session.abort(); + return _this.fail_payment('Merchant could no be validated. ' + error); + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.validate_merchant = function(url) { + return new Promise((function(_this) { + return function(resolve, reject) { + var data; + data = { + 'action': 'sv_wc_apple_pay_validate_merchant', + 'nonce': _this.params.validate_nonce, + 'merchant_id': _this.params.merchant_id, + 'url': url + }; + return $.post(_this.params.ajax_url, data, function(response) { + if (response.result === 'success') { + return resolve(response.merchant_session); + } else { + return reject(response.message); + } + }); + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.on_process_authorization = function(event) { + return this.process_authorization(event.payment).then((function(_this) { + return function(response) { + _this.set_payment_status(response.result); + return _this.complete_purchase(response); + }; + })(this), (function(_this) { + return function(error) { + _this.set_payment_status(false); + return _this.fail_payment('Payment could no be processed. ' + error); + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.process_authorization = function(payment) { + return new Promise((function(_this) { + return function(resolve, reject) { + var data; + data = { + action: 'sv_wc_apple_pay_process_payment', + nonce: _this.params.process_nonce, + payment: JSON.stringify(payment) + }; + return $.post(_this.params.ajax_url, data, function(response) { + if (response.result === 'success') { + return resolve(response); + } else { + return reject(response.message); + } + }); + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.on_cancel_payment = function(event) { + return this.unblock_ui(); + }; + + SV_WC_Apple_Pay_Handler.prototype.complete_purchase = function(response) { + return window.location = response.redirect; + }; + + SV_WC_Apple_Pay_Handler.prototype.fail_payment = function(error) { + console.log('[Apple Pay Error] ' + error); + this.unblock_ui(); + return this.render_errors(['An error occurred']); + }; + + SV_WC_Apple_Pay_Handler.prototype.set_payment_status = function(result) { + var status; + if (result === 'success') { + status = ApplePaySession.STATUS_SUCCESS; + } else { + status = ApplePaySession.STATUS_FAILURE; + } + return this.session.completePayment(status); + }; + + SV_WC_Apple_Pay_Handler.prototype.render_errors = function(errors) { + $('.woocommerce-error, .woocommerce-message').remove(); + this.payment_form.prepend(''); + this.payment_form.removeClass('processing').unblock(); + return $('html, body').animate({ + scrollTop: this.payment_form.offset().top - 100 + }, 1000); + }; + + SV_WC_Apple_Pay_Handler.prototype.block_ui = function() { + return this.payment_form.addClass('processing').block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + }; + + SV_WC_Apple_Pay_Handler.prototype.unblock_ui = function() { + return this.payment_form.removeClass('processing').unblock(); + }; + + return SV_WC_Apple_Pay_Handler; + + })(); + window.SV_WC_Apple_Pay_Cart_Handler = (function(superClass) { + extend(SV_WC_Apple_Pay_Cart_Handler, superClass); + + function SV_WC_Apple_Pay_Cart_Handler(args) { + this.payment_form = $('.cart_totals'); + SV_WC_Apple_Pay_Cart_Handler.__super__.constructor.call(this, args); + $(document.body).on('updated_cart_totals', (function(_this) { + return function() { + return _this.init(); + }; + })(this)); + } + + return SV_WC_Apple_Pay_Cart_Handler; + + })(SV_WC_Apple_Pay_Handler); + window.SV_WC_Apple_Pay_Checkout_Handler = (function(superClass) { + extend(SV_WC_Apple_Pay_Checkout_Handler, superClass); + + function SV_WC_Apple_Pay_Checkout_Handler(args) { + this.payment_form = $('form.woocommerce-checkout'); + SV_WC_Apple_Pay_Checkout_Handler.__super__.constructor.call(this, args); + $(document.body).on('update_checkout', (function(_this) { + return function() { + return _this.init(); + }; + })(this)); + } + + return SV_WC_Apple_Pay_Checkout_Handler; + + })(SV_WC_Apple_Pay_Handler); + return window.SV_WC_Apple_Pay_Product_Handler = (function(superClass) { + extend(SV_WC_Apple_Pay_Product_Handler, superClass); + + function SV_WC_Apple_Pay_Product_Handler(args) { + this.init = bind(this.init, this); + this.init_variations = bind(this.init_variations, this); + this.payment_form = $('form.cart'); + this.product_id = args.product_id; + SV_WC_Apple_Pay_Product_Handler.__super__.constructor.call(this, args); + $(document.body).on('show_variation', (function(_this) { + return function(event, variation, purchasable) { + return _this.init_variations(variation.variation_id, purchasable); + }; + })(this)); + } + + SV_WC_Apple_Pay_Product_Handler.prototype.init_variations = function(variation_id, purchasable) { + if (variation_id && purchasable) { + this.product_id = variation_id; + } else { + this.product_id = 0; + } + return this.init(); + }; + + SV_WC_Apple_Pay_Product_Handler.prototype.init = function() { + if (this.product_id === 0) { + return; + } + return SV_WC_Apple_Pay_Product_Handler.__super__.init.call(this); + }; + + return SV_WC_Apple_Pay_Product_Handler; + + })(SV_WC_Apple_Pay_Handler); + }); + +}).call(this); + +//# sourceMappingURL=sv-wc-payment-gateway-apple-pay.min.js.map diff --git a/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php b/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php index 878507fd9..6c44e2af3 100644 --- a/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php +++ b/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php @@ -91,6 +91,9 @@ abstract class SV_WC_Payment_Gateway_Plugin extends SV_WC_Plugin { /** @var SV_WC_Payment_Gateway_My_Payment_Methods adds My Payment Method functionality */ private $my_payment_methods; + /** @var \SV_WC_Payment_Gateway_Apple_Pay the Apple Pay handler instance */ + private $apple_pay; + /** * Initialize the plugin @@ -136,6 +139,9 @@ public function __construct( $id, $version, $args ) { add_action( 'wp', array( $this, 'maybe_init_my_payment_methods' ) ); } + // Apple Pay feature + add_action( 'init', array( $this, 'maybe_init_apple_pay' ) ); + // Admin if ( is_admin() && ! is_ajax() ) { @@ -240,6 +246,9 @@ public function lib_includes() { require_once( $payment_gateway_framework_path . '/class-sv-wc-payment-gateway-payment-form.php' ); require_once( $payment_gateway_framework_path . '/class-sv-wc-payment-gateway-my-payment-methods.php' ); + // apple pay + require_once( $payment_gateway_framework_path . '/apple-pay/class-sv-wc-payment-gateway-apple-pay.php' ); + // payment tokens require_once( $payment_gateway_framework_path . '/payment-tokens/class-sv-wc-payment-gateway-payment-token.php' ); require_once( $payment_gateway_framework_path . '/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php' ); @@ -322,6 +331,55 @@ protected function get_my_payment_methods_instance() { } + /** Apple Pay *************************************************************/ + + + /** + * Initializes Apple Pay if it's supported. + * + * @since 4.6.0-dev + */ + public function maybe_init_apple_pay() { + + if ( $this->supports_apple_pay() ) { + $this->apple_pay = $this->get_apple_pay_instance(); + } + } + + + /** + * Gets the Apple Pay handler instance. + * + * @since 4.6.0-dev + * @return \SV_WC_Payment_Gateway_Apple_Pay + */ + public function get_apple_pay_instance() { + + return new SV_WC_Payment_Gateway_Apple_Pay( $this ); + } + + + /** + * Determines if this plugin has any gateways with Apple Pay support. + * + * @since 4.6.0-dev + * @return bool + */ + public function supports_apple_pay() { + + $is_supported = false; + + foreach ( $this->get_gateways() as $gateway ) { + + if ( $gateway->supports_apple_pay() ) { + $is_supported = true; + } + } + + return $is_supported; + } + + /** Admin methods ******************************************************/ diff --git a/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php b/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php index 8aeaaec0d..a2d6e2d20 100644 --- a/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php +++ b/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php @@ -112,6 +112,9 @@ abstract class SV_WC_Payment_Gateway extends WC_Payment_Gateway { /** Customer ID feature */ const FEATURE_CUSTOMER_ID = 'customer_id'; + /** Apple Pay feature */ + const FEATURE_APPLE_PAY = 'apple_pay'; + /** @var SV_WC_Payment_Gateway_Plugin the parent plugin class */ private $plugin; @@ -697,6 +700,58 @@ public function get_payment_method_defaults() { } + /** Apple Pay Feature *****************************************************/ + + + /** + * Determines whether this gateway supports Apple Pay. + * + * @since 4.6.0-dev + * @return bool + */ + public function supports_apple_pay() { + + return $this->supports( self::FEATURE_APPLE_PAY ); + } + + + /** + * Gets the Apple Pay gateway capabilities. + * + * Gateways should override this if they have more or less capabilities than + * the default. See https://developer.apple.com/reference/applepayjs/paymentrequest/1916123-merchantcapabilities + * for valid values. + * + * @since 4.6.0-dev + * @return array + */ + public function get_apple_pay_capabilities() { + + return array( + 'supports3DS', + 'supportsCredit', + 'supportsDebit', + ); + } + + + /** + * Adds the Apple Pay payment data to the order object. + * + * Gateways should override this to see the appropriate values depending on + * how their processing API needs to handle the data. + * + * @since 4.6.0-dev + * @param \WC_Order the order object + * @param object the authorized payment data. see https://developer.apple.com/reference/applepayjs/payment for structure + * @return \WC_Order + */ + public function add_apple_pay_order_data( $order, $payment_data ) { + + return $order; + } + + /** * Get the default payment method title, which is configurable within the * admin and displayed on checkout From d9b96e2d7d5814dcc40e3ff7fc49e48aa24a9408 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Tue, 22 Nov 2016 17:18:24 -0800 Subject: [PATCH 02/41] Apple Pay: add single product page handling --- ...-sv-wc-payment-gateway-apple-pay-admin.php | 21 ++ ...-wc-payment-gateway-apple-pay-frontend.php | 212 +++++++++++------- .../class-sv-wc-payment-gateway-apple-pay.php | 95 ++++++-- .../sv-wc-payment-gateway-apple-pay.coffee | 69 +++--- .../sv-wc-payment-gateway-apple-pay.min.js | 63 +++--- 5 files changed, 300 insertions(+), 160 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php index fb9353480..cd5ae7d15 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php @@ -127,6 +127,27 @@ public function get_settings() { 'type' => 'sectionend', ), + array( + 'title' => __( 'Buy Now', 'woocommerce-plugin-framework' ), + 'type' => 'title', + ), + + array( + 'id' => 'sv_wc_apple_pay_buy_now_tax_rate', + 'title' => __( 'Tax Rate', 'woocommerce-plugin-framework' ), + 'type' => 'text', + ), + + array( + 'id' => 'sv_wc_apple_pay_buy_now_shipping_cost', + 'title' => __( 'Shipping Cost', 'woocommerce-plugin-framework' ), + 'type' => 'text', + ), + + array( + 'type' => 'sectionend', + ), + array( 'title' => __( 'Connection Settings', 'woocommerce-plugin-framework' ), 'type' => 'title', diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index 3c65e0a9d..d6cb6dd07 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -41,8 +41,6 @@ class SV_WC_Payment_Gateway_Apple_Pay_Frontend { /** @var \SV_WC_Payment_Gateway $gateway the gateway instance */ protected $gateway; - protected $payment_data; - /** * Constructs the class. @@ -59,10 +57,9 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin, SV_WC_Payment $this->gateway = $this->get_handler()->get_processing_gateway(); - add_action( 'wp', array( $this, 'init' ) ); - - #add_action( 'wp_ajax_sv_wc_apple_pay_get_product_payment_request', array( $this, 'get_product_payment_request' ) ); - #add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_product_payment_request', array( $this, 'get_product_payment_request' ) ); + if ( $this->get_handler()->is_available() ) { + add_action( 'wp', array( $this, 'init' ) ); + } add_action( 'wp_ajax_sv_wc_apple_pay_get_cart_payment_request', array( $this, 'get_cart_payment_request' ) ); add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_cart_payment_request', array( $this, 'get_cart_payment_request' ) ); @@ -79,14 +76,10 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin, SV_WC_Payment */ public function init() { - if ( ! $this->get_handler()->is_available() ) { - return; - } - add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); - if ( is_product() ) { - #$this->init_product(); + if ( is_product() && 'yes' === get_option( 'sv_wc_apple_pay_single_product' ) ) { + $this->init_product(); } else if ( is_cart() && 'yes' === get_option( 'sv_wc_apple_pay_cart' ) ) { $this->init_cart(); } else if ( is_checkout() && 'yes' === get_option( 'sv_wc_apple_pay_checkout' ) ) { @@ -166,6 +159,13 @@ public function init_product() { $product = wc_get_product( get_the_ID() ); + // simple products only, for now + if ( ! $product || ! $product->is_type( 'simple' ) ) { + return; + } + + $payment_request = $this->get_product_payment_request( $product ); + /** * Filters the Apple Pay product handler args. * @@ -173,9 +173,7 @@ public function init_product() { * @param array $args */ $args = apply_filters( 'sv_wc_apple_pay_product_handler_args', array( - 'request_action' => 'sv_wc_apple_pay_get_product_payment_request', - 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_product_payment_request' ), - 'product_id' => $product->is_type( 'variable' ) ? 0 : $product->get_id(), + 'payment_request' => $payment_request, ) ); wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Product_Handler(%s);', json_encode( $args ) ) ); @@ -184,6 +182,51 @@ public function init_product() { } + /** + * Gets a single product payment request. + * + * @since 4.6.0-dev + * @param \WC_Product $product the product object + * @return array + */ + public function get_product_payment_request( WC_Product $product ) { + + $line_items = array( + $product->get_id() => array( + 'name' => $product->get_title(), + 'quantity' => 1, + 'amount' => $product->get_price(), + ), + ); + + $args = array( + 'shipping_required' => $product->needs_shipping(), + 'shipping_total' => 0, + 'tax_total' => 0, + ); + + $shipping_cost = (int) get_option( 'sv_wc_apple_pay_buy_now_shipping_cost', 0 ); + $tax_rate = (int) get_option( 'sv_wc_apple_pay_buy_now_tax_rate', 0 ); + + if ( $product->needs_shipping() && $shipping_cost ) { + $args['shipping_total'] = $shipping_cost; + } + + $total = array( + 'amount' => $product->get_price() + $args['shipping_total'], + ); + + if ( $tax_rate && wc_tax_enabled() ) { + + $args['tax_total'] = round( $total['amount'] * ( $tax_rate / 100 ), 2 ); + + $total['amount'] += $args['tax_total']; + } + + return $this->build_payment_request( $total, $line_items, $args ); + } + + /** Cart functionality ****************************************************/ @@ -194,6 +237,10 @@ public function init_product() { */ public function init_cart() { + if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { + return; + } + /** * Filters the Apple Pay cart handler args. * @@ -239,6 +286,65 @@ public function get_cart_payment_request() { } + /** Checkout functionality ************************************************/ + + + /** + * Initializes Apple Pay on the checkout page. + * + * @since 4.6.0-dev + */ + public function init_checkout() { + + if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { + return; + } + + /** + * Filters the Apple Pay checkout handler args. + * + * @since 4.6.0-dev + * @param array $args + */ + $args = apply_filters( 'sv_wc_apple_pay_checkout_handler_args', array( + 'request_action' => 'sv_wc_apple_pay_get_checkout_payment_request', + 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_checkout_payment_request' ) + ) ); + + wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Checkout_Handler(%s);', json_encode( $args ) ) ); + + add_action( 'woocommerce_review_order_before_payment', array( $this, 'render_button' ) ); + } + + + /** + * Gets a payment request for the checkout. + * + * @since 4.6.0-dev + */ + public function get_checkout_payment_request() { + + check_ajax_referer( 'sv_wc_apple_pay_get_checkout_payment_request', 'nonce' ); + + try { + + $request = $this->build_cart_payment_request( WC()->cart ); + + wp_send_json( array( + 'result' => 'success', + 'request' => json_encode( $request ), + ) ); + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + wp_send_json( array( + 'result' => 'error', + 'message' => $e->getMessage(), + ) ); + } + } + + /** * Builds a payment request based on WC cart data. * @@ -253,7 +359,7 @@ protected function build_cart_payment_request( WC_Cart $cart ) { foreach ( $cart->get_cart() as $cart_item_key => $item ) { - $line_items[] = array( + $line_items[ $item['data']->get_id() ] = array( 'name' => $item['data']->get_title(), 'quantity' => $item['quantity'], 'amount' => $item['line_subtotal'], @@ -276,6 +382,8 @@ protected function build_cart_payment_request( WC_Cart $cart ) { if ( $cart->needs_shipping() ) { + $args['shipping_required'] = true; + // shipping $shipping_packages = WC()->shipping->get_packages(); $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() ); @@ -321,61 +429,6 @@ protected function build_cart_payment_request( WC_Cart $cart ) { } - /** Checkout functionality ************************************************/ - - - /** - * Initializes Apple Pay on the checkout page. - * - * @since 4.6.0-dev - */ - public function init_checkout() { - - /** - * Filters the Apple Pay checkout handler args. - * - * @since 4.6.0-dev - * @param array $args - */ - $args = apply_filters( 'sv_wc_apple_pay_checkout_handler_args', array( - 'request_action' => 'sv_wc_apple_pay_get_checkout_payment_request', - 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_checkout_payment_request' ) - ) ); - - wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Checkout_Handler(%s);', json_encode( $args ) ) ); - - add_action( 'woocommerce_review_order_before_payment', array( $this, 'render_button' ) ); - } - - - /** - * Gets a payment request for the checkout. - * - * @since 4.6.0-dev - */ - public function get_checkout_payment_request() { - - check_ajax_referer( 'sv_wc_apple_pay_get_checkout_payment_request', 'nonce' ); - - try { - - $request = $this->build_cart_payment_request( WC()->cart ); - - wp_send_json( array( - 'result' => 'success', - 'request' => json_encode( $request ), - ) ); - - } catch ( SV_WC_Payment_Gateway_Exception $e ) { - - wp_send_json( array( - 'result' => 'error', - 'message' => $e->getMessage(), - ) ); - } - } - - /** * Builds an Apple Pay payment request. * @@ -390,7 +443,7 @@ public function get_checkout_payment_request() { * @type string $amount the total payment amount * } * @param array $line_items { - * The order line items. + * Optional. The order line items. * * @type string $name the line item name (usually a WC product title) * @type string $amount the line subtotal @@ -405,13 +458,14 @@ public function get_checkout_payment_request() { * } * @return array */ - protected function build_payment_request( $total, $line_items, $args = array() ) { + protected function build_payment_request( $total, $line_items = array(), $args = array() ) { $args = wp_parse_args( $args, array( 'currency_code' => get_woocommerce_currency(), 'country_code' => get_option( 'woocommerce_default_country' ), 'merchant_capabilities' => $this->get_handler()->get_capabilities(), 'supported_networks' => $this->get_handler()->get_supported_networks(), + 'shipping_required' => false, ) ); // set the base required defaults @@ -429,7 +483,7 @@ protected function build_payment_request( $total, $line_items, $args = array() ) ); // if a shipping address is required - if ( ! empty( $args['shipping_methods'] ) || ! empty( $args['shipping_total'] ) ) { + if ( $args['shipping_required'] ) { $request['requiredShippingContactFields'][] = 'postalAddress'; } @@ -473,7 +527,9 @@ protected function build_payment_request( $total, $line_items, $args = array() ) ); } - $request['lineItems'] = array_values( $line_items ); + if ( ! empty( $line_items ) ) { + $request['lineItems'] = $line_items; + } $request['total'] = wp_parse_args( $total, array( 'label' => get_bloginfo( 'name', 'display' ), @@ -482,6 +538,12 @@ protected function build_payment_request( $total, $line_items, $args = array() ) $request['total']['amount'] = $this->format_price( $request['total']['amount'] ); + $this->get_handler()->store_payment_request( $request ); + + if ( ! empty( $request['lineItems'] ) ) { + $request['lineItems'] = array_values( $request['lineItems'] ); + } + // log the payment request $this->get_gateway()->add_debug_message( "Apple Pay Payment Request:\n" . print_r( $request, true ) ); diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 41f389de4..994c5a3d2 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -132,6 +132,7 @@ public function validate_merchant() { */ public function process_payment() { + $type = SV_WC_Helper::get_post( 'type' ); $payment = json_decode( stripslashes( SV_WC_Helper::get_post( 'payment' ) ) ); try { @@ -146,7 +147,13 @@ public function process_payment() { $this->get_processing_gateway()->add_debug_message( "Apple Pay Payment Response:\n" . print_r( $payment, true ) ); // create a new order - $order = $this->create_cart_order(); + if ( 'cart' === $type || 'checkout' === $type ) { + $order = $this->create_cart_order(); + } else if ( 'product' === $type ) { + $order = $this->create_product_order(); + } else { + throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment type recieved' ); + } $billing_address = $shipping_address = array(); @@ -166,7 +173,7 @@ public function process_payment() { 'city' => $payment->billingContact->locality, 'state' => $payment->billingContact->administrativeArea, 'postcode' => $payment->billingContact->postalCode, - 'country' => $payment->billingContact->countryCode, + 'country' => strtoupper( $payment->billingContact->countryCode ), ) ); } @@ -189,7 +196,7 @@ public function process_payment() { 'city' => $payment->shippingContact->locality, 'state' => $payment->shippingContact->administrativeArea, 'postcode' => $payment->shippingContact->postalCode, - 'country' => $payment->shippingContact->countryCode, + 'country' => strtoupper( $payment->shippingContact->countryCode ), ) ); } @@ -210,6 +217,9 @@ public function process_payment() { // process the payment via the gateway $result = $this->get_processing_gateway()->process_payment( $order->id ); + // clear the payment request data + unset( WC()->session->apple_pay_payment_request ); + wp_send_json( $result ); } catch ( SV_WC_Payment_Gateway_Exception $e ) { @@ -272,18 +282,10 @@ public function create_cart_order() { 'fees' => WC()->cart->get_fees(), ); - foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) { - - $args['taxes'][ $tax_rate_id ] = array( - 'amount' => WC()->cart->get_tax_amount( $tax_rate_id ), - 'shipping_amount' => WC()->cart->get_shipping_tax_amount( $tax_rate_id ), - ); - } - foreach ( WC()->shipping->get_packages() as $key => $package ) { $args['packages'][ $key ] = array( - + // TODO ); } @@ -301,6 +303,50 @@ public function create_cart_order() { } + /** + * Creates an order from a single product request. + * + * @since 4.6.0-dev + * @throws \SV_WC_Plugin_Exception + */ + protected function create_product_order() { + + $payment_request = $this->get_stored_payment_request(); + + $items = array(); + + foreach ( $payment_request['lineItems'] as $product_id => $item ) { + + $product = wc_get_product( $product_id ); + + if ( ! $product ) { + continue; + } + + $items[] = array( + 'product' => $product, + 'quantity' => 1, + 'args' => array(), + 'values' => $item, + ); + } + + $order = $this->create_order( $items ); + + if ( ! empty( $payment_request['lineItems']['taxes'] ) ) { + $order->set_total( $payment_request['lineItems']['taxes']['amount'], 'tax' ); + } + + if ( ! empty( $payment_request['lineItems']['shipping'] ) ) { + $order->set_total( $payment_request['lineItems']['shipping']['amount'], 'shipping' ); + } + + $order->set_total( $payment_request['total']['amount'] ); + + return $order; + } + + /** * Creates a new order from provided data. * @@ -308,7 +354,7 @@ public function create_cart_order() { * @param array $args the order args * @throws \SV_WC_Plugin_Exception */ - public function create_order( $items, $args ) { + public function create_order( $items, $args = array() ) { $args = wp_parse_args( $args, array( 'customer_id' => get_current_user_id(), @@ -407,6 +453,29 @@ public function create_order( $items, $args ) { } + /** + * Gets the stored payment request data. + * + * @since 4.6.0-dev + * @return array + */ + public function get_stored_payment_request() { + + return WC()->session->get( 'apple_pay_payment_request', array() ); + } + + + /** + * Stores payment request data for later use. + * + * @since 4.6.0-dev + */ + public function store_payment_request( $data ) { + + WC()->session->set( 'apple_pay_payment_request', $data ); + } + + /** * Gets the Apple Pay API. * diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee index 6bc0ed138..483b43aa1 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee @@ -27,6 +27,8 @@ jQuery( document ).ready ($) -> @request_action = args.request_action @request_nonce = args.request_nonce + @payment_request = args.payment_request + if this.is_available() this.init() @@ -47,25 +49,31 @@ jQuery( document ).ready ($) -> init: -> - this.block_ui() - @buttons = $( '.sv-wc-apple-pay-button' ) - this.get_payment_request().then ( response ) => + if not @payment_request - @payment_request = $.parseJSON( response ) + this.block_ui() - if @payment_request + this.get_payment_request().then ( response ) => - @buttons.show().prop( 'disabled', false ) + @payment_request = $.parseJSON( response ) - this.unblock_ui() + if @payment_request - , ( response ) => + @buttons.show().prop( 'disabled', false ) - console.log '[Apple Pay Error] ' + response + this.unblock_ui() - this.unblock_ui() + , ( response ) => + + console.log '[Apple Pay Error] ' + response + + this.unblock_ui() + + else + + @buttons.show().prop( 'disabled', false ) $( document.body ).on 'click', '.sv-wc-apple-pay-button:not([disabled])', ( e ) => @@ -172,6 +180,7 @@ jQuery( document ).ready ($) -> data = { action: 'sv_wc_apple_pay_process_payment', nonce: @params.process_nonce, + type: @type, payment: JSON.stringify( payment ) } @@ -256,12 +265,18 @@ jQuery( document ).ready ($) -> # @since 3.9.2-1 constructor: (args) -> + @type = 'cart' + @payment_form = $( '.cart_totals' ) super(args) # re-init if the cart totals are updated - $( document.body ).on( 'updated_cart_totals', => this.init() ) + $( document.body ).on 'updated_cart_totals', => + + @payment_request = false + + this.init() # The WooCommerce Apple Pay checkout handler class. @@ -275,12 +290,18 @@ jQuery( document ).ready ($) -> # @since 3.9.2-1 constructor: (args) -> + @type = 'checkout' + @payment_form = $( 'form.woocommerce-checkout' ) super(args) # re-init if the cart totals are updated - $( document.body ).on( 'update_checkout', => this.init() ) + $( document.body ).on 'update_checkout', => + + @payment_request = false + + this.init() # The WooCommerce Apple Pay product handler class. @@ -294,28 +315,8 @@ jQuery( document ).ready ($) -> # @since 3.9.2-1 constructor: (args) -> - @payment_form = $( 'form.cart' ) + @type = 'product' - @product_id = args.product_id + @payment_form = $( 'form.cart' ) super(args) - - # re-init if the varation form is updated - $( document.body ).on( 'show_variation', ( event, variation, purchasable ) => this.init_variations( variation.variation_id, purchasable ) ) - - - init_variations: ( variation_id, purchasable ) => - - if variation_id and purchasable - @product_id = variation_id - else - @product_id = 0 - - this.init() - - - init: => - - return if @product_id is 0 - - super() diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js index 7ff95f3e9..f0757c682 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js @@ -26,6 +26,7 @@ this.params = sv_wc_apple_pay_params; this.request_action = args.request_action; this.request_nonce = args.request_nonce; + this.payment_request = args.payment_request; if (this.is_available()) { this.init(); } @@ -43,22 +44,26 @@ }; SV_WC_Apple_Pay_Handler.prototype.init = function() { - this.block_ui(); this.buttons = $('.sv-wc-apple-pay-button'); - this.get_payment_request().then((function(_this) { - return function(response) { - _this.payment_request = $.parseJSON(response); - if (_this.payment_request) { - _this.buttons.show().prop('disabled', false); + if (!this.payment_request) { + this.block_ui(); + this.get_payment_request().then((function(_this) { + return function(response) { + _this.payment_request = $.parseJSON(response); + if (_this.payment_request) { + _this.buttons.show().prop('disabled', false); + return _this.unblock_ui(); + } + }; + })(this), (function(_this) { + return function(response) { + console.log('[Apple Pay Error] ' + response); return _this.unblock_ui(); - } - }; - })(this), (function(_this) { - return function(response) { - console.log('[Apple Pay Error] ' + response); - return _this.unblock_ui(); - }; - })(this)); + }; + })(this)); + } else { + this.buttons.show().prop('disabled', false); + } return $(document.body).on('click', '.sv-wc-apple-pay-button:not([disabled])', (function(_this) { return function(e) { var error; @@ -161,6 +166,7 @@ data = { action: 'sv_wc_apple_pay_process_payment', nonce: _this.params.process_nonce, + type: _this.type, payment: JSON.stringify(payment) }; return $.post(_this.params.ajax_url, data, function(response) { @@ -228,10 +234,12 @@ extend(SV_WC_Apple_Pay_Cart_Handler, superClass); function SV_WC_Apple_Pay_Cart_Handler(args) { + this.type = 'cart'; this.payment_form = $('.cart_totals'); SV_WC_Apple_Pay_Cart_Handler.__super__.constructor.call(this, args); $(document.body).on('updated_cart_totals', (function(_this) { return function() { + _this.payment_request = false; return _this.init(); }; })(this)); @@ -244,10 +252,12 @@ extend(SV_WC_Apple_Pay_Checkout_Handler, superClass); function SV_WC_Apple_Pay_Checkout_Handler(args) { + this.type = 'checkout'; this.payment_form = $('form.woocommerce-checkout'); SV_WC_Apple_Pay_Checkout_Handler.__super__.constructor.call(this, args); $(document.body).on('update_checkout', (function(_this) { return function() { + _this.payment_request = false; return _this.init(); }; })(this)); @@ -260,34 +270,11 @@ extend(SV_WC_Apple_Pay_Product_Handler, superClass); function SV_WC_Apple_Pay_Product_Handler(args) { - this.init = bind(this.init, this); - this.init_variations = bind(this.init_variations, this); + this.type = 'product'; this.payment_form = $('form.cart'); - this.product_id = args.product_id; SV_WC_Apple_Pay_Product_Handler.__super__.constructor.call(this, args); - $(document.body).on('show_variation', (function(_this) { - return function(event, variation, purchasable) { - return _this.init_variations(variation.variation_id, purchasable); - }; - })(this)); } - SV_WC_Apple_Pay_Product_Handler.prototype.init_variations = function(variation_id, purchasable) { - if (variation_id && purchasable) { - this.product_id = variation_id; - } else { - this.product_id = 0; - } - return this.init(); - }; - - SV_WC_Apple_Pay_Product_Handler.prototype.init = function() { - if (this.product_id === 0) { - return; - } - return SV_WC_Apple_Pay_Product_Handler.__super__.init.call(this); - }; - return SV_WC_Apple_Pay_Product_Handler; })(SV_WC_Apple_Pay_Handler); From 97cccddeb93a85b952625a75428f4b357f819cda Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 12:40:52 -0800 Subject: [PATCH 03/41] [Apple Pay] add settings descriptions --- ...-sv-wc-payment-gateway-apple-pay-admin.php | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php index cd5ae7d15..8b96cd727 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php @@ -130,18 +130,24 @@ public function get_settings() { array( 'title' => __( 'Buy Now', 'woocommerce-plugin-framework' ), 'type' => 'title', + 'desc' => sprintf( + __( 'The %1$sBuy Now with Apple Pay%2$s button is displayed on single product pages, and is only available for simple products. Use these settings to set an optional tax rate and shipping cost for customers who use Buy Now.', 'woocommerce-plugin-framework' ), + '', '' + ), ), array( - 'id' => 'sv_wc_apple_pay_buy_now_tax_rate', - 'title' => __( 'Tax Rate', 'woocommerce-plugin-framework' ), - 'type' => 'text', + 'id' => 'sv_wc_apple_pay_buy_now_tax_rate', + 'title' => __( 'Tax Rate', 'woocommerce-plugin-framework' ), + 'type' => 'text', + 'desc_tip' => __( 'The optional tax rate percentage to apply to Buy Now orders.', 'woocommerce-plugin-framework' ), ), array( - 'id' => 'sv_wc_apple_pay_buy_now_shipping_cost', - 'title' => __( 'Shipping Cost', 'woocommerce-plugin-framework' ), - 'type' => 'text', + 'id' => 'sv_wc_apple_pay_buy_now_shipping_cost', + 'title' => __( 'Shipping Cost', 'woocommerce-plugin-framework' ), + 'type' => 'text', + 'desc_tip' => __( 'The optional flat-rate shipping cost to add to Buy Now orders.', 'woocommerce-plugin-framework' ), ), array( @@ -160,9 +166,15 @@ public function get_settings() { ), array( - 'id' => 'sv_wc_apple_pay_cert_path', - 'title' => __( 'Certificate Path', 'woocommerce-plugin-framework' ), - 'type' => 'text', + 'id' => 'sv_wc_apple_pay_cert_path', + 'title' => __( 'Certificate Path', 'woocommerce-plugin-framework' ), + 'type' => 'text', + 'desc_tip' => 'The full system path to your certificate file from Apple. For security reasons you should store this outside of your web root.', + 'desc' => sprintf( + /* translators: Placeholders: %s - the server's web root path */ + __( 'For reference, your current web root path is: %s', 'woocommerce-plugin-framework' ), + '' . ABSPATH . '' + ), ), array( From bc9037ef01af81f52c6fb514000db9436091b5db Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 12:44:21 -0800 Subject: [PATCH 04/41] [Apple Pay] clear todos --- ...-payment-gateway-apple-pay-api-request.php | 2 +- ...ss-sv-wc-payment-gateway-apple-pay-api.php | 2 +- ...-wc-payment-gateway-apple-pay-frontend.php | 62 +++++++++++++------ .../class-sv-wc-payment-gateway-apple-pay.php | 33 ++++++++-- .../sv-wc-payment-gateway-apple-pay.coffee | 2 +- .../sv-wc-payment-gateway-apple-pay.min.js | 2 +- 6 files changed, 75 insertions(+), 28 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php index da39facd8..7b1187e36 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php @@ -60,7 +60,7 @@ public function set_merchant_data( $merchant_id, $domain_name, $display_name ) { $data = array( 'merchantIdentifier' => $merchant_id, - 'domainName' => 'applepay-skyverge.fwd.wf', // TODO: remove hardcode + 'domainName' => str_replace( array( 'http://', 'https://' ), '', home_url() ), 'displayName' => $display_name, ); diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php index 688fe59ed..34f0fe6be 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php @@ -46,7 +46,7 @@ public function __construct( SV_WC_Payment_Gateway $gateway ) { $this->gateway = $gateway; - $this->request_uri = 'https://apple-pay-gateway-cert.apple.com/paymentservices/startSession'; // TODO: production URL + $this->request_uri = 'https://apple-pay-gateway-cert.apple.com/paymentservices/startSession'; $this->set_request_content_type_header( 'application/json' ); $this->set_request_accept_header( 'application/json' ); diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index d6cb6dd07..d578ac397 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -110,6 +110,7 @@ public function enqueue_scripts() { 'ajax_url' => admin_url( 'admin-ajax.php' ), 'validate_nonce' => wp_create_nonce( 'sv_wc_apple_pay_validate_merchant' ), 'process_nonce' => wp_create_nonce( 'sv_wc_apple_pay_process_payment' ), + 'generic_error' => __( 'An error occurred, please try again or try an alternate form of payment', 'woocommerce-plugin-framework' ), ) ); wp_localize_script( 'sv-wc-apple-pay', 'sv_wc_apple_pay_params', $params ); @@ -178,7 +179,7 @@ public function init_product() { wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Product_Handler(%s);', json_encode( $args ) ) ); - add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_button' ) ); + add_action( 'woocommerce_before_add_to_cart_button', array( $this, 'render_button' ) ); } @@ -205,8 +206,8 @@ public function get_product_payment_request( WC_Product $product ) { 'tax_total' => 0, ); - $shipping_cost = (int) get_option( 'sv_wc_apple_pay_buy_now_shipping_cost', 0 ); - $tax_rate = (int) get_option( 'sv_wc_apple_pay_buy_now_tax_rate', 0 ); + $shipping_cost = (float) get_option( 'sv_wc_apple_pay_buy_now_shipping_cost', 0 ); + $tax_rate = (float) get_option( 'sv_wc_apple_pay_buy_now_tax_rate', 0 ); if ( $product->needs_shipping() && $shipping_cost ) { $args['shipping_total'] = $shipping_cost; @@ -357,6 +358,7 @@ protected function build_cart_payment_request( WC_Cart $cart ) { // product line items $line_items = array(); + // set the line items foreach ( $cart->get_cart() as $cart_item_key => $item ) { $line_items[ $item['data']->get_id() ] = array( @@ -366,20 +368,24 @@ protected function build_cart_payment_request( WC_Cart $cart ) { ); } - // TODO: fees? + // set any fees + foreach ( $cart->get_fees() as $fee ) { - // TODO: discounts? + $line_items[ $fee->id ] = array( + 'name' => $fee->name, + 'quantity' => 1, + 'amount' => $fee->amount, + ); + } - // order total - $total = array( - 'amount' => $cart->total, - ); + $args = array(); - // taxes - $args = array( - 'tax_total' => $cart->tax_total + $cart->shipping_tax_total, - ); + // discount total + if ( $cart->has_discount() ) { + $args['discount_total'] = $cart->get_cart_discount_total(); + } + // set shipping totals if ( $cart->needs_shipping() ) { $args['shipping_required'] = true; @@ -413,6 +419,14 @@ protected function build_cart_payment_request( WC_Cart $cart ) { } } + // tax total + $args['tax_total'] = $cart->tax_total + $cart->shipping_tax_total; + + // order total + $total = array( + 'amount' => $cart->total, + ); + $this->get_gateway()->add_debug_message( 'Generating Apple Pay Payment Request' ); // build it! @@ -487,6 +501,7 @@ protected function build_payment_request( $total, $line_items = array(), $args = $request['requiredShippingContactFields'][] = 'postalAddress'; } + // line items foreach ( $line_items as $key => $item ) { $label = $item['name']; @@ -503,13 +518,13 @@ protected function build_payment_request( $total, $line_items = array(), $args = ); } - // taxes - if ( ! empty( $args['tax_total'] ) ) { + // discounts + if ( ! empty( $args['discount_total'] ) ) { - $line_items['taxes'] = array( + $line_items[ 'discount' ] = array( 'type' => 'final', - 'label' => __( 'Taxes', 'woocommerce-plugin-framework' ), - 'amount' => $this->format_price( $args['tax_total'] ), + 'label' => __( 'Discount', 'woocommerce-plugin-framework' ), + 'amount' => $this->format_price( $args['discount_total'] ), ); } @@ -527,10 +542,21 @@ protected function build_payment_request( $total, $line_items = array(), $args = ); } + // taxes + if ( ! empty( $args['tax_total'] ) ) { + + $line_items['taxes'] = array( + 'type' => 'final', + 'label' => __( 'Taxes', 'woocommerce-plugin-framework' ), + 'amount' => $this->format_price( $args['tax_total'] ), + ); + } + if ( ! empty( $line_items ) ) { $request['lineItems'] = $line_items; } + // order total $request['total'] = wp_parse_args( $total, array( 'label' => get_bloginfo( 'name', 'display' ), 'amount' => 0, diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 994c5a3d2..2a4ad3a8f 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -138,11 +138,16 @@ public function process_payment() { try { if ( ! $payment ) { - throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment data recieved' ); // TODO + throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment data recieved' ); } $this->payment_data = $payment; + // pretend this is at checkout so totals are fully calculated + if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { + define( 'WOOCOMMERCE_CHECKOUT', true ); + } + // log the payment response $this->get_processing_gateway()->add_debug_message( "Apple Pay Payment Response:\n" . print_r( $payment, true ) ); @@ -155,6 +160,10 @@ public function process_payment() { throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment type recieved' ); } + // if we got to this point, the payment was authorized by Apple Pay + // from here on out, it's up to the gateway to not screw things up. + $order->add_order_note( __( 'Apple Pay payment authorized.', 'woocommerce-plugin-framework' ) ); + $billing_address = $shipping_address = array(); // set the billing address @@ -226,6 +235,12 @@ public function process_payment() { $this->get_processing_gateway()->add_debug_message( 'Apple Pay payment failed. ' . $e->getMessage() ); + $order->add_order_note( sprintf( + /** translators: Placeholders: %s - the error message */ + __( 'Apple Pay payment failed. %s', 'woocommerce-plugin-framework' ), + $e->getMessage() + ) ); + wp_send_json( array( 'result' => 'error', 'message' => $e->getMessage(), @@ -259,6 +274,8 @@ public function create_cart_order() { $items = array(); + WC()->cart->calculate_totals(); + foreach ( WC()->cart->get_cart() as $cart_item_key => $item ) { $items[ $cart_item_key ] = array( @@ -282,11 +299,8 @@ public function create_cart_order() { 'fees' => WC()->cart->get_fees(), ); - foreach ( WC()->shipping->get_packages() as $key => $package ) { - - $args['packages'][ $key ] = array( - // TODO - ); + if ( $packages = WC()->shipping->get_packages() ) { + $args['packages'] = $packages; } foreach ( WC()->cart->get_coupons() as $code => $coupon ) { @@ -333,6 +347,7 @@ protected function create_product_order() { $order = $this->create_order( $items ); + // set the totals if ( ! empty( $payment_request['lineItems']['taxes'] ) ) { $order->set_total( $payment_request['lineItems']['taxes']['amount'], 'tax' ); } @@ -350,6 +365,8 @@ protected function create_product_order() { /** * Creates a new order from provided data. * + * This is adapted from WooCommerce's `WC_Checkout::create_order()` + * * @since 4.6.0-dev * @param array $args the order args * @throws \SV_WC_Plugin_Exception @@ -390,6 +407,7 @@ public function create_order( $items, $args = array() ) { $order->set_payment_method( $this->get_processing_gateway()->get_id() ); + // add line items foreach ( $items as $key => $item ) { $item_id = $order->add_product( $item['product'], $item['quantity'], $item['args'] ); @@ -401,6 +419,7 @@ public function create_order( $items, $args = array() ) { do_action( 'woocommerce_add_order_item_meta', $item_id, $item['values'], $key ); } + // add fees foreach ( $args['fees'] as $key => $fee ) { $item_id = $order->add_fee( $fee ); @@ -412,6 +431,7 @@ public function create_order( $items, $args = array() ) { do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $key ); } + // add shipping packages foreach ( $args['packages'] as $key => $package ) { $shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); @@ -428,6 +448,7 @@ public function create_order( $items, $args = array() ) { } } + // add coupons foreach ( $args['coupons'] as $code => $coupon ) { if ( ! $order->add_coupon( $code, $coupon['amount'], $coupon['tax_amount'] ) ) { diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee index 483b43aa1..d361eff4c 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee @@ -208,7 +208,7 @@ jQuery( document ).ready ($) -> this.unblock_ui() - this.render_errors( ['An error occurred'] ) # localize + this.render_errors( [ @params.generic_error ] ) # Sets the Apple Pay payment status depending on the processing result. diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js index f0757c682..4386c692a 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js @@ -191,7 +191,7 @@ SV_WC_Apple_Pay_Handler.prototype.fail_payment = function(error) { console.log('[Apple Pay Error] ' + error); this.unblock_ui(); - return this.render_errors(['An error occurred']); + return this.render_errors([this.params.generic_error]); }; SV_WC_Apple_Pay_Handler.prototype.set_payment_status = function(result) { From 8a642d387337eb25162d0ffd249ac29694d58d1e Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 12:45:19 -0800 Subject: [PATCH 05/41] [Apple Pay] only allow Buy Now for in-stock products --- .../class-sv-wc-payment-gateway-apple-pay-frontend.php | 5 +++++ .../apple-pay/class-sv-wc-payment-gateway-apple-pay.php | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index d578ac397..43de2eace 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -165,6 +165,11 @@ public function init_product() { return; } + // if this product can't be purchased, bail + if ( ! $product->is_purchasable() || ! $product->is_in_stock() || ! $product->has_enough_stock( 1 ) ) { + return; + } + $payment_request = $this->get_product_payment_request( $product ); /** diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 2a4ad3a8f..3da51c190 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -337,6 +337,10 @@ protected function create_product_order() { continue; } + if ( ! $product->is_in_stock() || ! $product->has_enough_stock( 1 ) ) { + throw new SV_WC_Payment_Gateway_Exception( __( 'The product is out of stock.', 'woocommerce-plugin-framework' ) ); + } + $items[] = array( 'product' => $product, 'quantity' => 1, From ffcd632b54395b9dc2f8e61cf38e150adc253c61 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 12:46:39 -0800 Subject: [PATCH 06/41] [Apple Pay] use session for payment response data --- .../class-sv-wc-payment-gateway-apple-pay.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 3da51c190..8145c87ad 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -41,9 +41,6 @@ class SV_WC_Payment_Gateway_Apple_Pay { /** @var \SV_WC_Payment_Gateway_Plugin the plugin instance */ protected $plugin; - /** @var object the authorized payment data to be sent to the gateway API */ - protected $payment_data; - /** @var \SV_WC_Payment_Gateway_Apple_Pay_API the Apple Pay API */ protected $api; @@ -141,7 +138,8 @@ public function process_payment() { throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment data recieved' ); } - $this->payment_data = $payment; + // store the the payment response for later use + WC()->session->set( 'apple_pay_payment_response', $payment ); // pretend this is at checkout so totals are fully calculated if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { @@ -258,7 +256,11 @@ public function process_payment() { */ public function add_order_data( $order ) { - $order = $this->get_processing_gateway()->add_apple_pay_order_data( $order, $this->payment_data ); + $payment_data = WC()->session->set( 'apple_pay_payment_response', array() ); + + $order = $this->get_processing_gateway()->add_apple_pay_order_data( $order, $payment_data ); + + unset( WC()->session->apple_pay_payment_response ); return $order; } From 42930c8b8e3334a24a7fc93056d45eb2979518e3 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 12:47:03 -0800 Subject: [PATCH 07/41] [Apple Pay] allow resuming failed orders --- .../class-sv-wc-payment-gateway-apple-pay.php | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 8145c87ad..5a2cd0707 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -162,6 +162,9 @@ public function process_payment() { // from here on out, it's up to the gateway to not screw things up. $order->add_order_note( __( 'Apple Pay payment authorized.', 'woocommerce-plugin-framework' ) ); + // set the new order ID so it can be resumed in case of failure + WC()->session->order_awaiting_payment = $order->id; + $billing_address = $shipping_address = array(); // set the billing address @@ -226,6 +229,7 @@ public function process_payment() { // clear the payment request data unset( WC()->session->apple_pay_payment_request ); + unset( WC()->session->order_awaiting_payment ); wp_send_json( $result ); @@ -313,6 +317,9 @@ public function create_cart_order() { ); } + // set the cart hash to this can be resumed on failure + $args['cart_hash'] = md5( json_encode( wc_clean( WC()->cart->get_cart_for_session() ) ) . WC()->cart->total ); + $order = $this->create_order( $items, $args ); return $order; @@ -330,6 +337,7 @@ protected function create_product_order() { $payment_request = $this->get_stored_payment_request(); $items = array(); + $args = array(); foreach ( $payment_request['lineItems'] as $product_id => $item ) { @@ -351,7 +359,10 @@ protected function create_product_order() { ); } - $order = $this->create_order( $items ); + // set the cart hash to this can be resumed on failure + $args['cart_hash'] = md5( json_encode( wc_clean( $payment_request ) ) . $payment_request['total']['amount'] ); + + $order = $this->create_order( $items, $args ); // set the totals if ( ! empty( $payment_request['lineItems']['taxes'] ) ) { @@ -386,6 +397,7 @@ public function create_order( $items, $args = array() ) { 'coupons' => array(), 'billing_address' => array(), 'shipping_address' => array(), + 'cart_hash' => '', ) ); try { @@ -395,20 +407,53 @@ public function create_order( $items, $args = array() ) { $order_data = array( 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), 'customer_id' => $args['customer_id'], + 'cart_hash' => $args['cart_hash'], 'created_via' => 'apple_pay', ); - $order = wc_create_order( $order_data ); + // Insert or update the post data + $order_id = absint( WC()->session->order_awaiting_payment ); + + /** + * If there is an order pending payment, we can resume it here so + * long as it has not changed. If the order has changed, i.e. + * different items or cost, create a new order. We use a hash to + * detect changes which is based on cart items + order total. + */ + if ( $order_id && $order_data['cart_hash'] === get_post_meta( $order_id, '_cart_hash', true ) && ( $order = wc_get_order( $order_id ) ) && $order->has_status( array( 'pending', 'failed' ) ) ) { + + $order_data['order_id'] = $order_id; + + $order = wc_update_order( $order_data ); + + if ( is_wp_error( $order ) ) { + + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 522 ) ); + } else { + + $order->remove_order_items(); + + do_action( 'woocommerce_resume_order', $order_id ); + } - if ( is_wp_error( $order ) ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); - } elseif ( false === $order ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); } else { - $order_id = $order->id; + $order = wc_create_order( $order_data ); + + if ( is_wp_error( $order ) ) { + + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); + + } elseif ( false === $order ) { - do_action( 'woocommerce_new_order', $order_id ); + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); + + } else { + + $order_id = $order->id; + + do_action( 'woocommerce_new_order', $order_id ); + } } $order->set_payment_method( $this->get_processing_gateway()->get_id() ); From 0d44371ee9d7d075117b2d8df2237333a20aefbb Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 12:54:29 -0800 Subject: [PATCH 08/41] [Apple Pay] tweaks and versioning --- woocommerce/changelog.txt | 3 + woocommerce/class-sv-wc-plugin.php | 4 +- .../woocommerce-plugin-framework.pot | 318 ++++++++++++------ ...payment-gateway-apple-pay-api-response.php | 12 + .../class-sv-wc-payment-gateway-apple-pay.php | 7 +- 5 files changed, 232 insertions(+), 112 deletions(-) diff --git a/woocommerce/changelog.txt b/woocommerce/changelog.txt index d96d8d654..59f812902 100644 --- a/woocommerce/changelog.txt +++ b/woocommerce/changelog.txt @@ -1,5 +1,8 @@ *** SkyVerge WooCommerce Plugin Framework Changelog *** +2016.nn.nn - version 4.6.0-dev + * Feature - Add Apple Pay framework + 2016.11.18 - version 4.5.1 * Fix - Prevent a potential fatal error for plugins not using the latest JSON/XML request classes diff --git a/woocommerce/class-sv-wc-plugin.php b/woocommerce/class-sv-wc-plugin.php index 3643915f9..bdd3437d2 100644 --- a/woocommerce/class-sv-wc-plugin.php +++ b/woocommerce/class-sv-wc-plugin.php @@ -34,13 +34,13 @@ * plugin. This class handles all the "non-feature" support tasks such * as verifying dependencies are met, loading the text domain, etc. * - * @version 4.5.1 + * @version 4.6.0-dev */ abstract class SV_WC_Plugin { /** Plugin Framework Version */ - const VERSION = '4.5.1'; + const VERSION = '4.6.0-dev'; /** @var object single instance of plugin */ protected static $instance; diff --git a/woocommerce/i18n/languages/woocommerce-plugin-framework.pot b/woocommerce/i18n/languages/woocommerce-plugin-framework.pot index 90c318b18..4688ebab9 100644 --- a/woocommerce/i18n/languages/woocommerce-plugin-framework.pot +++ b/woocommerce/i18n/languages/woocommerce-plugin-framework.pot @@ -209,7 +209,7 @@ msgid "This section contains configuration settings for this gateway." msgstr "" #: payment-gateway/admin/views/html-admin-gateway-status.php:52 -#: payment-gateway/class-sv-wc-payment-gateway.php:876 +#: payment-gateway/class-sv-wc-payment-gateway.php:931 #. translators: environment as in a software environment (test/production) msgid "Environment" msgstr "" @@ -239,6 +239,7 @@ msgid "The gateway customer ID for the user. Only edit this if necessary." msgstr "" #: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:95 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:113 msgid "An error occurred, please try again or try an alternate form of payment" msgstr "" @@ -358,6 +359,119 @@ msgid "" "Please verify the address and try again." msgstr "" +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:68 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:85 +msgid "Apple Pay" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:91 +#: payment-gateway/class-sv-wc-payment-gateway.php:807 +msgid "Enable / Disable" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:92 +msgid "Accept Apple Pay" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:101 +msgid "At checkout" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:110 +msgid "On the Cart page" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:119 +msgid "On single product pages" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:131 +msgid "Buy Now" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:134 +msgid "" +"The %1$sBuy Now with Apple Pay%2$s button is displayed on single product " +"pages, and is only available for simple products. Use these settings to set " +"an optional tax rate and shipping cost for customers who use Buy Now." +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:141 +msgid "Tax Rate" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:143 +msgid "The optional tax rate percentage to apply to Buy Now orders." +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:148 +msgid "Shipping Cost" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:150 +msgid "The optional flat-rate shipping cost to add to Buy Now orders." +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:158 +#: payment-gateway/class-sv-wc-payment-gateway.php:982 +msgid "Connection Settings" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:164 +msgid "Apple Merchant ID" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:170 +msgid "Certificate Path" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:175 +#. translators: Placeholders: %s - the server's web root path +msgid "For reference, your current web root path is: %s" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:182 +msgid "Processing Gateway" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:423 +msgid "No shipping totals available." +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:531 +msgid "Discount" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:545 +msgid "Shipping" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:555 +msgid "Taxes" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:163 +msgid "Apple Pay payment authorized." +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:242 +msgid "Apple Pay payment failed. %s" +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:351 +msgid "The product is out of stock." +msgstr "" + +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:431 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:446 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:450 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:466 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:478 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:494 +#: payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:505 +msgid "Error %d: Unable to create order. Please try again." +msgstr "" + #: payment-gateway/class-sv-wc-payment-gateway-direct.php:99 msgid "" "Payment error, please try another payment method or contact us to complete " @@ -365,82 +479,82 @@ msgid "" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:199 -#: payment-gateway/class-sv-wc-payment-gateway.php:410 +#: payment-gateway/class-sv-wc-payment-gateway.php:413 msgid "Card expiration date is invalid" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:223 -#: payment-gateway/class-sv-wc-payment-gateway.php:403 +#: payment-gateway/class-sv-wc-payment-gateway.php:406 msgid "Card number is missing" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:229 -#: payment-gateway/class-sv-wc-payment-gateway.php:406 +#: payment-gateway/class-sv-wc-payment-gateway.php:409 msgid "Card number is invalid (wrong length)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:234 -#: payment-gateway/class-sv-wc-payment-gateway.php:405 +#: payment-gateway/class-sv-wc-payment-gateway.php:408 msgid "Card number is invalid (only digits allowed)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:239 -#: payment-gateway/class-sv-wc-payment-gateway.php:404 +#: payment-gateway/class-sv-wc-payment-gateway.php:407 msgid "Card number is invalid" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:266 -#: payment-gateway/class-sv-wc-payment-gateway.php:408 +#: payment-gateway/class-sv-wc-payment-gateway.php:411 msgid "Card security code is invalid (only digits are allowed)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:272 -#: payment-gateway/class-sv-wc-payment-gateway.php:409 +#: payment-gateway/class-sv-wc-payment-gateway.php:412 msgid "Card security code is invalid (must be 3 or 4 digits)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:278 -#: payment-gateway/class-sv-wc-payment-gateway.php:407 +#: payment-gateway/class-sv-wc-payment-gateway.php:410 msgid "Card security code is missing" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:305 -#: payment-gateway/class-sv-wc-payment-gateway.php:419 +#: payment-gateway/class-sv-wc-payment-gateway.php:422 msgid "Routing Number is missing" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:312 -#: payment-gateway/class-sv-wc-payment-gateway.php:420 +#: payment-gateway/class-sv-wc-payment-gateway.php:423 msgid "Routing Number is invalid (only digits are allowed)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:318 -#: payment-gateway/class-sv-wc-payment-gateway.php:421 +#: payment-gateway/class-sv-wc-payment-gateway.php:424 msgid "Routing number is invalid (must be 9 digits)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:327 -#: payment-gateway/class-sv-wc-payment-gateway.php:416 +#: payment-gateway/class-sv-wc-payment-gateway.php:419 msgid "Account Number is missing" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:334 -#: payment-gateway/class-sv-wc-payment-gateway.php:417 +#: payment-gateway/class-sv-wc-payment-gateway.php:420 msgid "Account Number is invalid (only digits are allowed)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:340 -#: payment-gateway/class-sv-wc-payment-gateway.php:418 +#: payment-gateway/class-sv-wc-payment-gateway.php:421 msgid "Account number is invalid (must be between 5 and 17 digits)" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:347 -#: payment-gateway/class-sv-wc-payment-gateway.php:415 +#: payment-gateway/class-sv-wc-payment-gateway.php:418 msgid "Drivers license number is invalid" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:353 -#: payment-gateway/class-sv-wc-payment-gateway.php:411 +#: payment-gateway/class-sv-wc-payment-gateway.php:414 msgid "Check Number is invalid (only digits are allowed)" msgstr "" @@ -459,9 +573,9 @@ msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:613 #: payment-gateway/class-sv-wc-payment-gateway-direct.php:688 #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:694 -#: payment-gateway/class-sv-wc-payment-gateway.php:1301 -#: payment-gateway/class-sv-wc-payment-gateway.php:1611 -#: payment-gateway/class-sv-wc-payment-gateway.php:1832 +#: payment-gateway/class-sv-wc-payment-gateway.php:1356 +#: payment-gateway/class-sv-wc-payment-gateway.php:1666 +#: payment-gateway/class-sv-wc-payment-gateway.php:1887 #: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:313 #. translators: Placeholders: %s - transaction ID msgid "(Transaction ID %s)" @@ -745,14 +859,14 @@ msgstr "" msgid "Securely Save to Account" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:425 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:483 #. translators: Placeholders: %s - plugin name msgid "" "%s: WooCommerce is not being forced over SSL; your customer's payment data " "may be at risk." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:482 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:540 #. translators: Placeholders: %1$s - plugin name, %2$s - a #. currency/comma-separated list of currencies, %3$s - tag, %4$s - tag msgid "" @@ -764,7 +878,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:526 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:584 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - tag, %3$s - tag msgid "" @@ -772,7 +886,7 @@ msgid "" "tokenization%3$s to activate %1$s for Subscriptions." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:544 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:602 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - tag, %3$s - tag msgid "" @@ -780,18 +894,18 @@ msgid "" "tokenization%3$s to activate %1$s for Pre-Orders." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:580 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:638 msgid "" "You must enable tokenization for this gateway in order to support automatic " "renewal payments with the WooCommerce Subscriptions extension." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:581 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:639 msgid "Inactive" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:719 -#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:788 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:777 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:846 #. translators: verb, as in "Capture credit card charge". Used when an #. amount has been pre-authorized before, but funds have not yet been captured #. (taken) from the card. Capturing the charge will take the money from the @@ -799,149 +913,141 @@ msgstr "" msgid "Capture Charge" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:412 +#: payment-gateway/class-sv-wc-payment-gateway.php:415 msgid "Check Number is missing" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:413 +#: payment-gateway/class-sv-wc-payment-gateway.php:416 msgid "Drivers license state is missing" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:414 +#: payment-gateway/class-sv-wc-payment-gateway.php:417 msgid "Drivers license number is missing" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:590 +#: payment-gateway/class-sv-wc-payment-gateway.php:593 msgid "Continue" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:590 +#: payment-gateway/class-sv-wc-payment-gateway.php:593 msgid "Place order" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:622 +#: payment-gateway/class-sv-wc-payment-gateway.php:625 msgid "Thank you for your order." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:711 +#: payment-gateway/class-sv-wc-payment-gateway.php:766 msgid "Credit Card" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:713 +#: payment-gateway/class-sv-wc-payment-gateway.php:768 msgid "eCheck" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:731 +#: payment-gateway/class-sv-wc-payment-gateway.php:786 msgid "Pay securely using your credit card." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:733 +#: payment-gateway/class-sv-wc-payment-gateway.php:788 msgid "Pay securely using your checking account." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:752 -msgid "Enable / Disable" -msgstr "" - -#: payment-gateway/class-sv-wc-payment-gateway.php:753 +#: payment-gateway/class-sv-wc-payment-gateway.php:808 msgid "Enable this gateway" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:759 +#: payment-gateway/class-sv-wc-payment-gateway.php:814 msgid "Title" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:761 +#: payment-gateway/class-sv-wc-payment-gateway.php:816 msgid "Payment method title that the customer will see during checkout." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:766 +#: payment-gateway/class-sv-wc-payment-gateway.php:821 msgid "Description" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:768 +#: payment-gateway/class-sv-wc-payment-gateway.php:823 msgid "Payment method description that the customer will see during checkout." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:797 +#: payment-gateway/class-sv-wc-payment-gateway.php:852 msgid "Detailed Decline Messages" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:799 +#: payment-gateway/class-sv-wc-payment-gateway.php:854 msgid "" "Check to enable detailed decline messages to the customer during checkout " "when possible, rather than a generic decline message." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:806 +#: payment-gateway/class-sv-wc-payment-gateway.php:861 msgid "Debug Mode" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:809 +#: payment-gateway/class-sv-wc-payment-gateway.php:864 #. translators: Placeholders: %1$s - tag, %2$s - tag msgid "" "Show Detailed Error Messages and API requests/responses on the checkout " "page and/or save them to the %1$sdebug log%2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:812 +#: payment-gateway/class-sv-wc-payment-gateway.php:867 msgid "Off" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:813 +#: payment-gateway/class-sv-wc-payment-gateway.php:868 msgid "Show on Checkout Page" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:814 +#: payment-gateway/class-sv-wc-payment-gateway.php:869 msgid "Save to Log" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:816 +#: payment-gateway/class-sv-wc-payment-gateway.php:871 #. translators: show debugging information on both checkout page and in the log msgid "Both" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:879 +#: payment-gateway/class-sv-wc-payment-gateway.php:934 msgid "Select the gateway environment to use for transactions." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:927 -msgid "Connection Settings" -msgstr "" - -#: payment-gateway/class-sv-wc-payment-gateway.php:933 +#: payment-gateway/class-sv-wc-payment-gateway.php:988 msgid "Share connection settings" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:935 +#: payment-gateway/class-sv-wc-payment-gateway.php:990 msgid "Use connection/authentication settings from other gateway" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:938 +#: payment-gateway/class-sv-wc-payment-gateway.php:993 msgid "Disabled because the other gateway is using these settings" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:955 +#: payment-gateway/class-sv-wc-payment-gateway.php:1010 msgid "Card Verification (CSC)" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:956 +#: payment-gateway/class-sv-wc-payment-gateway.php:1011 msgid "Display the Card Security Code (CV2) field on checkout" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1254 +#: payment-gateway/class-sv-wc-payment-gateway.php:1309 #. translators: Placeholders: %1$s - site title, %2$s - order number msgid "%1$s - Order %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1294 +#: payment-gateway/class-sv-wc-payment-gateway.php:1349 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - transaction amount. Definitions: #. Capture, as in capture funds from a credit card. msgid "%1$s Capture of %2$s Approved" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1322 +#: payment-gateway/class-sv-wc-payment-gateway.php:1377 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - transaction amount, %3$s - #. transaction status message. Definitions: Capture, as in capture funds from a @@ -949,73 +1055,73 @@ msgstr "" msgid "%1$s Capture Failed: %2$s - %3$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1338 +#: payment-gateway/class-sv-wc-payment-gateway.php:1393 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - failure message. Definitions: #. "capture" as in capturing funds from a credit card. msgid "%1$s Capture Failed: %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1377 +#: payment-gateway/class-sv-wc-payment-gateway.php:1432 #. translators: Placeholders: %1$s - site title, %2$s - order number. #. Definitions: Capture as in capture funds from a credit card. msgid "%1$s - Capture for Order %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1541 +#: payment-gateway/class-sv-wc-payment-gateway.php:1596 #. translators: Placeholders: %1$s - site title, %2$s - order number msgid "%1$s - Refund for Order %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1604 +#: payment-gateway/class-sv-wc-payment-gateway.php:1659 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - a monetary amount msgid "%1$s Refund in the amount of %2$s approved." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1631 +#: payment-gateway/class-sv-wc-payment-gateway.php:1686 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - error code, %3$s - error message msgid "%1$s Refund Failed: %2$s - %3$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1639 +#: payment-gateway/class-sv-wc-payment-gateway.php:1694 #. translators: Placeholders: %1$s - payment gateway title (such as #. Authorize.net, Braintree, etc), %2$s - error message msgid "%1$s Refund Failed: %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1659 +#: payment-gateway/class-sv-wc-payment-gateway.php:1714 #. translators: Placeholders: %s - payment gateway title (such as #. Authorize.net, Braintree, etc) msgid "%s Order completely refunded." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1712 +#: payment-gateway/class-sv-wc-payment-gateway.php:1767 msgid "" "Oops, you cannot partially void this order. Please use the full order " "amount." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1796 +#: payment-gateway/class-sv-wc-payment-gateway.php:1851 #. translators: Placeholders: %1$s - payment gateway title, %2$s - error code, #. %3$s - error message. Void as in to void an order. msgid "%1$s Void Failed: %2$s - %3$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1804 +#: payment-gateway/class-sv-wc-payment-gateway.php:1859 #. translators: Placeholders: %1$s - payment gateway title, %2$s - error #. message. Void as in to void an order. msgid "%1$s Void Failed: %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1825 +#: payment-gateway/class-sv-wc-payment-gateway.php:1880 #. translators: Placeholders: %1$s - payment gateway title, %2$s - a monetary #. amount. Void as in to void an order. msgid "%1$s Void in the amount of %2$s approved." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1918 +#: payment-gateway/class-sv-wc-payment-gateway.php:1973 #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:144 #. translators: Placeholders: %1$s - status code, %2$s - status message #. translators: Placeholders: %1$s - payment request response status code, %2$s @@ -1023,90 +1129,90 @@ msgstr "" msgid "Status code %1$s: %2$s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1921 +#: payment-gateway/class-sv-wc-payment-gateway.php:1976 #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:147 #. translators: Placeholders: %s - status code #. translators: Placeholders: %s - payment request response status code msgid "Status code: %s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1924 +#: payment-gateway/class-sv-wc-payment-gateway.php:1979 #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:150 #. translators: Placeholders; %s - status message #. translators: Placeholders: %s - payment request response status message msgid "Status message: %s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1929 +#: payment-gateway/class-sv-wc-payment-gateway.php:1984 #: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:157 msgid "Transaction ID %s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2040 +#: payment-gateway/class-sv-wc-payment-gateway.php:2095 #. translators: Placeholders: %1$s - payment gateway title, %2$s - message #. (probably reason for the transaction being held for review) msgid "%1$s Transaction Held for Review (%2$s)" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2071 +#: payment-gateway/class-sv-wc-payment-gateway.php:2126 msgid "" "Your order has been received and is being reviewed. Thank you for your " "business." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2116 +#: payment-gateway/class-sv-wc-payment-gateway.php:2171 #. translators: Placeholders: %1$s - payment gateway title, %2$s - error #. message; e.g. Order Note: [Payment method] Payment failed [error] msgid "%1$s Payment Failed (%2$s)" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2133 +#: payment-gateway/class-sv-wc-payment-gateway.php:2188 msgid "An error occurred, please try again or try an alternate form of payment." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2150 +#: payment-gateway/class-sv-wc-payment-gateway.php:2205 #. translators: Placeholders: %1$s - payment gateway title, %2$s - #. message/error msgid "%1$s Transaction Cancelled (%2$s)" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2380 +#: payment-gateway/class-sv-wc-payment-gateway.php:2435 msgid "Transaction Type" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2382 +#: payment-gateway/class-sv-wc-payment-gateway.php:2437 msgid "" "Select how transactions should be processed. Charge submits all " "transactions for settlement, Authorization simply authorizes the order " "total for capture later." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2393 +#: payment-gateway/class-sv-wc-payment-gateway.php:2448 msgid "Charge Virtual-Only Orders" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2395 +#: payment-gateway/class-sv-wc-payment-gateway.php:2450 msgid "" "If the order contains exclusively virtual items, enable this to immediately " "charge, rather than authorize, the transaction." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2563 +#: payment-gateway/class-sv-wc-payment-gateway.php:2618 msgid "Accepted Card Types" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2565 +#: payment-gateway/class-sv-wc-payment-gateway.php:2620 msgid "Select which card types you accept." msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2653 +#: payment-gateway/class-sv-wc-payment-gateway.php:2708 #. translators: #. http:www.cybersource.com/products/payment_security/payment_tokenization/ and #. https:en.wikipedia.org/wiki/Tokenization_(data_security) msgid "Tokenization" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2654 +#: payment-gateway/class-sv-wc-payment-gateway.php:2709 msgid "Allow customers to securely save their payment details for future checkout." msgstr "" @@ -1222,14 +1328,14 @@ msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:679 #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:578 -#: payment-gateway/class-sv-wc-payment-gateway.php:2386 +#: payment-gateway/class-sv-wc-payment-gateway.php:2441 msgctxt "credit card transaction type" msgid "Authorization" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-direct.php:679 #: payment-gateway/class-sv-wc-payment-gateway-hosted.php:580 -#: payment-gateway/class-sv-wc-payment-gateway.php:2385 +#: payment-gateway/class-sv-wc-payment-gateway.php:2440 msgctxt "noun, credit card transaction type" msgid "Charge" msgstr "" @@ -1240,19 +1346,19 @@ msgid "Account" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-helper.php:232 -#: payment-gateway/class-sv-wc-payment-gateway.php:2590 +#: payment-gateway/class-sv-wc-payment-gateway.php:2645 msgctxt "credit card type" msgid "Visa" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-helper.php:236 -#: payment-gateway/class-sv-wc-payment-gateway.php:2591 +#: payment-gateway/class-sv-wc-payment-gateway.php:2646 msgctxt "credit card type" msgid "MasterCard" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-helper.php:240 -#: payment-gateway/class-sv-wc-payment-gateway.php:2592 +#: payment-gateway/class-sv-wc-payment-gateway.php:2647 msgctxt "credit card type" msgid "American Express" msgstr "" @@ -1263,13 +1369,13 @@ msgid "Diners Club" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-helper.php:248 -#: payment-gateway/class-sv-wc-payment-gateway.php:2593 +#: payment-gateway/class-sv-wc-payment-gateway.php:2648 msgctxt "credit card type" msgid "Discover" msgstr "" #: payment-gateway/class-sv-wc-payment-gateway-helper.php:252 -#: payment-gateway/class-sv-wc-payment-gateway.php:2595 +#: payment-gateway/class-sv-wc-payment-gateway.php:2650 msgctxt "credit card type" msgid "JCB" msgstr "" @@ -1289,7 +1395,7 @@ msgctxt "credit card type" msgid "Laser" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:2594 +#: payment-gateway/class-sv-wc-payment-gateway.php:2649 msgctxt "credit card type" msgid "Diners" msgstr "" @@ -1306,7 +1412,7 @@ msgctxt "account type" msgid "Savings" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:1897 +#: payment-gateway/class-sv-wc-payment-gateway.php:1952 msgctxt "hash before order number" msgid "#" msgstr "" @@ -1316,7 +1422,7 @@ msgctxt "hash before order number" msgid "#%s" msgstr "" -#: payment-gateway/class-sv-wc-payment-gateway.php:3141 +#: payment-gateway/class-sv-wc-payment-gateway.php:3196 #. translators: https:www.skyverge.com/for-translators-environments/ msgctxt "software environment" msgid "Production" diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php index b2c2a4360..c7497ce40 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php @@ -32,12 +32,24 @@ class SV_WC_Payment_Gateway_Apple_Pay_API_Response extends SV_WC_API_JSON_Response { + /** + * Gets the status code. + * + * @since 4.6.0-dev + * @return string + */ public function get_status_code() { return $this->statusCode; } + /** + * Gets the status message. + * + * @since 4.6.0-dev + * @return string + */ public function get_status_message() { return $this->statusMessage; diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 5a2cd0707..40656e047 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -428,7 +428,8 @@ public function create_order( $items, $args = array() ) { if ( is_wp_error( $order ) ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 522 ) ); + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 522 ) ); + } else { $order->remove_order_items(); @@ -450,9 +451,7 @@ public function create_order( $items, $args = array() ) { } else { - $order_id = $order->id; - - do_action( 'woocommerce_new_order', $order_id ); + do_action( 'woocommerce_new_order', $order->id ); } } From f3dbdaac9946143a07e2ba6b6fa1ced398ac3e25 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 14:46:57 -0800 Subject: [PATCH 09/41] [Apple Pay] tweak availability and session data --- .../class-sv-wc-payment-gateway-apple-pay.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 40656e047..ba9728133 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -65,7 +65,9 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin ) { add_action( 'wp_ajax_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); - add_filter( 'wc_payment_gateway_' . $this->get_processing_gateway()->get_id() . '_get_order', array( $this, 'add_order_data' ) ); + if ( $this->is_available() ) { + add_filter( 'wc_payment_gateway_' . $this->get_processing_gateway()->get_id() . '_get_order', array( $this, 'add_order_data' ) ); + } } @@ -260,11 +262,14 @@ public function process_payment() { */ public function add_order_data( $order ) { - $payment_data = WC()->session->set( 'apple_pay_payment_response', array() ); + $payment_data = WC()->session->get( 'apple_pay_payment_response', array() ); + + if ( ! empty( $payment_data ) ) { - $order = $this->get_processing_gateway()->add_apple_pay_order_data( $order, $payment_data ); + $order = $this->get_processing_gateway()->add_apple_pay_order_data( $order, $payment_data ); - unset( WC()->session->apple_pay_payment_response ); + unset( WC()->session->apple_pay_payment_response ); + } return $order; } @@ -455,7 +460,7 @@ public function create_order( $items, $args = array() ) { } } - $order->set_payment_method( $this->get_processing_gateway()->get_id() ); + $order->set_payment_method( $this->get_processing_gateway() ); // add line items foreach ( $items as $key => $item ) { From 9fc4f2ef834ff10e9941655334e1527992d8d9a2 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 23 Nov 2016 14:47:35 -0800 Subject: [PATCH 10/41] [Apple Pay] make the order note expiry date optional Apple Pay does not provide an expiry date --- .../class-sv-wc-payment-gateway-direct.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/woocommerce/payment-gateway/class-sv-wc-payment-gateway-direct.php b/woocommerce/payment-gateway/class-sv-wc-payment-gateway-direct.php index e39b5a934..1a3e9ea47 100644 --- a/woocommerce/payment-gateway/class-sv-wc-payment-gateway-direct.php +++ b/woocommerce/payment-gateway/class-sv-wc-payment-gateway-direct.php @@ -672,16 +672,25 @@ protected function do_credit_card_transaction( $order, $response = null ) { // credit card order note $message = sprintf( - /* translators: Placeholders: %1$s - payment method title, %2$s - environment ("Test"), %3$s - transaction type (authorization/charge), %4$s - card type (mastercard, visa, ...), %5$s - last four digits of the card, %6$s - expiry date */ - esc_html__( '%1$s %2$s %3$s Approved: %4$s ending in %5$s (expires %6$s)', 'woocommerce-plugin-framework' ), + /* translators: Placeholders: %1$s - payment method title, %2$s - environment ("Test"), %3$s - transaction type (authorization/charge), %4$s - card type (mastercard, visa, ...), %5$s - last four digits of the card */ + esc_html__( '%1$s %2$s %3$s Approved: %4$s ending in %5$s', 'woocommerce-plugin-framework' ), $this->get_method_title(), $this->is_test_environment() ? esc_html_x( 'Test', 'noun, software environment', 'woocommerce-plugin-framework' ) : '', $this->perform_credit_card_authorization( $order ) ? esc_html_x( 'Authorization', 'credit card transaction type', 'woocommerce-plugin-framework' ) : esc_html_x( 'Charge', 'noun, credit card transaction type', 'woocommerce-plugin-framework' ), SV_WC_Payment_Gateway_Helper::payment_type_to_name( $card_type ), - $last_four, - $order->payment->exp_month . '/' . substr( $order->payment->exp_year, -2 ) + $last_four ); + // add the expiry date if it is available + if ( ! empty( $order->payment->exp_month ) && ! empty( $order->payment->exp_year ) ) { + + $message .= ' ' . sprintf( + /** translators: Placeholders: %s - credit card expiry date */ + __( '(expires %s)', 'woocommerce-plugin-framework' ), + $order->payment->exp_month . '/' . substr( $order->payment->exp_year, -2 ) + ); + } + // adds the transaction id (if any) to the order note if ( $response->get_transaction_id() ) { /* translators: Placeholders: %s - transaction ID */ From 5ce26e87696bb05a6bf1f8f49c2387a5c24e1490 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Fri, 25 Nov 2016 11:58:02 -0800 Subject: [PATCH 11/41] Apple Pay: add response class for better data handling --- ...-payment-gateway-apple-pay-api-request.php | 0 ...payment-gateway-apple-pay-api-response.php | 0 ...ss-sv-wc-payment-gateway-apple-pay-api.php | 0 ...ent-gateway-apple-pay-payment-response.php | 163 ++++++++++++++++++ .../class-sv-wc-payment-gateway-apple-pay.php | 100 +++-------- .../class-sv-wc-payment-gateway.php | 11 +- 6 files changed, 195 insertions(+), 79 deletions(-) rename woocommerce/payment-gateway/apple-pay/{ => api}/class-sv-wc-payment-gateway-apple-pay-api-request.php (100%) rename woocommerce/payment-gateway/apple-pay/{ => api}/class-sv-wc-payment-gateway-apple-pay-api-response.php (100%) rename woocommerce/payment-gateway/apple-pay/{ => api}/class-sv-wc-payment-gateway-apple-pay-api.php (100%) create mode 100644 woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php similarity index 100% rename from woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php rename to woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-response.php similarity index 100% rename from woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php rename to woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-response.php diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api.php similarity index 100% rename from woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php rename to woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api.php diff --git a/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php new file mode 100644 index 000000000..7ed6928ac --- /dev/null +++ b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php @@ -0,0 +1,163 @@ +token->paymentData ) ? $this->token->paymentData : null; + } + + + /** + * Gets the authorization transaction ID. + * + * @since 4.6.0-dev + * @return string + */ + public function get_transaction_id() { + + return ! empty( $this->token->transactionIdentifier ) ? $this->token->transactionIdentifier : null; + } + + + /** + * Gets the authorized card type. + * + * @since 4.6.0-dev + * @return string + */ + public function get_card_type() { + + $card_type = ! empty( $this->token->paymentMethod->network ) ? $this->token->paymentMethod->network : 'card'; + + return SV_WC_Payment_Gateway_Helper::normalize_card_type( $card_type ); + } + + + /** + * Gets the last four digits of the authorized card. + * + * @since 4.6.0-dev + * @return string + */ + public function get_last_four() { + + $last_four = ''; + + if ( ! empty( $this->token->paymentMethod->displayName ) ) { + $last_four = substr( $this->token->paymentMethod->displayName, -4 ); + } + + return $last_four; + } + + + /** + * Gets the billing address. + * + * @since 4.6.0-dev + * @return array + */ + public function get_billing_address() { + + $address = ! empty( $this->response_data->billingContact ) ? $this->response_data->billingContact : null; + + $billing_address = $this->prepare_address( $address ); + + // set the billing email + if ( ! empty( $this->response_data->shippingContact->emailAddress ) ) { + $billing_address['email'] = $this->shippingContact->emailAddress; + } + + // set the billing phone number + if ( ! empty( $this->response_data->shippingContact->phoneNumber ) ) { + $billing_address['phone'] = $this->shippingContact->phoneNumber; + } + + return $billing_address; + } + + + /** + * Gets the shipping address. + * + * @since 4.6.0-dev + * @return array + */ + public function get_shipping_address() { + + $address = ! empty( $this->response_data->shippingContact ) ? $this->response_data->shippingContact : null; + + $shipping_address = $this->prepare_address( $address ); + + return $shipping_address; + } + + + /** + * Prepare an address to WC formatting. + * + * @since 4.6.0-dev + * @param object $contact the address to prepare + * @return array + */ + protected function prepare_address( $contact ) { + + $address = array( + 'first_name' => isset( $contact->givenName ) ? $contact->givenName : '', + 'last_name' => isset( $contact->familyName ) ? $contact->familyName : '', + 'address_1' => isset( $contact->addressLines[0] ) ? $contact->addressLines[0] : '', + 'address_2' => '', + 'city' => isset( $contact->locality ) ? $contact->locality : '', + 'state' => isset( $contact->administrativeArea ) ? $contact->administrativeArea : '', + 'postcode' => isset( $contact->postalCode ) ? $contact->postalCode : '', + 'country' => isset( $contact->countryCode ) ? $contact->countryCode : '', + ); + + if ( ! empty( $contact->addressLines[1] ) ) { + $address['address_2'] = $contact->addressLines[1]; + } + + $address['country'] = strtoupper( $address['country'] ); + + return $address; + } + + +} diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index ba9728133..266bfac46 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -81,6 +81,8 @@ protected function init() { require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php'); require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php'); + if ( is_admin() && ! is_ajax() ) { $this->admin = new SV_WC_Payment_Gateway_Apple_Pay_Admin( $this ); } else { @@ -131,26 +133,24 @@ public function validate_merchant() { */ public function process_payment() { - $type = SV_WC_Helper::get_post( 'type' ); - $payment = json_decode( stripslashes( SV_WC_Helper::get_post( 'payment' ) ) ); + $type = SV_WC_Helper::get_post( 'type' ); + $response = stripslashes( SV_WC_Helper::get_post( 'payment' ) ); try { - if ( ! $payment ) { - throw new SV_WC_Payment_Gateway_Exception( 'Invalid payment data recieved' ); - } + // store the the payment response JSON for later use + WC()->session->set( 'apple_pay_payment_response', $response ); - // store the the payment response for later use - WC()->session->set( 'apple_pay_payment_response', $payment ); + $response = new SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $response ); + + // log the payment response + $this->get_processing_gateway()->add_debug_message( "Apple Pay Payment Response:\n" . $response->to_string_safe() ); // pretend this is at checkout so totals are fully calculated if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { define( 'WOOCOMMERCE_CHECKOUT', true ); } - // log the payment response - $this->get_processing_gateway()->add_debug_message( "Apple Pay Payment Response:\n" . print_r( $payment, true ) ); - // create a new order if ( 'cart' === $type || 'checkout' === $type ) { $order = $this->create_cart_order(); @@ -166,71 +166,15 @@ public function process_payment() { // set the new order ID so it can be resumed in case of failure WC()->session->order_awaiting_payment = $order->id; - - $billing_address = $shipping_address = array(); - - // set the billing address - if ( isset( $payment->billingContact ) ) { - - if ( ! empty( $payment->billingContact->givenName ) ) { - $billing_address['first_name'] = $payment->billingContact->givenName; - $billing_address['last_name'] = $payment->billingContact->familyName; - } - - if ( ! empty( $payment->billingContact->addressLines ) ) { - - $billing_address = array_merge( $billing_address, array( - 'address_1' => $payment->billingContact->addressLines[0], - 'address_2' => ! empty( $payment->billingContact->addressLines[1] ) ? $payment->billingContact->addressLines[1] : '', - 'city' => $payment->billingContact->locality, - 'state' => $payment->billingContact->administrativeArea, - 'postcode' => $payment->billingContact->postalCode, - 'country' => strtoupper( $payment->billingContact->countryCode ), - ) ); - } - - // default the shipping address to the billing address - $shipping_address = $billing_address; - } - - // set the shipping address - if ( isset( $payment->shippingContact ) ) { - - if ( isset( $payment->shippingContact->givenName ) ) { - $shipping_address['first_name'] = $payment->shippingContact->givenName; - $shipping_address['last_name'] = $payment->shippingContact->familyName; - } - - if ( ! empty( $payment->shippingContact->addressLines ) ) { - $shipping_address = array_merge( $shipping_address, array( - 'address_1' => $payment->shippingContact->addressLines[0], - 'address_2' => ! empty( $payment->shippingContact->addressLines[1] ) ? $payment->shippingContact->addressLines[1] : '', - 'city' => $payment->shippingContact->locality, - 'state' => $payment->shippingContact->administrativeArea, - 'postcode' => $payment->shippingContact->postalCode, - 'country' => strtoupper( $payment->shippingContact->countryCode ), - ) ); - } - - // set the billing email - if ( ! empty( $payment->shippingContact->emailAddress ) ) { - $billing_address['email'] = $payment->shippingContact->emailAddress; - } - - // set the billing phone number - if ( ! empty( $payment->shippingContact->phoneNumber ) ) { - $billing_address['phone'] = $payment->shippingContact->phoneNumber; - } - } - - $order->set_address( $billing_address, 'billing' ); - $order->set_address( $shipping_address, 'shipping' ); + $order->set_address( $response->get_billing_address(), 'billing' ); + $order->set_address( $response->get_shipping_address(), 'shipping' ); // process the payment via the gateway $result = $this->get_processing_gateway()->process_payment( $order->id ); // clear the payment request data unset( WC()->session->apple_pay_payment_request ); + unset( WC()->session->apple_pay_payment_response ); unset( WC()->session->order_awaiting_payment ); wp_send_json( $result ); @@ -262,13 +206,13 @@ public function process_payment() { */ public function add_order_data( $order ) { - $payment_data = WC()->session->get( 'apple_pay_payment_response', array() ); + $response_data = WC()->session->get( 'apple_pay_payment_response', '' ); - if ( ! empty( $payment_data ) ) { + if ( ! empty( $response_data ) ) { - $order = $this->get_processing_gateway()->add_apple_pay_order_data( $order, $payment_data ); + $response = new SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $response_data ); - unset( WC()->session->apple_pay_payment_response ); + $order = $this->get_processing_gateway()->get_order_for_apple_pay( $order, $response ); } return $order; @@ -341,6 +285,10 @@ protected function create_product_order() { $payment_request = $this->get_stored_payment_request(); + if ( empty( $payment_request ) ) { + throw new SV_WC_Payment_Gateway_Exception( 'Payment request data is missing.' ); + } + $items = array(); $args = array(); @@ -562,9 +510,9 @@ protected function get_api() { if ( ! $this->api instanceof SV_WC_Payment_Gateway_Apple_Pay_API ) { - require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-api.php'); - require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-request.php'); - require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/class-sv-wc-payment-gateway-apple-pay-api-response.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php'); + require_once( $this->get_plugin()->get_payment_gateway_framework_path() . '/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-response.php'); $this->api = new SV_WC_Payment_Gateway_Apple_Pay_API( $this->get_processing_gateway() ); } diff --git a/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php b/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php index a2d6e2d20..89dad4094 100644 --- a/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php +++ b/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php @@ -738,15 +738,20 @@ public function get_apple_pay_capabilities() { /** * Adds the Apple Pay payment data to the order object. * - * Gateways should override this to see the appropriate values depending on + * Gateways should override this to set the appropriate values depending on * how their processing API needs to handle the data. * * @since 4.6.0-dev * @param \WC_Order the order object - * @param object the authorized payment data. see https://developer.apple.com/reference/applepayjs/payment for structure + * @param \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response the authorize payment response. + * see https://developer.apple.com/reference/applepayjs/payment for structure * @return \WC_Order */ - public function add_apple_pay_order_data( $order, $payment_data ) { + public function get_order_for_apple_pay( $order, SV_WC_Payment_Gateway_Apple_Pay_Payment_Response $response ) { + + $order->payment->account_number = $response->get_last_four(); + $order->payment->last_four = $response->get_last_four(); + $order->payment->card_type = $response->get_card_type(); return $order; } From 85387d261d6c5136e505403c5b976e4ce36c9903 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Fri, 25 Nov 2016 11:58:47 -0800 Subject: [PATCH 12/41] Apple Pay: fix existing session error when the JS handler is re-initialized --- .../js/frontend/sv-wc-payment-gateway-apple-pay.coffee | 6 ++++-- .../js/frontend/sv-wc-payment-gateway-apple-pay.min.js | 2 +- .../js/frontend/sv-wc-payment-gateway-frontend.min.map | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-frontend.min.map diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee index d361eff4c..3dd091187 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee @@ -51,6 +51,7 @@ jQuery( document ).ready ($) -> @buttons = $( '.sv-wc-apple-pay-button' ) + # get a new payment request via AJAX if one is not provided if not @payment_request this.block_ui() @@ -75,6 +76,9 @@ jQuery( document ).ready ($) -> @buttons.show().prop( 'disabled', false ) + # remove any previous click events + $( document.body ).off( 'click', '.sv-wc-apple-pay-button:not([disabled])' ) + $( document.body ).on 'click', '.sv-wc-apple-pay-button:not([disabled])', ( e ) => e.preventDefault() @@ -95,8 +99,6 @@ jQuery( document ).ready ($) -> catch error - @session.abort() - this.fail_payment( error ) diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js index 4386c692a..80a98bb72 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js @@ -64,6 +64,7 @@ } else { this.buttons.show().prop('disabled', false); } + $(document.body).off('click', '.sv-wc-apple-pay-button:not([disabled])'); return $(document.body).on('click', '.sv-wc-apple-pay-button:not([disabled])', (function(_this) { return function(e) { var error; @@ -83,7 +84,6 @@ return _this.session.begin(); } catch (_error) { error = _error; - _this.session.abort(); return _this.fail_payment(error); } }; diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-frontend.min.map b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-frontend.min.map new file mode 100644 index 000000000..00077b35b --- /dev/null +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-frontend.min.map @@ -0,0 +1 @@ +{"version":3,"sources":["sv-wc-payment-gateway-frontend.coffee"],"names":["jQuery","document","ready","$","window","SV_WC_Payment_Form_Handler","args","this","id","id_dasherized","plugin_id","type","csc_required","length","form","handle_checkout_page","handle_pay_page","console","log","handle_add_payment_method_page","params","on","_this","handle_sample_check_hint","trigger","instance","body","format_credit_card_inputs","set_payment_fields","handle_saved_payment_methods","validate_payment_data","submit","val","payment_fields","tokenized_payment_method_selected","is","find","validate_card_data","validate_account_data","payment","change","do_inline_credit_card_validation","$csc","$expiry","validateCardExpiry","addClass","removeClass","validateCardCVC","account_number","csc","errors","expiry","cardExpiryVal","replace","push","card_number_length_invalid","test","card_number_digits_invalid","validateCardNumber","card_number_invalid","card_number_missing","card_exp_date_invalid","cvv_digits_invalid","cvv_length_invalid","cvv_missing","render_errors","routing_number","routing_number_length_invalid","routing_number_digits_invalid","routing_number_missing","account_number_length_invalid","account_number_invalid","account_number_missing","remove","prepend","join","unblock","blur","animate","scrollTop","offset","top","$csc_field","$new_payment_method_selection","parent","slideUp","after","slideDown","$parent_row","closest","next","show","hide","$sample_check"],"mappings":"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","file":"sv-wc-payment-gateway-frontend.min.js","sourceRoot":"","sourcesContent":[null]} \ No newline at end of file From b53eee9ea21b194255eab22fd453e2239cb83daa Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Fri, 25 Nov 2016 11:59:56 -0800 Subject: [PATCH 13/41] Apple Pay: clean up the order creation methods --- .../class-sv-wc-payment-gateway-apple-pay.php | 142 +++++++++++------- 1 file changed, 84 insertions(+), 58 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 266bfac46..f1000d510 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -164,8 +164,6 @@ public function process_payment() { // from here on out, it's up to the gateway to not screw things up. $order->add_order_note( __( 'Apple Pay payment authorized.', 'woocommerce-plugin-framework' ) ); - // set the new order ID so it can be resumed in case of failure - WC()->session->order_awaiting_payment = $order->id; $order->set_address( $response->get_billing_address(), 'billing' ); $order->set_address( $response->get_shipping_address(), 'shipping' ); @@ -338,19 +336,38 @@ protected function create_product_order() { * This is adapted from WooCommerce's `WC_Checkout::create_order()` * * @since 4.6.0-dev - * @param array $args the order args + * @param array $items { + * The items to add to the order. + * + * @type \WC_Product $product The product object. + * @type int $quantity The item quantity. + * @type array $args The item args. See `WC_Abstract_Order::add_product()` for required keys. + * @type array $values The original cart item values. Only included to maintain compatibility + * with the `woocommerce_add_order_item_meta` filter. + * } + * @param array $args { + * Optional. The order args. + * + * @type int $customer_id The user ID for this customer. If left blank, the current user ID will be + * used, or the user will be Guest if there is no current user. + * @type array $fees Any fees to add to the order. See `WC_Abstract_Order::add_fee()` for + * required values. + * @type array $packages Any shipping packages to add to the order. As formatted by + * `WC()->shipping->get_packages()` + * @type array $coupons Any coupons to add to the order. Arrays as + * `$code => array( $amount => 0.00, $tax_amount => 0.00 )` + * @type string $cart_hash The hashed cart object to be used later in case the order is to be resumed. + * * @throws \SV_WC_Plugin_Exception */ public function create_order( $items, $args = array() ) { $args = wp_parse_args( $args, array( - 'customer_id' => get_current_user_id(), - 'fees' => array(), - 'packages' => array(), - 'coupons' => array(), - 'billing_address' => array(), - 'shipping_address' => array(), - 'cart_hash' => '', + 'customer_id' => get_current_user_id(), + 'fees' => array(), + 'packages' => array(), + 'coupons' => array(), + 'cart_hash' => '', ) ); try { @@ -364,49 +381,7 @@ public function create_order( $items, $args = array() ) { 'created_via' => 'apple_pay', ); - // Insert or update the post data - $order_id = absint( WC()->session->order_awaiting_payment ); - - /** - * If there is an order pending payment, we can resume it here so - * long as it has not changed. If the order has changed, i.e. - * different items or cost, create a new order. We use a hash to - * detect changes which is based on cart items + order total. - */ - if ( $order_id && $order_data['cart_hash'] === get_post_meta( $order_id, '_cart_hash', true ) && ( $order = wc_get_order( $order_id ) ) && $order->has_status( array( 'pending', 'failed' ) ) ) { - - $order_data['order_id'] = $order_id; - - $order = wc_update_order( $order_data ); - - if ( is_wp_error( $order ) ) { - - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 522 ) ); - - } else { - - $order->remove_order_items(); - - do_action( 'woocommerce_resume_order', $order_id ); - } - - } else { - - $order = wc_create_order( $order_data ); - - if ( is_wp_error( $order ) ) { - - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); - - } elseif ( false === $order ) { - - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); - - } else { - - do_action( 'woocommerce_new_order', $order->id ); - } - } + $order = $this->get_order_object( $order_data ); $order->set_payment_method( $this->get_processing_gateway() ); @@ -419,6 +394,7 @@ public function create_order( $items, $args = array() ) { throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 525 ) ); } + /** This action is a duplicate from \WC_Checkout::create_order() */ do_action( 'woocommerce_add_order_item_meta', $item_id, $item['values'], $key ); } @@ -431,7 +407,8 @@ public function create_order( $items, $args = array() ) { throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 526 ) ); } - do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $key ); + /** This action is a duplicate from \WC_Checkout::create_order() */ + do_action( 'woocommerce_add_order_fee_meta', $order->id, $item_id, $fee, $key ); } // add shipping packages @@ -447,7 +424,8 @@ public function create_order( $items, $args = array() ) { throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 527 ) ); } - do_action( 'woocommerce_add_shipping_order_item', $order_id, $item_id, $key ); + /** This action is a duplicate from \WC_Checkout::create_order() */ + do_action( 'woocommerce_add_shipping_order_item', $order->id, $item_id, $key ); } } @@ -459,9 +437,6 @@ public function create_order( $items, $args = array() ) { } } - $order->set_address( $args['billing_address'], 'billing' ); - $order->set_address( $args['shipping_address'], 'shipping' ); - $order->calculate_totals(); wc_transaction_query( 'commit' ); @@ -477,6 +452,57 @@ public function create_order( $items, $args = array() ) { } + /** + * Gets an order object for add items. + * + * @since 4.6.0-dev + * @param array $order_data the order data + * @return \WC_Order + * @throws \SV_WC_Plugin_Exception + */ + protected function get_order_object( $order_data ) { + + $order_id = (int) WC()->session->get( 'order_awaiting_payment', 0 ); + + if ( $order_id && $order_data['cart_hash'] === get_post_meta( $order_id, '_cart_hash', true ) && ( $order = wc_get_order( $order_id ) ) && $order->has_status( array( 'pending', 'failed' ) ) ) { + + $order_data['order_id'] = $order_id; + + $order = wc_update_order( $order_data ); + + if ( is_wp_error( $order ) ) { + + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 522 ) ); + + } else { + + $order->remove_order_items(); + + /** This action is a duplicate from \WC_Checkout::create_order() */ + do_action( 'woocommerce_resume_order', $order_id ); + } + + } else { + + $order = wc_create_order( $order_data ); + + if ( is_wp_error( $order ) ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); + } elseif ( false === $order ) { + throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); + } + + // set the new order ID so it can be resumed in case of failure + WC()->session->set( 'order_awaiting_payment', $order->id ); + + /** This action is a duplicate from \WC_Checkout::create_order() */ + do_action( 'woocommerce_new_order', $order->id ); + } + + return $order; + } + + /** * Gets the stored payment request data. * From 65753ef6fe21a4b2d6b7b545a5c93a3609c2029a Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Sat, 26 Nov 2016 11:22:16 -0800 Subject: [PATCH 14/41] Apple Pay: add request/response class unit tests --- phpunit.xml | 3 +- tests/bootstrap.php | 9 + .../payment-gateway/apple-pay-api-request.php | 66 +++++ .../apple-pay-api-response.php | 112 ++++++++ .../apple-pay-payment-response.php | 265 ++++++++++++++++++ ...-payment-gateway-apple-pay-api-request.php | 2 +- ...ent-gateway-apple-pay-payment-response.php | 4 +- 7 files changed, 457 insertions(+), 4 deletions(-) create mode 100644 tests/unit/payment-gateway/apple-pay-api-request.php create mode 100644 tests/unit/payment-gateway/apple-pay-api-response.php create mode 100644 tests/unit/payment-gateway/apple-pay-payment-response.php diff --git a/phpunit.xml b/phpunit.xml index 6fe6ed779..b3baf8eb9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,7 +12,8 @@ > - ./tests/unit + ./tests/unit/ + ./tests/unit/* ./tests/integration diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d2cdb73b1..98eedd0cf 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -67,10 +67,19 @@ public function load_framework() { require_once( $this->framework_dir . '/woocommerce/class-sv-wc-helper.php' ); require_once( $this->framework_dir . '/woocommerce/class-sv-wc-plugin-compatibility.php' ); + // API + require_once( $this->framework_dir . '/woocommerce/api/interface-sv-wc-api-request.php' ); + require_once( $this->framework_dir . '/woocommerce/api/interface-sv-wc-api-response.php' ); + require_once( $this->framework_dir . '/woocommerce/api/abstract-sv-wc-api-json-request.php' ); + require_once( $this->framework_dir . '/woocommerce/api/abstract-sv-wc-api-json-response.php' ); + // payment gateways require_once( $this->framework_dir . '/woocommerce/payment-gateway/class-sv-wc-payment-gateway-helper.php' ); require_once( $this->framework_dir . '/woocommerce/payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-token.php' ); require_once( $this->framework_dir . '/woocommerce/payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php' ); + require_once( $this->framework_dir . '/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php' ); + require_once( $this->framework_dir . '/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-response.php' ); + require_once( $this->framework_dir . '/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php' ); echo "Loaded Framework..." . PHP_EOL; diff --git a/tests/unit/payment-gateway/apple-pay-api-request.php b/tests/unit/payment-gateway/apple-pay-api-request.php new file mode 100644 index 000000000..126436218 --- /dev/null +++ b/tests/unit/payment-gateway/apple-pay-api-request.php @@ -0,0 +1,66 @@ +getMock( 'SV_WC_Payment_Gateway' ); + + $request = new \SV_WC_Payment_Gateway_Apple_Pay_API_Request( $gateway ); + + $request->set_merchant_data( $merchant_id, $domain_name, $display_name ); + + $this->assertEquals( $expected, $request->to_string() ); + } + + + /** + * Data provider for test_set_merchant_data() + * + * @since 4.5.0 + * @return array + */ + public function provider_test_set_merchant_data() { + + return array( + array( + 'merchant', + 'https://domain.com', + 'Domain', + json_encode( array( + 'merchantIdentifier' => 'merchant', + 'domainName' => 'domain.com', + 'displayName' => 'Domain', + ) ), + ), + array( + 'merchant', + 'http://domain.com', + 'Domain', + json_encode( array( + 'merchantIdentifier' => 'merchant', + 'domainName' => 'domain.com', + 'displayName' => 'Domain', + ) ), + ), + ); + } + + +} diff --git a/tests/unit/payment-gateway/apple-pay-api-response.php b/tests/unit/payment-gateway/apple-pay-api-response.php new file mode 100644 index 000000000..3412818bb --- /dev/null +++ b/tests/unit/payment-gateway/apple-pay-api-response.php @@ -0,0 +1,112 @@ +get_error_response_data() ); + + $this->assertEquals( '123', $response->get_status_code() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_status_code() + * + * @since 4.6.0-dev + */ + public function test_get_status_code_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_API_Response( '' ); + + $this->assertNull( $response->get_status_code() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_status_message() + * + * @since 4.6.0-dev + */ + public function test_get_status_message() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_API_Response( $this->get_error_response_data() ); + + $this->assertEquals( 'Error', $response->get_status_message() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_status_message() + * + * @since 4.6.0-dev + */ + public function test_get_status_message_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_API_Response( '' ); + + $this->assertNull( $response->get_status_message() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_merchant_session() + * + * @since 4.6.0-dev + */ + public function test_get_merchant_session() { + + $data = json_encode( array( 'response' ) ); + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_API_Response( $data ); + + $this->assertEquals( $data, $response->get_merchant_session() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_merchant_session() + * + * @since 4.6.0-dev + */ + public function test_get_merchant_session_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_API_Response( '' ); + + $this->assertEquals( '', $response->get_merchant_session() ); + } + + + /** + * Gets an example error response. + * + * @since 4.6.0-dev + * @return string + */ + private function get_error_response_data() { + + $data = array( + 'statusCode' => '123', + 'statusMessage' => 'Error', + ); + + return json_encode( $data ); + } + + +} diff --git a/tests/unit/payment-gateway/apple-pay-payment-response.php b/tests/unit/payment-gateway/apple-pay-payment-response.php new file mode 100644 index 000000000..9efb5d369 --- /dev/null +++ b/tests/unit/payment-gateway/apple-pay-payment-response.php @@ -0,0 +1,265 @@ +get_valid_response_data() ); + + $this->assertEquals( array( 'This is the payment data.' ), $response->get_payment_data() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_payment_data() + * + * @since 4.6.0-dev + */ + public function test_get_payment_data_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( '' ); + + $this->assertEquals( array(), $response->get_payment_data() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_transaction_id() + * + * @since 4.6.0-dev + */ + public function test_get_transaction_id() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $this->get_valid_response_data() ); + + $this->assertEquals( '12345', $response->get_transaction_id() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_transaction_id() + * + * @since 4.6.0-dev + */ + public function test_get_transaction_id_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( '' ); + + $this->assertEquals( '', $response->get_transaction_id() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_card_type() + * + * @since 4.6.0-dev + */ + public function test_get_card_type() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $this->get_valid_response_data() ); + + $this->assertEquals( 'visa', $response->get_card_type() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_card_type() + * + * @since 4.6.0-dev + */ + public function test_get_card_type_blank() { + + Mock::wpFunction( 'wp_list_pluck', array( + 'return' => array( + 'visa' => array( 'Visa' ), + ), + ) ); + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( '' ); + + $this->assertEquals( 'card', $response->get_card_type() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_last_four() + * + * @since 4.6.0-dev + */ + public function test_get_last_four() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $this->get_valid_response_data() ); + + $this->assertEquals( '1234', $response->get_last_four() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_last_four() + * + * @since 4.6.0-dev + */ + public function test_get_last_four_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( '' ); + + $this->assertEquals( '', $response->get_last_four() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_billing_address() + * + * @since 4.6.0-dev + */ + public function test_get_billing_address() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $this->get_valid_response_data() ); + + $expected = array( + 'first_name' => 'Lloyd', + 'last_name' => 'Christmas', + 'address_1' => '333 E Wonderview Ave.', + 'address_2' => 'Room 217', + 'city' => 'Estes Park', + 'state' => 'CO', + 'postcode' => '80517', + 'country' => 'US', + 'email' => 'lloyd@igotworms.com', + 'phone' => '(123) 555-1234', + ); + + $this->assertEquals( $expected, $response->get_billing_address() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_billing_address() + * + * @since 4.6.0-dev + */ + public function test_get_billing_address_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( '' ); + + $expected = array( + 'first_name' => '', + 'last_name' => '', + 'address_1' => '', + 'address_2' => '', + 'city' => '', + 'state' => '', + 'postcode' => '', + 'country' => '', + ); + + $this->assertEquals( $expected, $response->get_billing_address() ); + } + + + /** + * Test for \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_shipping_address() + * + * @since 4.6.0-dev + */ + public function test_get_shipping_address() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $this->get_valid_response_data() ); + + $expected = array( + 'first_name' => 'Mary', + 'last_name' => 'Swanson', + 'address_1' => '2250 Deer Valley Drive', + 'address_2' => '', + 'city' => 'Aspen', + 'state' => 'CO', + 'postcode' => '81611', + 'country' => 'US', + ); + + $this->assertEquals( $expected, $response->get_shipping_address() ); + } + + + /** + * Test for blank \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response::get_shipping_address() + * + * @since 4.6.0-dev + */ + public function test_get_shipping_address_blank() { + + $response = new \SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( '' ); + + $expected = array( + 'first_name' => '', + 'last_name' => '', + 'address_1' => '', + 'address_2' => '', + 'city' => '', + 'state' => '', + 'postcode' => '', + 'country' => '', + ); + + $this->assertEquals( $expected, $response->get_shipping_address() ); + } + + + private function get_valid_response_data() { + + $data = array( + 'token' => array( + 'paymentData' => array( 'This is the payment data.' ), + 'transactionIdentifier' => '12345', + 'paymentMethod' => array( + 'network' => 'Visa', + 'displayName' => 'Visa 1234', + ), + ), + 'billingContact' => array( + 'givenName' => 'Lloyd', + 'familyName' => 'Christmas', + 'addressLines' => array( + '333 E Wonderview Ave.', + 'Room 217', + ), + 'locality' => 'Estes Park', + 'administrativeArea' => 'CO', + 'postalCode' => '80517', + 'countryCode' => 'us', // Apple returns this as lowercase + ), + 'shippingContact' => array( + 'givenName' => 'Mary', + 'familyName' => 'Swanson', + 'addressLines' => array( + '2250 Deer Valley Drive', + ), + 'locality' => 'Aspen', + 'administrativeArea' => 'CO', + 'postalCode' => '81611', + 'countryCode' => 'us', + 'emailAddress' => 'lloyd@igotworms.com', // The shipping address contains the email & phone + 'phoneNumber' => '(123) 555-1234', // The tests ensure they end up with the billing address as per WC standards + ), + ); + + return json_encode( $data ); + } + + +} diff --git a/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php index 7b1187e36..d5155044c 100644 --- a/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php +++ b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-api-request.php @@ -60,7 +60,7 @@ public function set_merchant_data( $merchant_id, $domain_name, $display_name ) { $data = array( 'merchantIdentifier' => $merchant_id, - 'domainName' => str_replace( array( 'http://', 'https://' ), '', home_url() ), + 'domainName' => str_replace( array( 'http://', 'https://' ), '', $domain_name ), 'displayName' => $display_name, ); diff --git a/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php index 7ed6928ac..1e8b442d6 100644 --- a/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php +++ b/woocommerce/payment-gateway/apple-pay/api/class-sv-wc-payment-gateway-apple-pay-payment-response.php @@ -40,7 +40,7 @@ class SV_WC_Payment_Gateway_Apple_Pay_Payment_Response extends SV_WC_API_JSON_Re */ public function get_payment_data() { - return ! empty( $this->token->paymentData ) ? $this->token->paymentData : null; + return ! empty( $this->token->paymentData ) ? (array) $this->token->paymentData : array(); } @@ -52,7 +52,7 @@ public function get_payment_data() { */ public function get_transaction_id() { - return ! empty( $this->token->transactionIdentifier ) ? $this->token->transactionIdentifier : null; + return ! empty( $this->token->transactionIdentifier ) ? $this->token->transactionIdentifier : ''; } From 601525adea29c96722b7a77f6cb483d0ffbc3aab Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Mon, 28 Nov 2016 11:51:24 -0800 Subject: [PATCH 15/41] Apple Pay: clean up JS and improve payment request handling --- ...-wc-payment-gateway-apple-pay-frontend.php | 187 +++++++++--------- .../class-sv-wc-payment-gateway-apple-pay.php | 15 +- .../sv-wc-payment-gateway-apple-pay.coffee | 157 +++++++++------ .../sv-wc-payment-gateway-apple-pay.min.js | 103 +++++----- 4 files changed, 257 insertions(+), 205 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index 43de2eace..9465a2971 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -58,14 +58,14 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin, SV_WC_Payment $this->gateway = $this->get_handler()->get_processing_gateway(); if ( $this->get_handler()->is_available() ) { + add_action( 'wp', array( $this, 'init' ) ); - } - add_action( 'wp_ajax_sv_wc_apple_pay_get_cart_payment_request', array( $this, 'get_cart_payment_request' ) ); - add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_cart_payment_request', array( $this, 'get_cart_payment_request' ) ); + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + } - add_action( 'wp_ajax_sv_wc_apple_pay_get_checkout_payment_request', array( $this, 'get_checkout_payment_request' ) ); - add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_checkout_payment_request', array( $this, 'get_checkout_payment_request' ) ); + add_action( 'wp_ajax_sv_wc_apple_pay_get_payment_request', array( $this, 'get_payment_request' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_get_payment_request', array( $this, 'get_payment_request' ) ); } @@ -76,8 +76,6 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin, SV_WC_Payment */ public function init() { - add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); - if ( is_product() && 'yes' === get_option( 'sv_wc_apple_pay_single_product' ) ) { $this->init_product(); } else if ( is_cart() && 'yes' === get_option( 'sv_wc_apple_pay_cart' ) ) { @@ -158,29 +156,34 @@ public function render_button() { */ public function init_product() { - $product = wc_get_product( get_the_ID() ); + $args = array(); - // simple products only, for now - if ( ! $product || ! $product->is_type( 'simple' ) ) { - return; - } + try { + + $product = wc_get_product( get_the_ID() ); + + if ( ! $product ) { + throw new SV_WC_Payment_Gateway_Exception( 'Product does not exist.' ); + } + + $payment_request = $this->build_product_payment_request( $product ); + + $args['payment_request'] = $payment_request; + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + $this->get_gateway()->add_debug_message( 'Apple Pay Error: ' . $e->getMessage() ); - // if this product can't be purchased, bail - if ( ! $product->is_purchasable() || ! $product->is_in_stock() || ! $product->has_enough_stock( 1 ) ) { return; } - $payment_request = $this->get_product_payment_request( $product ); - /** * Filters the Apple Pay product handler args. * * @since 4.6.0-dev * @param array $args */ - $args = apply_filters( 'sv_wc_apple_pay_product_handler_args', array( - 'payment_request' => $payment_request, - ) ); + $args = apply_filters( 'sv_wc_apple_pay_product_handler_args', $args ); wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Product_Handler(%s);', json_encode( $args ) ) ); @@ -194,8 +197,26 @@ public function init_product() { * @since 4.6.0-dev * @param \WC_Product $product the product object * @return array + * @throws \SV_WC_Payment_Gateway_Exception */ - public function get_product_payment_request( WC_Product $product ) { + public function build_product_payment_request( WC_Product $product ) { + + if ( ! is_user_logged_in() ) { + WC()->session->set_customer_session_cookie( true ); + } + + if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Product::is_subscription( $product ) ) { + throw new SV_WC_Payment_Gateway_Exception( 'Not available for subscription products.' ); + } + + if ( ! $product->is_type( 'simple' ) ) { + throw new SV_WC_Payment_Gateway_Exception( 'Only available for simple products' ); + } + + // if this product can't be purchased, bail + if ( ! $product->is_purchasable() || ! $product->is_in_stock() || ! $product->has_enough_stock( 1 ) ) { + throw new SV_WC_Payment_Gateway_Exception( 'Product is not available for purchase.' ); + } $line_items = array( $product->get_id() => array( @@ -222,7 +243,7 @@ public function get_product_payment_request( WC_Product $product ) { 'amount' => $product->get_price() + $args['shipping_total'], ); - if ( $tax_rate && wc_tax_enabled() ) { + if ( wc_tax_enabled() && $tax_rate ) { $args['tax_total'] = round( $total['amount'] * ( $tax_rate / 100 ), 2 ); @@ -243,8 +264,21 @@ public function get_product_payment_request( WC_Product $product ) { */ public function init_cart() { - if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { - return; + $args = array(); + + try { + + if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { + throw new SV_WC_Payment_Gateway_Exception( 'Apple Pay is not available for carts containing subscription products.' ); + } + + $payment_request = $this->build_cart_payment_request( WC()->cart ); + + $args['payment_request'] = $payment_request; + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + $this->get_gateway()->add_debug_message( $e->getMessage() ); } /** @@ -253,10 +287,7 @@ public function init_cart() { * @since 4.6.0-dev * @param array $args */ - $args = apply_filters( 'sv_wc_apple_pay_cart_handler_args', array( - 'request_action' => 'sv_wc_apple_pay_get_cart_payment_request', - 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_cart_payment_request' ) - ) ); + $args = apply_filters( 'sv_wc_apple_pay_cart_handler_args', $args ); wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Cart_Handler(%s);', json_encode( $args ) ) ); @@ -264,34 +295,6 @@ public function init_cart() { } - /** - * Gets a payment request for the current cart. - * - * @since 4.6.0-dev - */ - public function get_cart_payment_request() { - - check_ajax_referer( 'sv_wc_apple_pay_get_cart_payment_request', 'nonce' ); - - try { - - $request = $this->build_cart_payment_request( WC()->cart ); - - wp_send_json( array( - 'result' => 'success', - 'request' => json_encode( $request ), - ) ); - - } catch ( SV_WC_Payment_Gateway_Exception $e ) { - - wp_send_json( array( - 'result' => 'error', - 'message' => $e->getMessage(), - ) ); - } - } - - /** Checkout functionality ************************************************/ @@ -302,20 +305,13 @@ public function get_cart_payment_request() { */ public function init_checkout() { - if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { - return; - } - /** * Filters the Apple Pay checkout handler args. * * @since 4.6.0-dev * @param array $args */ - $args = apply_filters( 'sv_wc_apple_pay_checkout_handler_args', array( - 'request_action' => 'sv_wc_apple_pay_get_checkout_payment_request', - 'request_nonce' => wp_create_nonce( 'sv_wc_apple_pay_get_checkout_payment_request' ) - ) ); + $args = apply_filters( 'sv_wc_apple_pay_checkout_handler_args', array() ); wc_enqueue_js( sprintf( 'window.sv_wc_apple_pay_handler = new SV_WC_Apple_Pay_Checkout_Handler(%s);', json_encode( $args ) ) ); @@ -323,40 +319,13 @@ public function init_checkout() { } - /** - * Gets a payment request for the checkout. - * - * @since 4.6.0-dev - */ - public function get_checkout_payment_request() { - - check_ajax_referer( 'sv_wc_apple_pay_get_checkout_payment_request', 'nonce' ); - - try { - - $request = $this->build_cart_payment_request( WC()->cart ); - - wp_send_json( array( - 'result' => 'success', - 'request' => json_encode( $request ), - ) ); - - } catch ( SV_WC_Payment_Gateway_Exception $e ) { - - wp_send_json( array( - 'result' => 'error', - 'message' => $e->getMessage(), - ) ); - } - } - - /** * Builds a payment request based on WC cart data. * * @since 4.6.0-dev * @param \WC_Cart $cart the cart object * @return array + * @throws \SV_WC_Payment_Gateway_Exception */ protected function build_cart_payment_request( WC_Cart $cart ) { @@ -448,6 +417,42 @@ protected function build_cart_payment_request( WC_Cart $cart ) { } + /** + * Gets a payment request for the specified type. + * + * @since 4.6.0-dev + */ + public function get_payment_request() { + + $type = SV_WC_Helper::get_post( 'type' ); + + try { + + if ( 'product' === $type ) { + $request = $this->build_product_payment_request( SV_WC_Helper::get_post( 'product_id' ) ); + } else if ( 'cart' === $type || 'checkout' === $type ) { + $request = $this->build_cart_payment_request( WC()->cart ); + } else if ( '' === $type ) { + throw new SV_WC_Payment_Gateway_Exception( 'Payment request type is missing.' ); + } else { + throw new SV_WC_Payment_Gateway_Exception( $type . ' is an invalid payment request type.' ); + } + + wp_send_json( array( + 'result' => 'success', + 'request' => json_encode( $request ), + ) ); + + } catch ( SV_WC_Payment_Gateway_Exception $e ) { + + wp_send_json( array( + 'result' => 'error', + 'message' => $e->getMessage(), + ) ); + } + } + + /** * Builds an Apple Pay payment request. * diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index f1000d510..57901afea 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -151,6 +151,8 @@ public function process_payment() { define( 'WOOCOMMERCE_CHECKOUT', true ); } + $order = null; + // create a new order if ( 'cart' === $type || 'checkout' === $type ) { $order = $this->create_cart_order(); @@ -181,11 +183,14 @@ public function process_payment() { $this->get_processing_gateway()->add_debug_message( 'Apple Pay payment failed. ' . $e->getMessage() ); - $order->add_order_note( sprintf( - /** translators: Placeholders: %s - the error message */ - __( 'Apple Pay payment failed. %s', 'woocommerce-plugin-framework' ), - $e->getMessage() - ) ); + if ( $order ) { + + $order->add_order_note( sprintf( + /** translators: Placeholders: %s - the error message */ + __( 'Apple Pay payment failed. %s', 'woocommerce-plugin-framework' ), + $e->getMessage() + ) ); + } wp_send_json( array( 'result' => 'error', diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee index 3dd091187..daf05b1d2 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee @@ -19,14 +19,11 @@ jQuery( document ).ready ($) -> # Constructs the handler. # - # @since 3.9.2-1 + # @since 4.6.0-dev constructor: (args) -> @params = sv_wc_apple_pay_params - @request_action = args.request_action - @request_nonce = args.request_nonce - @payment_request = args.payment_request if this.is_available() @@ -36,7 +33,7 @@ jQuery( document ).ready ($) -> # Determines if Apple Pay is available. # - # @since 3.9.2-1 + # @since 4.6.0-dev # @return bool is_available: -> @@ -47,38 +44,16 @@ jQuery( document ).ready ($) -> return canMakePayments + # Initializes the handler. + # + # @since 4.6.0-dev init: -> @buttons = $( '.sv-wc-apple-pay-button' ) - # get a new payment request via AJAX if one is not provided - if not @payment_request - - this.block_ui() - - this.get_payment_request().then ( response ) => - - @payment_request = $.parseJSON( response ) - - if @payment_request - - @buttons.show().prop( 'disabled', false ) - - this.unblock_ui() - - , ( response ) => - - console.log '[Apple Pay Error] ' + response - - this.unblock_ui() - - else - + if @payment_request @buttons.show().prop( 'disabled', false ) - # remove any previous click events - $( document.body ).off( 'click', '.sv-wc-apple-pay-button:not([disabled])' ) - $( document.body ).on 'click', '.sv-wc-apple-pay-button:not([disabled])', ( e ) => e.preventDefault() @@ -91,7 +66,7 @@ jQuery( document ).ready ($) -> @session.onvalidatemerchant = ( event ) => this.on_validate_merchant( event ) - @session.onpaymentauthorized = ( event ) => this.on_process_authorization( event ) + @session.onpaymentauthorized = ( event ) => this.on_payment_authorized( event ) @session.oncancel = ( event ) => this.on_cancel_payment( event ) @@ -102,17 +77,44 @@ jQuery( document ).ready ($) -> this.fail_payment( error ) + # Resets the payment request via AJAX. + # + # Extending handlers can call this on change events to refresh the data. + # + # @since 4.6.0-dev + reset_payment_request: ( data = {} ) => + + this.block_ui() + + this.get_payment_request( data ).then ( response ) => + + @payment_request = $.parseJSON( response ) + + @buttons.show().prop( 'disabled', false ) + + this.unblock_ui() + + , ( response ) => + + console.log '[Apple Pay Error] ' + response + + @buttons.prop( 'disabled', true ) + + this.unblock_ui() + + # Gets the payment request via AJAX. # # @since 4.6.0-dev - get_payment_request: => new Promise ( resolve, reject ) => + get_payment_request: ( data ) => new Promise ( resolve, reject ) => - data = { - 'action': @request_action - 'nonce': @request_nonce - 'product_id': @product_id + base_data = { + 'action': 'sv_wc_apple_pay_get_payment_request' + 'type' : @type } + $.extend data, base_data + # retrieve a payment request object $.post @params.ajax_url, data, ( response ) => @@ -122,6 +124,9 @@ jQuery( document ).ready ($) -> reject response.message + # The callback for after the merchant data is validated. + # + # @since 4.6.0-dev on_validate_merchant: ( event ) => this.validate_merchant( event.validationURL ).then ( merchant_session ) => @@ -139,7 +144,7 @@ jQuery( document ).ready ($) -> # Validates the merchant data. # - # @since 3.9.2-1 + # @since 4.6.0-dev # @return object validate_merchant: ( url ) => new Promise ( resolve, reject ) => @@ -159,7 +164,10 @@ jQuery( document ).ready ($) -> reject response.message - on_process_authorization: ( event ) => + # The callback for after the payment data is authorized. + # + # @since 4.6.0-dev + on_payment_authorized: ( event ) => this.process_authorization( event.payment ).then ( response ) => @@ -174,9 +182,9 @@ jQuery( document ).ready ($) -> this.fail_payment 'Payment could no be processed. ' + error - # Processes the transaction data after the payment is authorized. + # Processes the transaction data. # - # @since 3.9.2-1 + # @since 4.6.0-dev process_authorization: ( payment ) => new Promise ( resolve, reject ) => data = { @@ -194,16 +202,25 @@ jQuery( document ).ready ($) -> reject response.message + # The callback for when the payment card is cancelled/dismissed. + # + # @since 4.6.0-dev on_cancel_payment: ( event ) => this.unblock_ui() + # Completes the purchase based on the gateway result. + # + # @since 4.6.0-dev complete_purchase: ( response ) -> window.location = response.redirect + # Fails the purchase based on the gateway result. + # + # @since 4.6.0-dev fail_payment: ( error ) -> console.log '[Apple Pay Error] ' + error @@ -213,9 +230,9 @@ jQuery( document ).ready ($) -> this.render_errors( [ @params.generic_error ] ) - # Sets the Apple Pay payment status depending on the processing result. + # Sets the Apple Pay payment status depending on the gateway result. # - # @since 3.9.2-1 + # @since 4.6.0-dev set_payment_status: ( result ) -> if result is 'success' @@ -226,34 +243,34 @@ jQuery( document ).ready ($) -> @session.completePayment( status ) - # Public: Render any new errors and bring them into the viewport + # Renders any new errors and bring them into the viewport. # - # Returns nothing. + # @since 4.6.0-dev render_errors: ( errors ) -> # hide and remove any previous errors $( '.woocommerce-error, .woocommerce-message' ).remove() # add errors - @payment_form.prepend '
  • ' + errors.join( '
  • ' ) + '
' + @ui_element.prepend '
  • ' + errors.join( '
  • ' ) + '
' # unblock UI - @payment_form.removeClass( 'processing' ).unblock() + @ui_element.removeClass( 'processing' ).unblock() # scroll to top - $( 'html, body' ).animate( { scrollTop: @payment_form.offset().top - 100 }, 1000 ) + $( 'html, body' ).animate( { scrollTop: @ui_element.offset().top - 100 }, 1000 ) - # Blocks the payment form UI + # Blocks the payment form UI. # # @since 4.6.0-dev - block_ui: -> @payment_form.addClass( 'processing' ).block( message: null, overlayCSS: background: '#fff',opacity: 0.6 ) + block_ui: -> @ui_element.block( message: null, overlayCSS: background: '#fff', opacity: 0.6 ) - # Unblocks the payment form UI + # Unblocks the payment form UI. # # @since 4.6.0-dev - unblock_ui: -> @payment_form.removeClass( 'processing' ).unblock() + unblock_ui: -> @ui_element.unblock() # The WooCommerce Apple Pay cart handler class. @@ -264,21 +281,30 @@ jQuery( document ).ready ($) -> # Constructs the handler. # - # @since 3.9.2-1 + # @since 4.6.0-dev constructor: (args) -> @type = 'cart' - @payment_form = $( '.cart_totals' ) + @ui_element = $( '.cart_totals' ) super(args) + + init: => + + super() + # re-init if the cart totals are updated $( document.body ).on 'updated_cart_totals', => - @payment_request = false + @ui_element = $( '.cart_totals' ) - this.init() + @buttons = $( '.sv-wc-apple-pay-button' ) + + @buttons.show() + + this.reset_payment_request() # The WooCommerce Apple Pay checkout handler class. @@ -289,21 +315,24 @@ jQuery( document ).ready ($) -> # Constructs the handler. # - # @since 3.9.2-1 + # @since 4.6.0-dev constructor: (args) -> @type = 'checkout' - @payment_form = $( 'form.woocommerce-checkout' ) + @ui_element = $( 'form.woocommerce-checkout' ) super(args) - # re-init if the cart totals are updated - $( document.body ).on 'update_checkout', => - @payment_request = false + init: => - this.init() + super() + + # re-init if the cart totals are updated + $( document.body ).on 'updated_checkout', => + + this.reset_payment_request() # The WooCommerce Apple Pay product handler class. @@ -314,11 +343,11 @@ jQuery( document ).ready ($) -> # Constructs the handler. # - # @since 3.9.2-1 + # @since 4.6.0-dev constructor: (args) -> @type = 'product' - @payment_form = $( 'form.cart' ) + @ui_element = $( 'form.cart' ) super(args) diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js index 80a98bb72..91783c531 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js @@ -19,13 +19,12 @@ function SV_WC_Apple_Pay_Handler(args) { this.on_cancel_payment = bind(this.on_cancel_payment, this); this.process_authorization = bind(this.process_authorization, this); - this.on_process_authorization = bind(this.on_process_authorization, this); + this.on_payment_authorized = bind(this.on_payment_authorized, this); this.validate_merchant = bind(this.validate_merchant, this); this.on_validate_merchant = bind(this.on_validate_merchant, this); this.get_payment_request = bind(this.get_payment_request, this); + this.reset_payment_request = bind(this.reset_payment_request, this); this.params = sv_wc_apple_pay_params; - this.request_action = args.request_action; - this.request_nonce = args.request_nonce; this.payment_request = args.payment_request; if (this.is_available()) { this.init(); @@ -45,26 +44,9 @@ SV_WC_Apple_Pay_Handler.prototype.init = function() { this.buttons = $('.sv-wc-apple-pay-button'); - if (!this.payment_request) { - this.block_ui(); - this.get_payment_request().then((function(_this) { - return function(response) { - _this.payment_request = $.parseJSON(response); - if (_this.payment_request) { - _this.buttons.show().prop('disabled', false); - return _this.unblock_ui(); - } - }; - })(this), (function(_this) { - return function(response) { - console.log('[Apple Pay Error] ' + response); - return _this.unblock_ui(); - }; - })(this)); - } else { + if (this.payment_request) { this.buttons.show().prop('disabled', false); } - $(document.body).off('click', '.sv-wc-apple-pay-button:not([disabled])'); return $(document.body).on('click', '.sv-wc-apple-pay-button:not([disabled])', (function(_this) { return function(e) { var error; @@ -76,7 +58,7 @@ return _this.on_validate_merchant(event); }; _this.session.onpaymentauthorized = function(event) { - return _this.on_process_authorization(event); + return _this.on_payment_authorized(event); }; _this.session.oncancel = function(event) { return _this.on_cancel_payment(event); @@ -90,15 +72,35 @@ })(this)); }; - SV_WC_Apple_Pay_Handler.prototype.get_payment_request = function() { + SV_WC_Apple_Pay_Handler.prototype.reset_payment_request = function(data) { + if (data == null) { + data = {}; + } + this.block_ui(); + return this.get_payment_request(data).then((function(_this) { + return function(response) { + _this.payment_request = $.parseJSON(response); + _this.buttons.show().prop('disabled', false); + return _this.unblock_ui(); + }; + })(this), (function(_this) { + return function(response) { + console.log('[Apple Pay Error] ' + response); + _this.buttons.prop('disabled', true); + return _this.unblock_ui(); + }; + })(this)); + }; + + SV_WC_Apple_Pay_Handler.prototype.get_payment_request = function(data) { return new Promise((function(_this) { return function(resolve, reject) { - var data; - data = { - 'action': _this.request_action, - 'nonce': _this.request_nonce, - 'product_id': _this.product_id + var base_data; + base_data = { + 'action': 'sv_wc_apple_pay_get_payment_request', + 'type': _this.type }; + $.extend(data, base_data); return $.post(_this.params.ajax_url, data, function(response) { if (response.result === 'success') { return resolve(response.request); @@ -145,7 +147,7 @@ })(this)); }; - SV_WC_Apple_Pay_Handler.prototype.on_process_authorization = function(event) { + SV_WC_Apple_Pay_Handler.prototype.on_payment_authorized = function(event) { return this.process_authorization(event.payment).then((function(_this) { return function(response) { _this.set_payment_status(response.result); @@ -206,15 +208,15 @@ SV_WC_Apple_Pay_Handler.prototype.render_errors = function(errors) { $('.woocommerce-error, .woocommerce-message').remove(); - this.payment_form.prepend('
  • ' + errors.join('
  • ') + '
'); - this.payment_form.removeClass('processing').unblock(); + this.ui_element.prepend('
  • ' + errors.join('
  • ') + '
'); + this.ui_element.removeClass('processing').unblock(); return $('html, body').animate({ - scrollTop: this.payment_form.offset().top - 100 + scrollTop: this.ui_element.offset().top - 100 }, 1000); }; SV_WC_Apple_Pay_Handler.prototype.block_ui = function() { - return this.payment_form.addClass('processing').block({ + return this.ui_element.block({ message: null, overlayCSS: { background: '#fff', @@ -224,7 +226,7 @@ }; SV_WC_Apple_Pay_Handler.prototype.unblock_ui = function() { - return this.payment_form.removeClass('processing').unblock(); + return this.ui_element.unblock(); }; return SV_WC_Apple_Pay_Handler; @@ -234,16 +236,23 @@ extend(SV_WC_Apple_Pay_Cart_Handler, superClass); function SV_WC_Apple_Pay_Cart_Handler(args) { + this.init = bind(this.init, this); this.type = 'cart'; - this.payment_form = $('.cart_totals'); + this.ui_element = $('.cart_totals'); SV_WC_Apple_Pay_Cart_Handler.__super__.constructor.call(this, args); - $(document.body).on('updated_cart_totals', (function(_this) { + } + + SV_WC_Apple_Pay_Cart_Handler.prototype.init = function() { + SV_WC_Apple_Pay_Cart_Handler.__super__.init.call(this); + return $(document.body).on('updated_cart_totals', (function(_this) { return function() { - _this.payment_request = false; - return _this.init(); + _this.ui_element = $('.cart_totals'); + _this.buttons = $('.sv-wc-apple-pay-button'); + _this.buttons.show(); + return _this.reset_payment_request(); }; })(this)); - } + }; return SV_WC_Apple_Pay_Cart_Handler; @@ -252,16 +261,20 @@ extend(SV_WC_Apple_Pay_Checkout_Handler, superClass); function SV_WC_Apple_Pay_Checkout_Handler(args) { + this.init = bind(this.init, this); this.type = 'checkout'; - this.payment_form = $('form.woocommerce-checkout'); + this.ui_element = $('form.woocommerce-checkout'); SV_WC_Apple_Pay_Checkout_Handler.__super__.constructor.call(this, args); - $(document.body).on('update_checkout', (function(_this) { + } + + SV_WC_Apple_Pay_Checkout_Handler.prototype.init = function() { + SV_WC_Apple_Pay_Checkout_Handler.__super__.init.call(this); + return $(document.body).on('updated_checkout', (function(_this) { return function() { - _this.payment_request = false; - return _this.init(); + return _this.reset_payment_request(); }; })(this)); - } + }; return SV_WC_Apple_Pay_Checkout_Handler; @@ -271,7 +284,7 @@ function SV_WC_Apple_Pay_Product_Handler(args) { this.type = 'product'; - this.payment_form = $('form.cart'); + this.ui_element = $('form.cart'); SV_WC_Apple_Pay_Product_Handler.__super__.constructor.call(this, args); } From 0a2fdb63d14ed958eb1ebb689aa519a0258e29d9 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 30 Nov 2016 16:52:28 -0800 Subject: [PATCH 16/41] Apple Pay: refine settings page based on feedback --- ...-sv-wc-payment-gateway-apple-pay-admin.php | 291 ++++++++++++++---- ...-wc-payment-gateway-apple-pay-frontend.php | 20 +- .../class-sv-wc-payment-gateway-apple-pay.php | 73 ++++- .../class-sv-wc-payment-gateway.php | 14 +- 4 files changed, 327 insertions(+), 71 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php index 8b96cd727..9a3b3471f 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php @@ -51,8 +51,14 @@ public function __construct( $handler ) { // output the settings add_action( 'woocommerce_settings_checkout', array( $this, 'add_settings' ) ); + // render the special "static" gateway select + add_action( 'woocommerce_admin_field_static', array( $this, 'render_static_setting' ) ); + // save the settings add_action( 'woocommerce_settings_save_checkout', array( $this, 'save_settings' ) ); + + // add admin notices for configuration options that need attention + add_action( 'admin_footer', array( $this, 'add_admin_notices' ), 10 ); } @@ -92,68 +98,64 @@ public function get_settings() { 'desc' => __( 'Accept Apple Pay', 'woocommerce-plugin-framework' ), 'type' => 'checkbox', 'default' => 'no', - 'checkboxgroup' => 'start', - 'show_if_checked' => 'option', - ), - - array( - 'id' => 'sv_wc_apple_pay_checkout', - 'desc' => __( 'At checkout', 'woocommerce-plugin-framework' ), - 'type' => 'checkbox', - 'default' => 'yes', - 'checkboxgroup' => '', - 'show_if_checked' => 'yes', - ), - - array( - 'id' => 'sv_wc_apple_pay_cart', - 'desc' => __( 'On the Cart page', 'woocommerce-plugin-framework' ), - 'type' => 'checkbox', - 'default' => 'no', - 'checkboxgroup' => '', - 'show_if_checked' => 'yes', ), array( - 'id' => 'sv_wc_apple_pay_single_product', - 'desc' => __( 'On single product pages', 'woocommerce-plugin-framework' ), - 'type' => 'checkbox', - 'default' => 'no', - 'checkboxgroup' => '', - 'show_if_checked' => 'yes', + 'id' => 'sv_wc_apple_pay_display_locations', + 'title' => __( 'Allow Apple Pay on', 'woocommerce-plugin-framework' ), + 'type' => 'multiselect', + 'class' => 'wc-enhanced-select', + 'css' => 'width: 350px;', + 'options' => $this->get_display_location_options(), + 'default' => array_keys( $this->get_display_location_options() ), ), array( 'type' => 'sectionend', ), + ); - array( - 'title' => __( 'Buy Now', 'woocommerce-plugin-framework' ), - 'type' => 'title', - 'desc' => sprintf( - __( 'The %1$sBuy Now with Apple Pay%2$s button is displayed on single product pages, and is only available for simple products. Use these settings to set an optional tax rate and shipping cost for customers who use Buy Now.', 'woocommerce-plugin-framework' ), - '', '' + if ( wc_tax_enabled() || wc_shipping_enabled() ) { + + $buy_settings = array( + array( + 'title' => __( 'Buy Now', 'woocommerce-plugin-framework' ), + 'type' => 'title', + 'desc' => sprintf( + __( 'The %1$sBuy Now with Apple Pay%2$s button is displayed on single product pages, and is only available for simple products. Use these settings to set an optional tax rate and shipping cost for customers who use Buy Now.', 'woocommerce-plugin-framework' ), + '', '' + ), ), - ), + ); - array( - 'id' => 'sv_wc_apple_pay_buy_now_tax_rate', - 'title' => __( 'Tax Rate', 'woocommerce-plugin-framework' ), - 'type' => 'text', - 'desc_tip' => __( 'The optional tax rate percentage to apply to Buy Now orders.', 'woocommerce-plugin-framework' ), - ), + if ( wc_tax_enabled() ) { - array( - 'id' => 'sv_wc_apple_pay_buy_now_shipping_cost', - 'title' => __( 'Shipping Cost', 'woocommerce-plugin-framework' ), - 'type' => 'text', - 'desc_tip' => __( 'The optional flat-rate shipping cost to add to Buy Now orders.', 'woocommerce-plugin-framework' ), - ), + $buy_settings[] = array( + 'id' => 'sv_wc_apple_pay_buy_now_tax_rate', + 'title' => __( 'Tax Rate', 'woocommerce-plugin-framework' ), + 'type' => 'text', + 'desc_tip' => __( 'The optional tax rate percentage to apply to Buy Now orders.', 'woocommerce-plugin-framework' ), + ); + } - array( + if ( wc_shipping_enabled() ) { + + $buy_settings[] = array( + 'id' => 'sv_wc_apple_pay_buy_now_shipping_cost', + 'title' => __( 'Shipping Cost', 'woocommerce-plugin-framework' ), + 'type' => 'text', + 'desc_tip' => __( 'The optional flat-rate shipping cost to add to Buy Now orders.', 'woocommerce-plugin-framework' ), + ); + } + + $buy_settings[] = array( 'type' => 'sectionend', - ), + ); + + $settings = array_merge( $settings, $buy_settings ); + } + $connection_settings = array( array( 'title' => __( 'Connection Settings', 'woocommerce-plugin-framework' ), 'type' => 'title', @@ -163,6 +165,11 @@ public function get_settings() { 'id' => 'sv_wc_apple_pay_merchant_id', 'title' => __( 'Apple Merchant ID', 'woocommerce-plugin-framework' ), 'type' => 'text', + 'desc' => sprintf( + /** translators: Placeholders: %1$s - tag, %2$s - tag */ + __( 'This is found in your %1$sApple developer account%2$s', 'woocommerce-plugin-framework' ), + '', '' + ), ), array( @@ -176,19 +183,37 @@ public function get_settings() { '' . ABSPATH . '' ), ), + ); - array( - 'id' => 'sv_wc_apple_pay_payment_gateway', + $gateway_setting_id = 'sv_wc_apple_pay_payment_gateway'; + $gateway_options = $this->get_gateway_options(); + + if ( 1 === count( $gateway_options ) ) { + + $connection_settings[] = array( + 'id' => $gateway_setting_id, + 'title' => __( 'Processing Gateway', 'woocommerce-plugin-framework' ), + 'type' => 'static', + 'value' => key( $gateway_options ), + 'label' => current( $gateway_options ), + ); + + } else { + + $connection_settings[] = array( + 'id' => $gateway_setting_id, 'title' => __( 'Processing Gateway', 'woocommerce-plugin-framework' ), 'type' => 'select', 'options' => $this->get_gateway_options(), - ), + ); + } - array( - 'type' => 'sectionend', - ), + $connection_settings[] = array( + 'type' => 'sectionend', ); + $settings = array_merge( $settings, $connection_settings ); + /** * Filter the combined settings. * @@ -209,7 +234,30 @@ public function add_settings() { global $current_section; if ( 'apple-pay' === $current_section ) { + WC_Admin_Settings::output_fields( $this->get_settings() ); + + // add inline javascript + ob_start(); + ?> + $( '#sv_wc_apple_pay_display_locations' ).change( function() { + + var locations = $( this ).val(); + var hidden_section = $( '#sv_wc_apple_pay_buy_now_tax_rate, #sv_wc_apple_pay_buy_now_shipping_cost' ).closest( 'table' ); + var hidden_header = $( hidden_section ).prevUntil( 'table' ); + + if ( $.inArray( 'product', locations ) !== -1 ) { + $( hidden_header ).show(); + $( hidden_section ).show(); + } else { + $( hidden_header ).hide(); + $( hidden_section ).hide(); + } + + } ).change(); + with only + * one option. + * + * @since 4.6.0-dev + * @param array $setting + */ + public function render_static_setting( $setting ) { + + ?> + + + + + + + + + + handler->is_enabled() ) { + return; + } + + // if not on the settings screen, bail + if ( ! $this->is_settings_screen() ) { + return; + } + + $errors = array(); + + // HTTPS notice + if ( ! wc_site_is_https() ) { + $errors[] = __( 'Your site must be served over HTTPS with a valid SSL certificate.', 'woocommerce-plugin-framework' ); + } + + // Currency notice + if ( ! in_array( get_woocommerce_currency(), $this->handler->get_accepted_currencies(), true ) ) { + + $accepted_currencies = $this->handler->get_accepted_currencies(); + + $errors[] = sprintf( + /* translators: Placeholders: %1$s - plugin name, %2$s - a currency/comma-separated list of currencies, %3$s - tag, %4$s - tag */ + _n( + 'Accepts payment in %1$s only. %2$sConfigure%3$s WooCommerce to accept %1$s to enable Apple Pay.', + 'Accepts payment in one of %1$s only. %2$sConfigure%3$s WooCommerce to accept one of %1$s to enable Apple Pay.', + count( $accepted_currencies ), + 'woocommerce-plugin-framework' + ), + '' . implode( ', ', $accepted_currencies ) . '', + '', + '' + ); + } + + // bad cert config notice + // this first checks if the option has been set so the notice is not + // displayed without the user having the chance to set it. + if ( false !== $this->handler->get_cert_path() && ! $this->handler->is_cert_configured() ) { + + $errors[] = sprintf( + /** translators: Placeholders: %1$s - tag, %2$s - tag */ + __( 'Your %1$sMerchant Identity Certificate%2$s cannot be found. Please check your path configuration.', 'woocommerce-plugin-framework' ), + '', '' + ); + } + + if ( ! empty( $errors ) ) { + + $message = '' . __( 'Apple Pay is disabled.', 'woocommerce-plugin-framework' ) . ''; + + if ( 1 === count( $errors ) ) { + $message .= ' ' . current( $errors ); + } else { + $message .= '
  • ' . implode( '
  • ', $errors ) . '
'; + } + + $this->handler->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, 'apple-pay-https-required', array( + 'notice_class' => 'error', + 'dismissible' => false, + ) ); + } + } + + + /** + * Determines if the user is currently on the settings screen. + * + * @since 4.6.0-dev + * @return bool + */ + protected function is_settings_screen() { + + return 'wc-settings' === SV_WC_Helper::get_request( 'page' ) && 'apple-pay' === SV_WC_Helper::get_request( 'section' ); + } + + + /** + * Gets the available display location options. + * + * @since 4.6.0-dev + * @return array + */ + protected function get_display_location_options() { + + return array( + 'product' => __( 'Single products', 'woocommerce-plugin-framework' ), + 'cart' => __( 'Cart', 'woocommerce-plugin-framework' ), + 'checkout' => __( 'Checkout', 'woocommerce-plugin-framework' ), + ); + } + + + /** + * Gets the available gateway options. + * + * @since 4.6.0-dev + * @return array + */ protected function get_gateway_options() { $gateways = $this->handler->get_supporting_gateways(); diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index 9465a2971..e0fe51140 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -76,16 +76,30 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin, SV_WC_Payment */ public function init() { - if ( is_product() && 'yes' === get_option( 'sv_wc_apple_pay_single_product' ) ) { + $locations = $this->get_display_locations(); + + if ( is_product() && in_array( 'product', $locations, true ) ) { $this->init_product(); - } else if ( is_cart() && 'yes' === get_option( 'sv_wc_apple_pay_cart' ) ) { + } else if ( is_cart() && in_array( 'cart', $locations, true ) ) { $this->init_cart(); - } else if ( is_checkout() && 'yes' === get_option( 'sv_wc_apple_pay_checkout' ) ) { + } else if ( is_checkout() && in_array( 'checkout', $locations, true ) ) { $this->init_checkout(); } } + /** + * Gets the configured display locations. + * + * @since 4.6.0-dev + * @return array + */ + protected function get_display_locations() { + + return get_option( 'sv_wc_apple_pay_display_locations', array() ); + } + + /** * Enqueues the scripts. * diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 57901afea..382b71d19 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -57,15 +57,16 @@ public function __construct( SV_WC_Payment_Gateway_Plugin $plugin ) { $this->init(); - // validate a merchant via AJAX - add_action( 'wp_ajax_sv_wc_apple_pay_validate_merchant', array( $this, 'validate_merchant' ) ); - add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_validate_merchant', array( $this, 'validate_merchant' ) ); + if ( $this->is_available() ) { - // process the payment via AJAX - add_action( 'wp_ajax_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); - add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); + // validate a merchant via AJAX + add_action( 'wp_ajax_sv_wc_apple_pay_validate_merchant', array( $this, 'validate_merchant' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_validate_merchant', array( $this, 'validate_merchant' ) ); + + // process the payment via AJAX + add_action( 'wp_ajax_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); + add_action( 'wp_ajax_nopriv_sv_wc_apple_pay_process_payment', array( $this, 'process_payment' ) ); - if ( $this->is_available() ) { add_filter( 'wc_payment_gateway_' . $this->get_processing_gateway()->get_id() . '_get_order', array( $this, 'add_order_data' ) ); } } @@ -564,13 +565,17 @@ protected function get_api() { */ public function is_available() { + $is_available = wc_site_is_https() && $this->is_configured(); + + $is_available = $is_available && in_array( get_woocommerce_currency(), $this->get_accepted_currencies(), true ); + /** * Filters whether Apple Pay should be made available to users. * * @since 4.6.0-dev * @param bool $is_available */ - return apply_filters( 'sv_wc_apple_pay_is_available', $this->is_configured() ); + return apply_filters( 'sv_wc_apple_pay_is_available', $is_available ); } @@ -580,23 +585,39 @@ public function is_available() { * @since 4.6.0-dev * @return bool */ - protected function is_configured() { + public function is_configured() { + + if ( ! $this->get_processing_gateway() ) { + return false; + } - $is_configured = $this->is_enabled() && $this->get_merchant_id() && $this->get_processing_gateway() && $this->get_processing_gateway()->is_enabled(); + $is_configured = $this->is_enabled() && $this->get_merchant_id() && $this->get_processing_gateway()->is_enabled(); - $is_configured = $is_configured && $this->get_cert_path() && is_readable( $this->get_cert_path() ); + $is_configured = $is_configured && $this->is_cert_configured(); return $is_configured; } + /** + * Determines if the certification path is set and valid. + * + * @since 4.6.0-dev + * @return bool + */ + public function is_cert_configured() { + + return is_readable( $this->get_cert_path() ); + } + + /** * Determines if Apple Pay is enabled. * * @since 4.6.0-dev * @return bool */ - protected function is_enabled() { + public function is_enabled() { return 'yes' === get_option( 'sv_wc_apple_pay_enabled' ); } @@ -626,6 +647,26 @@ public function get_cert_path() { } + /** + * Gets the currencies accepted by the gateway's Apple Pay integration. + * + * @since 4.6.0-dev + * @return array + */ + public function get_accepted_currencies() { + + $currencies = ( $this->get_processing_gateway() ) ? $this->get_processing_gateway()->get_apple_pay_currencies() : array(); + + /** + * Filters the currencies accepted by the gateway's Apple Pay integration. + * + * @since 4.6.0-dev + * @return array + */ + return apply_filters( 'sv_wc_apple_pay_accepted_currencies', $currencies ); + } + + /** * Gets the gateway's Apple Pay capabilities. * @@ -641,7 +682,9 @@ public function get_capabilities() { 'supportsDebit', ); - $capabilities = array_intersect( $valid_capabilities, $this->get_processing_gateway()->get_apple_pay_capabilities() ); + $gateway_capabilities = ( $this->get_processing_gateway() ) ? $this->get_processing_gateway()->get_apple_pay_capabilities() : array(); + + $capabilities = array_intersect( $valid_capabilities, $gateway_capabilities ); /** * Filters the gateway's Apple Pay capabilities. @@ -662,7 +705,9 @@ public function get_capabilities() { */ public function get_supported_networks() { - $accepted_card_types = array_map( 'SV_WC_Payment_Gateway_Helper::normalize_card_type', $this->get_processing_gateway()->get_card_types() ); + $accepted_card_types = ( $this->get_processing_gateway() ) ? $this->get_processing_gateway()->get_card_types() : array(); + + $accepted_card_types = array_map( 'SV_WC_Payment_Gateway_Helper::normalize_card_type', $accepted_card_types ); $valid_networks = array( SV_WC_Payment_Gateway_Helper::CARD_TYPE_AMEX => 'amex', diff --git a/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php b/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php index 89dad4094..4688e5dcc 100644 --- a/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php +++ b/woocommerce/payment-gateway/class-sv-wc-payment-gateway.php @@ -735,6 +735,18 @@ public function get_apple_pay_capabilities() { } + /** + * Gets the currencies supported by Apple Pay. + * + * @since 4.6.0-dev + * @return array + */ + public function get_apple_pay_currencies() { + + return array( 'USD' ); + } + + /** * Adds the Apple Pay payment data to the order object. * @@ -747,7 +759,7 @@ public function get_apple_pay_capabilities() { * see https://developer.apple.com/reference/applepayjs/payment for structure * @return \WC_Order */ - public function get_order_for_apple_pay( $order, SV_WC_Payment_Gateway_Apple_Pay_Payment_Response $response ) { + public function get_order_for_apple_pay( $order, $response ) { $order->payment->account_number = $response->get_last_four(); $order->payment->last_four = $response->get_last_four(); From 35161f1748a63d91f03b2604725702f2f8bfe672 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Wed, 30 Nov 2016 16:52:45 -0800 Subject: [PATCH 17/41] Apple Pay: better debug messaging --- ...-wc-payment-gateway-apple-pay-frontend.php | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index e0fe51140..22a7a5c7a 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -186,7 +186,7 @@ public function init_product() { } catch ( SV_WC_Payment_Gateway_Exception $e ) { - $this->get_gateway()->add_debug_message( 'Apple Pay Error: ' . $e->getMessage() ); + $this->get_gateway()->add_debug_message( 'Apple Pay Error: ' . $e->getMessage(), 'error' ); return; } @@ -241,7 +241,7 @@ public function build_product_payment_request( WC_Product $product ) { ); $args = array( - 'shipping_required' => $product->needs_shipping(), + 'shipping_required' => wc_shipping_enabled() && $product->needs_shipping(), 'shipping_total' => 0, 'tax_total' => 0, ); @@ -249,7 +249,7 @@ public function build_product_payment_request( WC_Product $product ) { $shipping_cost = (float) get_option( 'sv_wc_apple_pay_buy_now_shipping_cost', 0 ); $tax_rate = (float) get_option( 'sv_wc_apple_pay_buy_now_tax_rate', 0 ); - if ( $product->needs_shipping() && $shipping_cost ) { + if ( $args['shipping_required'] && $shipping_cost ) { $args['shipping_total'] = $shipping_cost; } @@ -286,13 +286,15 @@ public function init_cart() { throw new SV_WC_Payment_Gateway_Exception( 'Apple Pay is not available for carts containing subscription products.' ); } + // TODO: Pre-orders + $payment_request = $this->build_cart_payment_request( WC()->cart ); $args['payment_request'] = $payment_request; } catch ( SV_WC_Payment_Gateway_Exception $e ) { - $this->get_gateway()->add_debug_message( $e->getMessage() ); + $this->get_gateway()->add_debug_message( 'Apple Pay Error: ' . $e->getMessage(), 'error' ); } /** @@ -343,6 +345,13 @@ public function init_checkout() { */ protected function build_cart_payment_request( WC_Cart $cart ) { + // ensure totals are fully calculated + if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { + define( 'WOOCOMMERCE_CHECKOUT', true ); + } + + $cart->calculate_totals(); + // product line items $line_items = array(); @@ -378,32 +387,13 @@ protected function build_cart_payment_request( WC_Cart $cart ) { $args['shipping_required'] = true; - // shipping - $shipping_packages = WC()->shipping->get_packages(); $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() ); // if shipping methods have already been chosen, simply add the total as a line item - // otherwise, we will build the list of options to choose via the Apple Pay card. if ( ! empty( $chosen_shipping_methods ) ) { - $args['shipping_total'] = $cart->shipping_total; - - } else if ( 1 === count( $shipping_packages ) ) { - - $package = current( $shipping_packages ); - - foreach ( $package['rates'] as $rate ) { - - $args['shipping_methods'][] = array( - 'label' => $rate->get_label(), - 'amount' => $this->format_price( $rate->cost ), - 'identifier' => $rate->id, - ); - } - } else { - - throw new SV_WC_Payment_Gateway_Exception( __( 'No shipping totals available.', 'woocommerce-plugin-framework' ) ); + throw new SV_WC_Payment_Gateway_Exception( __( 'No shipping method chosen.', 'woocommerce-plugin-framework' ) ); } } From b2bb753059cb4492d9e1ba4bd83ba89f883c2e37 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Thu, 1 Dec 2016 11:41:30 -0800 Subject: [PATCH 18/41] Apple Pay: tweaks for better log messaging & WC compat --- .../class-sv-wc-plugin-compatibility.php | 19 ++++++ ...-sv-wc-payment-gateway-apple-pay-admin.php | 4 +- ...-wc-payment-gateway-apple-pay-frontend.php | 55 +++++++++------- .../class-sv-wc-payment-gateway-apple-pay.php | 62 +++++++++++++------ .../sv-wc-payment-gateway-apple-pay.coffee | 6 +- .../sv-wc-payment-gateway-apple-pay.min.js | 7 ++- 6 files changed, 107 insertions(+), 46 deletions(-) diff --git a/woocommerce/class-sv-wc-plugin-compatibility.php b/woocommerce/class-sv-wc-plugin-compatibility.php index fdaf83fcc..b968838cc 100644 --- a/woocommerce/class-sv-wc-plugin-compatibility.php +++ b/woocommerce/class-sv-wc-plugin-compatibility.php @@ -87,6 +87,25 @@ public static function product_get_id( WC_Product $product ) { } + /** + * Backports wc_shipping_enabled() to < 2.6.0 + * + * @since 4.6.0-dev + * @return bool + */ + public static function wc_shipping_enabled() { + + if ( self::is_wc_version_gte_2_6() ) { + + return wc_shipping_enabled(); + + } else { + + return 'yes' === get_option( 'woocommerce_calc_shipping' ); + } + } + + /** * Backports wc_help_tip() to WC 2.4.x * diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php index 9a3b3471f..b7c26a259 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php @@ -115,7 +115,7 @@ public function get_settings() { ), ); - if ( wc_tax_enabled() || wc_shipping_enabled() ) { + if ( wc_tax_enabled() || SV_WC_Plugin_Compatibility::wc_shipping_enabled() ) { $buy_settings = array( array( @@ -138,7 +138,7 @@ public function get_settings() { ); } - if ( wc_shipping_enabled() ) { + if ( SV_WC_Plugin_Compatibility::wc_shipping_enabled() ) { $buy_settings[] = array( 'id' => 'sv_wc_apple_pay_buy_now_shipping_cost', diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index 22a7a5c7a..40e05e76e 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -186,8 +186,6 @@ public function init_product() { } catch ( SV_WC_Payment_Gateway_Exception $e ) { - $this->get_gateway()->add_debug_message( 'Apple Pay Error: ' . $e->getMessage(), 'error' ); - return; } @@ -219,12 +217,19 @@ public function build_product_payment_request( WC_Product $product ) { WC()->session->set_customer_session_cookie( true ); } + // no subscription products if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Product::is_subscription( $product ) ) { throw new SV_WC_Payment_Gateway_Exception( 'Not available for subscription products.' ); } + // no pre-order "charge upon release" products + if ( $this->get_plugin()->is_pre_orders_active() && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) { + throw new SV_WC_Payment_Gateway_Exception( 'Not available for pre-order products that are set to charge upon release.' ); + } + + // only simple products if ( ! $product->is_type( 'simple' ) ) { - throw new SV_WC_Payment_Gateway_Exception( 'Only available for simple products' ); + throw new SV_WC_Payment_Gateway_Exception( 'Buy Now is only available for simple products' ); } // if this product can't be purchased, bail @@ -233,7 +238,7 @@ public function build_product_payment_request( WC_Product $product ) { } $line_items = array( - $product->get_id() => array( + $product->id => array( 'name' => $product->get_title(), 'quantity' => 1, 'amount' => $product->get_price(), @@ -241,7 +246,7 @@ public function build_product_payment_request( WC_Product $product ) { ); $args = array( - 'shipping_required' => wc_shipping_enabled() && $product->needs_shipping(), + 'shipping_required' => SV_WC_Plugin_Compatibility::wc_shipping_enabled() && $product->needs_shipping(), 'shipping_total' => 0, 'tax_total' => 0, ); @@ -282,19 +287,13 @@ public function init_cart() { try { - if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { - throw new SV_WC_Payment_Gateway_Exception( 'Apple Pay is not available for carts containing subscription products.' ); - } - - // TODO: Pre-orders - - $payment_request = $this->build_cart_payment_request( WC()->cart ); + $payment_request = $this->build_cart_payment_request(); $args['payment_request'] = $payment_request; } catch ( SV_WC_Payment_Gateway_Exception $e ) { - $this->get_gateway()->add_debug_message( 'Apple Pay Error: ' . $e->getMessage(), 'error' ); + $args['payment_request'] = false; } /** @@ -339,17 +338,26 @@ public function init_checkout() { * Builds a payment request based on WC cart data. * * @since 4.6.0-dev - * @param \WC_Cart $cart the cart object * @return array * @throws \SV_WC_Payment_Gateway_Exception */ - protected function build_cart_payment_request( WC_Cart $cart ) { + protected function build_cart_payment_request() { + + $cart = WC()->cart; // ensure totals are fully calculated if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { define( 'WOOCOMMERCE_CHECKOUT', true ); } + if ( $this->get_plugin()->is_subscriptions_active() && WC_Subscriptions_Cart::cart_contains_subscription() ) { + throw new SV_WC_Payment_Gateway_Exception( 'Cart contains subscriptions.' ); + } + + if ( $this->get_plugin()->is_pre_orders_active() && WC_Pre_Orders_Cart::cart_contains_pre_order() ) { + throw new SV_WC_Payment_Gateway_Exception( 'Cart contains pre-orders.' ); + } + $cart->calculate_totals(); // product line items @@ -358,7 +366,7 @@ protected function build_cart_payment_request( WC_Cart $cart ) { // set the line items foreach ( $cart->get_cart() as $cart_item_key => $item ) { - $line_items[ $item['data']->get_id() ] = array( + $line_items[ $item['data']->id ] = array( 'name' => $item['data']->get_title(), 'quantity' => $item['quantity'], 'amount' => $item['line_subtotal'], @@ -378,7 +386,7 @@ protected function build_cart_payment_request( WC_Cart $cart ) { $args = array(); // discount total - if ( $cart->has_discount() ) { + if ( 0 < count( $cart->applied_coupons ) ) { // TODO: switch to $cart->has_discount()` when WC 2.5 is required $args['discount_total'] = $cart->get_cart_discount_total(); } @@ -405,8 +413,6 @@ protected function build_cart_payment_request( WC_Cart $cart ) { 'amount' => $cart->total, ); - $this->get_gateway()->add_debug_message( 'Generating Apple Pay Payment Request' ); - // build it! $request = $this->build_payment_request( $total, $line_items, $args ); @@ -435,7 +441,7 @@ public function get_payment_request() { if ( 'product' === $type ) { $request = $this->build_product_payment_request( SV_WC_Helper::get_post( 'product_id' ) ); } else if ( 'cart' === $type || 'checkout' === $type ) { - $request = $this->build_cart_payment_request( WC()->cart ); + $request = $this->build_cart_payment_request(); } else if ( '' === $type ) { throw new SV_WC_Payment_Gateway_Exception( 'Payment request type is missing.' ); } else { @@ -449,9 +455,12 @@ public function get_payment_request() { } catch ( SV_WC_Payment_Gateway_Exception $e ) { + $this->get_handler()->log( 'Could not build payment request. ' . $e->getMessage() ); + wp_send_json( array( 'result' => 'error', - 'message' => $e->getMessage(), + 'error' => $e->getMessage(), + 'message' => __( 'Apple Pay is currently unavailable.', 'woocommerce-plugin-framework' ), ) ); } } @@ -488,6 +497,8 @@ public function get_payment_request() { */ protected function build_payment_request( $total, $line_items = array(), $args = array() ) { + $this->get_handler()->log( 'Building payment request.' ); + $args = wp_parse_args( $args, array( 'currency_code' => get_woocommerce_currency(), 'country_code' => get_option( 'woocommerce_default_country' ), @@ -585,7 +596,7 @@ protected function build_payment_request( $total, $line_items = array(), $args = } // log the payment request - $this->get_gateway()->add_debug_message( "Apple Pay Payment Request:\n" . print_r( $request, true ) ); + $this->get_handler()->log( "Payment Request:\n" . print_r( $request, true ) ); return $request; } diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 382b71d19..58d10b630 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -115,7 +115,7 @@ public function validate_merchant() { } catch ( SV_WC_API_Exception $e ) { - $this->get_processing_gateway()->add_debug_message( 'Apple Pay API error. ' . $e->getMessage() ); + $this->log( 'Could not validate merchant. ' . $e->getMessage() ); wp_send_json( array( 'result' => 'error', @@ -130,7 +130,6 @@ public function validate_merchant() { * Processes the payment after the Apple Pay authorization. * * @since 4.6.0-dev - * @throws \SV_WC_Payment_Gateway_Exception */ public function process_payment() { @@ -145,7 +144,7 @@ public function process_payment() { $response = new SV_WC_Payment_Gateway_Apple_Pay_Payment_Response( $response ); // log the payment response - $this->get_processing_gateway()->add_debug_message( "Apple Pay Payment Response:\n" . $response->to_string_safe() ); + $this->log( "Payment Response:\n" . $response->to_string_safe() ); // pretend this is at checkout so totals are fully calculated if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { @@ -182,7 +181,7 @@ public function process_payment() { } catch ( SV_WC_Payment_Gateway_Exception $e ) { - $this->get_processing_gateway()->add_debug_message( 'Apple Pay payment failed. ' . $e->getMessage() ); + $this->log( 'Payment failed. ' . $e->getMessage() ); if ( $order ) { @@ -283,7 +282,7 @@ public function create_cart_order() { * Creates an order from a single product request. * * @since 4.6.0-dev - * @throws \SV_WC_Plugin_Exception + * @throws \SV_WC_Payment_Gateway_Exception */ protected function create_product_order() { @@ -364,7 +363,7 @@ protected function create_product_order() { * `$code => array( $amount => 0.00, $tax_amount => 0.00 )` * @type string $cart_hash The hashed cart object to be used later in case the order is to be resumed. * - * @throws \SV_WC_Plugin_Exception + * @throws \SV_WC_Payment_Gateway_Exception */ public function create_order( $items, $args = array() ) { @@ -378,7 +377,10 @@ public function create_order( $items, $args = array() ) { try { - wc_transaction_query( 'start' ); + if ( SV_WC_Plugin_Compatibility::is_wc_version_gte_2_5() ) { + wc_transaction_query( 'start' ); + } + $order_data = array( 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), @@ -397,7 +399,7 @@ public function create_order( $items, $args = array() ) { $item_id = $order->add_product( $item['product'], $item['quantity'], $item['args'] ); if ( ! $item_id ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 525 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 525 ) ); } /** This action is a duplicate from \WC_Checkout::create_order() */ @@ -410,7 +412,7 @@ public function create_order( $items, $args = array() ) { $item_id = $order->add_fee( $fee ); if ( ! $item_id ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 526 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce' ), 526 ) ); } /** This action is a duplicate from \WC_Checkout::create_order() */ @@ -427,7 +429,7 @@ public function create_order( $items, $args = array() ) { $item_id = $order->add_shipping( $package['rates'][ $shipping_methods[ $key ] ] ); if ( ! $item_id ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 527 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 527 ) ); } /** This action is a duplicate from \WC_Checkout::create_order() */ @@ -439,19 +441,23 @@ public function create_order( $items, $args = array() ) { foreach ( $args['coupons'] as $code => $coupon ) { if ( ! $order->add_coupon( $code, $coupon['amount'], $coupon['tax_amount'] ) ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 529 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 529 ) ); } } $order->calculate_totals(); - wc_transaction_query( 'commit' ); + if ( SV_WC_Plugin_Compatibility::is_wc_version_gte_2_5() ) { + wc_transaction_query( 'commit' ); + } return $order; - } catch ( SV_WC_Plugin_Exception $e ) { + } catch ( SV_WC_Payment_Gateway_Exception $e ) { - wc_transaction_query( 'rollback' ); + if ( SV_WC_Plugin_Compatibility::is_wc_version_gte_2_5() ) { + wc_transaction_query( 'rollback' ); + } throw $e; } @@ -464,7 +470,7 @@ public function create_order( $items, $args = array() ) { * @since 4.6.0-dev * @param array $order_data the order data * @return \WC_Order - * @throws \SV_WC_Plugin_Exception + * @throws \SV_WC_Payment_Gateway_Exception */ protected function get_order_object( $order_data ) { @@ -478,7 +484,7 @@ protected function get_order_object( $order_data ) { if ( is_wp_error( $order ) ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 522 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 522 ) ); } else { @@ -493,9 +499,9 @@ protected function get_order_object( $order_data ) { $order = wc_create_order( $order_data ); if ( is_wp_error( $order ) ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 520 ) ); } elseif ( false === $order ) { - throw new SV_WC_Plugin_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); + throw new SV_WC_Payment_Gateway_Exception( sprintf( __( 'Error %d: Unable to create order. Please try again.', 'woocommerce-plugin-framework' ), 521 ) ); } // set the new order ID so it can be resumed in case of failure @@ -553,6 +559,26 @@ protected function get_api() { } + /** + * Adds a log entry to the gateway's debug log. + * + * @since 4.6.0-dev + * @param string $message the log message to add + */ + public function log( $message ) { + + $gateway = $this->get_processing_gateway(); + + if ( ! $gateway ) { + return; + } + + if ( $gateway->debug_log() ) { + $gateway->get_plugin()->log( '[Apple Pay] ' . $message, $gateway->get_id() ); + } + } + + /** * Determines if Apple Pay is available. * diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee index daf05b1d2..203a820ad 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.coffee @@ -96,7 +96,8 @@ jQuery( document ).ready ($) -> , ( response ) => - console.log '[Apple Pay Error] ' + response + if response.error + console.log '[Apple Pay Error] ' + response.error @buttons.prop( 'disabled', true ) @@ -121,7 +122,7 @@ jQuery( document ).ready ($) -> if response.result is 'success' resolve response.request else - reject response.message + reject response # The callback for after the merchant data is validated. @@ -290,6 +291,7 @@ jQuery( document ).ready ($) -> super(args) + $( document.body ).trigger( 'wc_update_cart' ) init: => diff --git a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js index 91783c531..36d405ed8 100644 --- a/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js +++ b/woocommerce/payment-gateway/assets/js/frontend/sv-wc-payment-gateway-apple-pay.min.js @@ -85,7 +85,9 @@ }; })(this), (function(_this) { return function(response) { - console.log('[Apple Pay Error] ' + response); + if (response.error) { + console.log('[Apple Pay Error] ' + response.error); + } _this.buttons.prop('disabled', true); return _this.unblock_ui(); }; @@ -105,7 +107,7 @@ if (response.result === 'success') { return resolve(response.request); } else { - return reject(response.message); + return reject(response); } }); }; @@ -240,6 +242,7 @@ this.type = 'cart'; this.ui_element = $('.cart_totals'); SV_WC_Apple_Pay_Cart_Handler.__super__.constructor.call(this, args); + $(document.body).trigger('wc_update_cart'); } SV_WC_Apple_Pay_Cart_Handler.prototype.init = function() { From 7929f448bb0a89e37526601c05568cd8c9cb4b33 Mon Sep 17 00:00:00 2001 From: Beka Rice Date: Mon, 12 Dec 2016 14:30:52 -0500 Subject: [PATCH 19/41] [Compat] Add helper to normalize a WC screen ID will be added to several plugins for WC 2.7 compat --- woocommerce/changelog.txt | 1 + .../class-sv-wc-plugin-compatibility.php | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/woocommerce/changelog.txt b/woocommerce/changelog.txt index 59f812902..610495d28 100644 --- a/woocommerce/changelog.txt +++ b/woocommerce/changelog.txt @@ -2,6 +2,7 @@ 2016.nn.nn - version 4.6.0-dev * Feature - Add Apple Pay framework + * Misc - Add helper method to get normalized WooCommerce screen IDs 2016.11.18 - version 4.5.1 * Fix - Prevent a potential fatal error for plugins not using the latest JSON/XML request classes diff --git a/woocommerce/class-sv-wc-plugin-compatibility.php b/woocommerce/class-sv-wc-plugin-compatibility.php index b968838cc..11935b24f 100644 --- a/woocommerce/class-sv-wc-plugin-compatibility.php +++ b/woocommerce/class-sv-wc-plugin-compatibility.php @@ -198,6 +198,30 @@ public static function is_wc_version_gt( $version ) { } + /** WordPress core ******************************************************/ + + + /** + * Normalizes a WooCommerce page screen ID. + * + * Needed because WordPress uses a menu title (which is translatable), not slug, to generate screen ID. + * See details in: https://core.trac.wordpress.org/ticket/21454 + * TODO: Add WP version check when https://core.trac.wordpress.org/ticket/18857 is addressed {BR 2016-12-12} + * + * @since 4.6.0 + * @param string $screen slug the slug for the screen ID to normalize (minus `woocommerce_page_`) + * @return string normalized screen ID + */ + public static function normalize_wc_screen_id( $slug = 'wc-settings' ) { + + // the textdomain usage is intentional here, we need to match the menu title + $prefix = sanitize_title( __( 'WooCommerce', 'woocommerce' ) ); + $screen_id = $prefix . '_page_' . $slug; + + return $screen_id; + } + + /** Subscriptions *********************************************************/ From c50a0670088a9a9e1200a1d28619244ad44af5d6 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Fri, 27 Jan 2017 11:58:36 -0800 Subject: [PATCH 20/41] Apple Pay: add button color setting & improve styling --- ...-sv-wc-payment-gateway-apple-pay-admin.php | 12 ++ ...-wc-payment-gateway-apple-pay-frontend.php | 63 +++++++---- .../class-sv-wc-payment-gateway-apple-pay.php | 12 ++ .../sv-wc-payment-gateway-apple-pay.css | 107 ++++++++++++++++++ 4 files changed, 170 insertions(+), 24 deletions(-) create mode 100644 woocommerce/payment-gateway/assets/css/frontend/sv-wc-payment-gateway-apple-pay.css diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php index b7c26a259..8b22fce62 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php @@ -110,6 +110,18 @@ public function get_settings() { 'default' => array_keys( $this->get_display_location_options() ), ), + array( + 'id' => 'sv_wc_apple_pay_button_style', + 'title' => __( 'Button Style', 'woocommerce-plugin-framework' ), + 'type' => 'select', + 'options' => array( + 'black' => __( 'Black', 'woocommerce-plugin-framework' ), + 'white' => __( 'White', 'woocommerce-plugin-framework' ), + 'white-with-line' => __( 'White with outline', 'woocommerce-plugin-framework' ), + ), + 'default' => 'black', + ), + array( 'type' => 'sectionend', ), diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index 40e05e76e..bb4de551a 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -107,6 +107,8 @@ protected function get_display_locations() { */ public function enqueue_scripts() { + wp_enqueue_style( 'sv-wc-apple-pay', $this->get_plugin()->get_payment_gateway_framework_assets_url() . '/css/frontend/sv-wc-payment-gateway-apple-pay.css', array(), $this->get_plugin()->get_version() ); // TODO: min + wp_enqueue_script( 'sv-wc-apple-pay', $this->get_plugin()->get_payment_gateway_framework_assets_url() . '/js/frontend/sv-wc-payment-gateway-apple-pay.min.js', array( 'jquery' ), $this->get_plugin()->get_version(), true ); /** @@ -136,30 +138,43 @@ public function enqueue_scripts() { */ public function render_button() { - ?> - - - - - - get_handler()->get_button_style() ) { + + case 'black': + $classes[] = 'apple-pay-button-black'; + break; + + case 'white': + $classes[] = 'apple-pay-button-white'; + break; + + case 'white-with-line': + $classes[] = 'apple-pay-button-white-with-line'; + break; + } + + // if on the single product page, add some text + if ( is_product() ) { + $classes[] = 'apple-pay-button-buy-now'; + $button_text = __( 'Buy with', 'woocommerce-plugin-framework' ); + } + + if ( $button_text ) { + $classes[] = 'apple-pay-button-with-text'; + } + + echo ''; } diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php index 58d10b630..9e5003b5f 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay.php @@ -794,6 +794,18 @@ public function get_processing_gateway() { } + /** + * Gets the Apple Pay button style. + * + * @since 4.6.0-dev + * @return string + */ + public function get_button_style() { + + return get_option( 'sv_wc_apple_pay_button_style', 'black' ); + } + + /** * Gets the gateway plugin instance. * diff --git a/woocommerce/payment-gateway/assets/css/frontend/sv-wc-payment-gateway-apple-pay.css b/woocommerce/payment-gateway/assets/css/frontend/sv-wc-payment-gateway-apple-pay.css new file mode 100644 index 000000000..72ea38e4f --- /dev/null +++ b/woocommerce/payment-gateway/assets/css/frontend/sv-wc-payment-gateway-apple-pay.css @@ -0,0 +1,107 @@ +.sv-wc-apple-pay-button { + display: none; + width: 100%; + height: 44px; + margin: 0 0 1em 0; +} + +@supports ( -webkit-appearance: -apple-pay-button ) { + + .sv-wc-apple-pay-button { + -webkit-appearance: -apple-pay-button; + } + + .sv-wc-apple-pay-button.apple-pay-button-black { + -apple-pay-button-style: black; + } + + .sv-wc-apple-pay-button.apple-pay-button-white { + -apple-pay-button-style: white; + } + + .sv-wc-apple-pay-button.apple-pay-button-white-with-line { + -apple-pay-button-style: white-outline; + } + + .sv-wc-apple-pay-button.apple-pay-button-buy-now { + -apple-pay-button-type: buy; + } + + .sv-wc-apple-pay-button.apple-pay-button-buy-now > * { + display: none; + } +} + +@supports not ( -webkit-appearance: -apple-pay-button ) { + + .sv-wc-apple-pay-button { + background-size: 100% 60%; + background-repeat: no-repeat; + background-position: 50% 50%; + border-radius: 5px; + padding: 0px; + box-sizing: border-box; + min-width: 100px; + min-height: 40px; + max-height: 64px; + width: 100%; + } + + .sv-wc-apple-pay-button.apple-pay-button-black { + background-image: -webkit-named-image( apple-pay-logo-white ); + background-color: black; + color: white; + } + + .sv-wc-apple-pay-button.apple-pay-button-white, + .sv-wc-apple-pay-button.apple-pay-button-white-with-line { + background-image: -webkit-named-image( apple-pay-logo-black ); + background-color: white; + color: black; + } + + .sv-wc-apple-pay-button.apple-pay-button-white-with-line { + border: .5px solid black; + } + + .sv-wc-apple-pay-button.apple-pay-button-with-text { + --apple-pay-scale: 1; /* (height / 32) */ + justify-content: center; + font-size: 12px; + background: none; + } + + .sv-wc-apple-pay-button.apple-pay-button-with-text > .text { + font-family: -apple-system; + font-size: calc( 1em * var( --apple-pay-scale ) ); + font-weight: 300; + align-self: center; + margin-right: calc( 2px * var( --apple-pay-scale ) ); + } + + .sv-wc-apple-pay-button.apple-pay-button-with-text > .logo { + width: calc( 35px * var( --scale ) ); + height: 100%; + background-size: 100% 60%; + background-repeat: no-repeat; + background-position: 0 50%; + margin-left: calc( 2px * var( --apple-pay-scale ) ); + border: none; + } + + .sv-wc-apple-pay-button.apple-pay-button-black .apple-pay-button-with-text > .logo { + background-image: -webkit-named-image( apple-pay-logo-white ); + background-color: black; + } + + .sv-wc-apple-pay-button.apple-pay-button-white .apple-pay-button-with-text > .logo, + .sv-wc-apple-pay-button.apple-pay-button-white-with-line .apple-pay-button-with-text > .logo { + background-image: -webkit-named-image( apple-pay-logo-black ); + background-color: white; + } +} + +.sv-wc-apple-pay-checkout-divider { + display: block; + text-align: center; +} From 9e7857dde26aef42b9872e11e5f801c06670a00a Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Thu, 1 Jun 2017 13:17:24 -0700 Subject: [PATCH 21/41] Apple Pay: add filter for activating the feature --- .../class-sv-wc-payment-gateway-plugin.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php b/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php index 6e9c96255..ed1575eb3 100644 --- a/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php +++ b/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php @@ -341,7 +341,16 @@ protected function get_my_payment_methods_instance() { */ public function maybe_init_apple_pay() { - if ( $this->supports_apple_pay() ) { + /** + * Filters whether Apple Pay is activated. + * + * @since 4.7.0-dev + * + * @param bool $activated whether Apple Pay is activated + */ + $activated = (bool) apply_filters( 'wc_payment_gateway_' . $this->get_id() . '_activate_apple_pay', false ); + + if ( $this->supports_apple_pay() && $activated ) { $this->apple_pay = $this->get_apple_pay_instance(); } } From 095ba677a939b8f9f7dfbc8340f33a25525bae06 Mon Sep 17 00:00:00 2001 From: Chase Wiseman Date: Thu, 1 Jun 2017 13:20:33 -0700 Subject: [PATCH 22/41] Apple Pay: improve the frontend JS UX --- ...-wc-payment-gateway-apple-pay-frontend.php | 2 +- .../sv-wc-payment-gateway-apple-pay.coffee | 57 +++++++++---------- .../sv-wc-payment-gateway-apple-pay.min.js | 34 ++++++----- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php index bb4de551a..12294e2c0 100644 --- a/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php +++ b/woocommerce/payment-gateway/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php @@ -168,7 +168,7 @@ public function render_button() { $classes[] = 'apple-pay-button-with-text'; } - echo '