init
This commit is contained in:
+606
@@ -0,0 +1,606 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_Express_Checkout_Ajax_Handler
|
||||
*
|
||||
* @package WooCommerce\Payments
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use WCPay\Constants\Country_Code;
|
||||
use WCPay\Exceptions\Invalid_Price_Exception;
|
||||
use WCPay\Logger;
|
||||
|
||||
/**
|
||||
* WC_Payments_Express_Checkout_Ajax_Handler class.
|
||||
*/
|
||||
class WC_Payments_Express_Checkout_Ajax_Handler {
|
||||
/**
|
||||
* WC_Payments_Express_Checkout_Button_Helper instance.
|
||||
*
|
||||
* @var WC_Payments_Express_Checkout_Button_Helper
|
||||
*/
|
||||
private $express_checkout_button_helper;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param WC_Payments_Express_Checkout_Button_Helper $express_checkout_button_helper Express checkout button helper.
|
||||
*/
|
||||
public function __construct( WC_Payments_Express_Checkout_Button_Helper $express_checkout_button_helper ) {
|
||||
$this->express_checkout_button_helper = $express_checkout_button_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'wc_ajax_wcpay_ece_create_order', [ $this, 'ajax_create_order' ] );
|
||||
add_action( 'wc_ajax_wcpay_ece_pay_for_order', [ $this, 'ajax_pay_for_order' ] );
|
||||
add_action( 'wc_ajax_wcpay_ece_get_shipping_options', [ $this, 'ajax_get_shipping_options' ] );
|
||||
add_action( 'wc_ajax_wcpay_ece_get_cart_details', [ $this, 'ajax_get_cart_details' ] );
|
||||
add_action( 'wc_ajax_wcpay_ece_update_shipping_method', [ $this, 'ajax_update_shipping_method' ] );
|
||||
add_action( 'wc_ajax_wcpay_ece_get_selected_product_data', [ $this, 'ajax_get_selected_product_data' ] );
|
||||
if ( function_exists( 'woocommerce_store_api_register_update_callback' ) ) {
|
||||
woocommerce_store_api_register_update_callback(
|
||||
[
|
||||
'namespace' => 'woopayments/express-checkout/refresh-ui',
|
||||
// do nothing, this callback is needed just to refresh the UI.
|
||||
'callback' => '__return_null',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( WC_Payments_Features::is_tokenized_cart_ece_enabled() ) {
|
||||
add_action(
|
||||
'woocommerce_store_api_checkout_update_order_from_request',
|
||||
[
|
||||
$this,
|
||||
'tokenized_cart_set_payment_method_type',
|
||||
],
|
||||
10,
|
||||
2
|
||||
);
|
||||
add_filter( 'rest_pre_dispatch', [ $this, 'tokenized_cart_store_api_address_normalization' ], 10, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create order. Security is handled by WC.
|
||||
*
|
||||
* @throws Exception If cart is empty. That is handled within the method.
|
||||
*/
|
||||
public function ajax_create_order() {
|
||||
try {
|
||||
if ( WC()->cart->is_empty() ) {
|
||||
throw new Exception( __( 'Empty cart', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
|
||||
define( 'WOOCOMMERCE_CHECKOUT', true );
|
||||
}
|
||||
|
||||
if ( ! defined( 'WCPAY_ECE_CHECKOUT' ) ) {
|
||||
define( 'WCPAY_ECE_CHECKOUT', true );
|
||||
}
|
||||
|
||||
$this->express_checkout_button_helper->normalize_state();
|
||||
|
||||
// In case the state is required, but is missing, add a more descriptive error notice.
|
||||
$this->express_checkout_button_helper->validate_state();
|
||||
|
||||
WC()->checkout()->process_checkout();
|
||||
} catch ( Exception $e ) {
|
||||
Logger::error( 'Failed to process express checkout payment: ' . $e );
|
||||
|
||||
$response = [
|
||||
'result' => 'error',
|
||||
'messages' => $e->getMessage(),
|
||||
];
|
||||
wp_send_json( $response, 400 );
|
||||
}
|
||||
|
||||
die( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles payment requests on the Pay for Order page.
|
||||
*
|
||||
* @throws Exception All exceptions are handled within the method.
|
||||
*/
|
||||
public function ajax_pay_for_order() {
|
||||
check_ajax_referer( 'pay_for_order' );
|
||||
|
||||
try {
|
||||
if (
|
||||
! isset( $_POST['payment_method'] ) || 'woocommerce_payments' !== $_POST['payment_method']
|
||||
|| ! isset( $_POST['order'] ) || ! intval( $_POST['order'] )
|
||||
|| ! isset( $_POST['wcpay-payment-method'] ) || empty( $_POST['wcpay-payment-method'] )
|
||||
) {
|
||||
// Incomplete request.
|
||||
throw new Exception( __( 'Invalid request', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
// Set up an environment, similar to core checkout.
|
||||
wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
|
||||
wc_set_time_limit( 0 );
|
||||
|
||||
// Load the order.
|
||||
$order_id = intval( $_POST['order'] );
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! is_a( $order, WC_Order::class ) ) {
|
||||
throw new Exception( __( 'Invalid order!', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
if ( ! $order->needs_payment() ) {
|
||||
throw new Exception( __( 'This order does not require payment!', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
$this->express_checkout_button_helper->add_order_payment_method_title( $order_id );
|
||||
|
||||
// Load the gateway.
|
||||
$all_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
$gateway = $all_gateways['woocommerce_payments'];
|
||||
$result = $gateway->process_payment( $order_id );
|
||||
|
||||
// process_payment() should only return `success` or throw an exception.
|
||||
if ( ! is_array( $result ) || ! isset( $result['result'] ) || 'success' !== $result['result'] || ! isset( $result['redirect'] ) ) {
|
||||
throw new Exception( __( 'Unable to determine payment success.', 'woocommerce-payments' ) );
|
||||
}
|
||||
|
||||
// Include the order ID in the result.
|
||||
$result['order_id'] = $order_id;
|
||||
|
||||
$result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id );
|
||||
|
||||
wp_send_json( $result );
|
||||
} catch ( Exception $e ) {
|
||||
$order_message = isset( $order_id ) ? "order #$order_id" : 'invalid order';
|
||||
Logger::error( 'Failed to process express checkout payment for ' . $order_message . ': ' . $e );
|
||||
|
||||
$result = [
|
||||
'result' => 'error',
|
||||
'messages' => $e->getMessage(),
|
||||
];
|
||||
wp_send_json( $result, 400 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shipping options.
|
||||
*
|
||||
* @see WC_Cart::get_shipping_packages().
|
||||
* @see WC_Shipping::calculate_shipping().
|
||||
* @see WC_Shipping::get_packages().
|
||||
*/
|
||||
public function ajax_get_shipping_options() {
|
||||
check_ajax_referer( 'wcpay-payment-request-shipping', 'security' );
|
||||
|
||||
$shipping_address = filter_input_array(
|
||||
INPUT_POST,
|
||||
[
|
||||
'country' => FILTER_SANITIZE_SPECIAL_CHARS,
|
||||
'state' => FILTER_SANITIZE_SPECIAL_CHARS,
|
||||
'postcode' => FILTER_SANITIZE_SPECIAL_CHARS,
|
||||
'city' => FILTER_SANITIZE_SPECIAL_CHARS,
|
||||
'address_1' => FILTER_SANITIZE_SPECIAL_CHARS,
|
||||
'address_2' => FILTER_SANITIZE_SPECIAL_CHARS,
|
||||
]
|
||||
);
|
||||
$product_view_options = filter_input_array( INPUT_POST, [ 'is_product_page' => FILTER_SANITIZE_SPECIAL_CHARS ] );
|
||||
$should_show_itemized_view = ! isset( $product_view_options['is_product_page'] ) ? true : filter_var( $product_view_options['is_product_page'], FILTER_VALIDATE_BOOLEAN );
|
||||
|
||||
$data = $this->express_checkout_button_helper->get_shipping_options( $shipping_address, $should_show_itemized_view );
|
||||
wp_send_json( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cart details.
|
||||
*/
|
||||
public function ajax_get_cart_details() {
|
||||
check_ajax_referer( 'wcpay-get-cart-details', 'security' );
|
||||
|
||||
if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
|
||||
define( 'WOOCOMMERCE_CART', true );
|
||||
}
|
||||
|
||||
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
|
||||
define( 'WOOCOMMERCE_CHECKOUT', true );
|
||||
}
|
||||
|
||||
WC()->cart->calculate_totals();
|
||||
|
||||
wp_send_json(
|
||||
array_merge(
|
||||
$this->express_checkout_button_helper->build_display_items(),
|
||||
[
|
||||
'needs_shipping' => WC()->cart->needs_shipping(),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update shipping method.
|
||||
*/
|
||||
public function ajax_update_shipping_method() {
|
||||
check_ajax_referer( 'wcpay-update-shipping-method', 'security' );
|
||||
|
||||
if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
|
||||
define( 'WOOCOMMERCE_CART', true );
|
||||
}
|
||||
|
||||
$shipping_methods = filter_input( INPUT_POST, 'shipping_method', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
|
||||
$this->express_checkout_button_helper->update_shipping_method( $shipping_methods );
|
||||
|
||||
WC()->cart->calculate_totals();
|
||||
|
||||
$product_view_options = filter_input_array( INPUT_POST, [ 'is_product_page' => FILTER_SANITIZE_SPECIAL_CHARS ] );
|
||||
$should_show_itemized_view = ! isset( $product_view_options['is_product_page'] ) ? true : filter_var( $product_view_options['is_product_page'], FILTER_VALIDATE_BOOLEAN );
|
||||
|
||||
$data = $this->express_checkout_button_helper->build_display_items( $should_show_itemized_view );
|
||||
$data['result'] = 'success';
|
||||
|
||||
wp_send_json( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the selected product data.
|
||||
*
|
||||
* @throws Exception If product or stock is unavailable - caught inside function.
|
||||
*/
|
||||
public function ajax_get_selected_product_data() {
|
||||
check_ajax_referer( 'wcpay-get-selected-product-data', 'security' );
|
||||
|
||||
try {
|
||||
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : false;
|
||||
$qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id );
|
||||
$addon_value = isset( $_POST['addon_value'] ) ? max( (float) $_POST['addon_value'], 0 ) : 0;
|
||||
$product = wc_get_product( $product_id );
|
||||
$variation_id = null;
|
||||
$currency = get_woocommerce_currency();
|
||||
$is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === sanitize_text_field( wp_unslash( $_POST['wc_deposit_option'] ) ) : null;
|
||||
$deposit_plan_id = isset( $_POST['wc_deposit_payment_plan'] ) ? absint( $_POST['wc_deposit_payment_plan'] ) : 0;
|
||||
|
||||
if ( ! is_a( $product, 'WC_Product' ) ) {
|
||||
/* translators: product ID */
|
||||
throw new Exception( sprintf( __( 'Product with the ID (%d) cannot be found.', 'woocommerce-payments' ), $product_id ) );
|
||||
}
|
||||
|
||||
if ( ( 'variable' === $product->get_type() || 'variable-subscription' === $product->get_type() ) && isset( $_POST['attributes'] ) ) {
|
||||
$attributes = wc_clean( wp_unslash( $_POST['attributes'] ) );
|
||||
|
||||
$data_store = WC_Data_Store::load( 'product' );
|
||||
$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
|
||||
|
||||
if ( ! empty( $variation_id ) ) {
|
||||
$product = wc_get_product( $variation_id );
|
||||
}
|
||||
}
|
||||
|
||||
// Force quantity to 1 if sold individually and check for existing item in cart.
|
||||
if ( $product->is_sold_individually() ) {
|
||||
$qty = apply_filters( 'wcpay_payment_request_add_to_cart_sold_individually_quantity', 1, $qty, $product_id, $variation_id );
|
||||
}
|
||||
|
||||
if ( ! $product->has_enough_stock( $qty ) ) {
|
||||
/* translators: 1: product name 2: quantity in stock */
|
||||
throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s"; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce-payments' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) );
|
||||
}
|
||||
|
||||
$price = $this->express_checkout_button_helper->get_product_price( $product, $is_deposit, $deposit_plan_id );
|
||||
$total = $qty * $price + $addon_value;
|
||||
|
||||
$quantity_label = 1 < $qty ? ' (x' . $qty . ')' : '';
|
||||
|
||||
$data = [];
|
||||
$items = [];
|
||||
|
||||
$items[] = [
|
||||
'label' => $product->get_name() . $quantity_label,
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $total, $currency ),
|
||||
];
|
||||
|
||||
$total_tax = 0;
|
||||
foreach ( $this->express_checkout_button_helper->get_taxes_like_cart( $product, $price ) as $tax ) {
|
||||
$total_tax += $tax;
|
||||
|
||||
$items[] = [
|
||||
'label' => __( 'Tax', 'woocommerce-payments' ),
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $tax, $currency ),
|
||||
'pending' => 0 === $tax,
|
||||
];
|
||||
}
|
||||
|
||||
if ( wc_shipping_enabled() && $product->needs_shipping() ) {
|
||||
$items[] = [
|
||||
'label' => __( 'Shipping', 'woocommerce-payments' ),
|
||||
'amount' => 0,
|
||||
'pending' => true,
|
||||
];
|
||||
|
||||
$data['shippingOptions'] = [
|
||||
'id' => 'pending',
|
||||
'label' => __( 'Pending', 'woocommerce-payments' ),
|
||||
'detail' => '',
|
||||
'amount' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$data['displayItems'] = $items;
|
||||
$data['total'] = [
|
||||
'label' => $this->express_checkout_button_helper->get_total_label(),
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $total + $total_tax, $currency ),
|
||||
'pending' => true,
|
||||
];
|
||||
|
||||
$data['needs_shipping'] = wc_shipping_enabled() && 0 !== wc_get_shipping_method_count( true ) && $product->needs_shipping();
|
||||
$data['currency'] = strtolower( get_woocommerce_currency() );
|
||||
$data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 );
|
||||
$data['has_free_trial'] = class_exists( 'WC_Subscriptions_Product' ) ? WC_Subscriptions_Product::get_trial_length( $product ) > 0 : false;
|
||||
|
||||
wp_send_json( $data );
|
||||
} catch ( Exception $e ) {
|
||||
if ( is_a( $e, Invalid_Price_Exception::class ) ) {
|
||||
Logger::log( $e->getMessage() );
|
||||
}
|
||||
wp_send_json( [ 'error' => wp_strip_all_tags( $e->getMessage() ) ], 500 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current product to the cart. Used on product detail page.
|
||||
*/
|
||||
public function ajax_add_to_cart() {
|
||||
check_ajax_referer( 'wcpay-add-to-cart', 'security' );
|
||||
|
||||
if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
|
||||
define( 'WOOCOMMERCE_CART', true );
|
||||
}
|
||||
|
||||
WC()->shipping->reset_shipping();
|
||||
|
||||
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : false;
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( ! $product ) {
|
||||
wp_send_json(
|
||||
[
|
||||
'error' => [
|
||||
'code' => 'invalid_product_id',
|
||||
'message' => __( 'Invalid product id', 'woocommerce-payments' ),
|
||||
],
|
||||
],
|
||||
404
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$quantity = $this->express_checkout_button_helper->get_quantity();
|
||||
|
||||
$product_type = $product->get_type();
|
||||
|
||||
$is_add_to_cart_valid = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
|
||||
|
||||
if ( ! $is_add_to_cart_valid ) {
|
||||
// Some extensions error messages needs to be
|
||||
// submitted to show error messages.
|
||||
wp_send_json(
|
||||
[
|
||||
'error' => true,
|
||||
'submit' => true,
|
||||
],
|
||||
400
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// First empty the cart to prevent wrong calculation.
|
||||
WC()->cart->empty_cart();
|
||||
|
||||
if ( ( 'variable' === $product_type || 'variable-subscription' === $product_type ) && isset( $_POST['attributes'] ) ) {
|
||||
$attributes = wc_clean( wp_unslash( $_POST['attributes'] ) );
|
||||
|
||||
$data_store = WC_Data_Store::load( 'product' );
|
||||
$variation_id = $data_store->find_matching_product_variation( $product, $attributes );
|
||||
|
||||
WC()->cart->add_to_cart( $product->get_id(), $quantity, $variation_id, $attributes );
|
||||
}
|
||||
|
||||
if ( in_array( $product_type, [ 'simple', 'variation', 'subscription', 'subscription_variation', 'booking', 'bundle', 'mix-and-match' ], true ) ) {
|
||||
$allowed_item_data = [
|
||||
// Teams for WooCommerce Memberships fields.
|
||||
'team_name',
|
||||
'team_owner_takes_seat',
|
||||
];
|
||||
$item_data = [];
|
||||
|
||||
foreach ( $allowed_item_data as $item ) {
|
||||
if ( isset( $_POST[ $item ] ) ) {
|
||||
$item_data[ $item ] = wc_clean( wp_unslash( $_POST[ $item ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
WC()->cart->add_to_cart( $product->get_id(), $quantity, 0, [], $item_data );
|
||||
}
|
||||
|
||||
WC()->cart->calculate_totals();
|
||||
|
||||
if ( 'booking' === $product_type ) {
|
||||
$booking_id = $this->express_checkout_button_helper->get_booking_id_from_cart();
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$data += $this->express_checkout_button_helper->build_display_items();
|
||||
$data['result'] = 'success';
|
||||
|
||||
if ( ! empty( $booking_id ) ) {
|
||||
$data['bookingId'] = $booking_id;
|
||||
}
|
||||
|
||||
wp_send_json( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the checkout order based on the request, to set the Apple Pay/Google Pay payment method title.
|
||||
*
|
||||
* @param \WC_Order $order The order to be updated.
|
||||
* @param \WP_REST_Request $request Store API request to update the order.
|
||||
*/
|
||||
public function tokenized_cart_set_payment_method_type( \WC_Order $order, \WP_REST_Request $request ) {
|
||||
if ( ! isset( $request['payment_method'] ) || 'woocommerce_payments' !== $request['payment_method'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $request['payment_data'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_data = [];
|
||||
foreach ( $request['payment_data'] as $data ) {
|
||||
$payment_data[ sanitize_key( $data['key'] ) ] = wc_clean( $data['value'] );
|
||||
}
|
||||
|
||||
if ( empty( $payment_data['payment_request_type'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_request_type = wc_clean( wp_unslash( $payment_data['payment_request_type'] ) );
|
||||
|
||||
$payment_method_titles = [
|
||||
'apple_pay' => 'Apple Pay',
|
||||
'google_pay' => 'Google Pay',
|
||||
];
|
||||
|
||||
$suffix = apply_filters( 'wcpay_payment_request_payment_method_title_suffix', 'WooPayments' );
|
||||
if ( ! empty( $suffix ) ) {
|
||||
$suffix = " ($suffix)";
|
||||
}
|
||||
|
||||
$payment_method_title = isset( $payment_method_titles[ $payment_request_type ] ) ? $payment_method_titles[ $payment_request_type ] : 'Payment Request';
|
||||
$order->set_payment_method_title( $payment_method_title . $suffix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Pay/Apple Pay parameters for address data might need some massaging for some of the countries.
|
||||
* Ensuring that the Store API doesn't throw a `rest_invalid_param` error message for some of those scenarios.
|
||||
*
|
||||
* @param mixed $response Response to replace the requested version with.
|
||||
* @param \WP_REST_Server $server Server instance.
|
||||
* @param \WP_REST_Request $request Request used to generate the response.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function tokenized_cart_store_api_address_normalization( $response, $server, $request ) {
|
||||
if ( 'true' !== $request->get_header( 'X-WooPayments-Tokenized-Cart' ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// header added as additional layer of security.
|
||||
$nonce = $request->get_header( 'X-WooPayments-Tokenized-Cart-Nonce' );
|
||||
if ( ! wp_verify_nonce( $nonce, 'woopayments_tokenized_cart_nonce' ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// This route is used to get shipping rates.
|
||||
// GooglePay/ApplePay might provide us with "trimmed" zip codes.
|
||||
// If that's the case, let's temporarily allow to skip the zip code validation, in order to get some shipping rates.
|
||||
$is_update_customer_route = $request->get_route() === '/wc/store/v1/cart/update-customer';
|
||||
if ( $is_update_customer_route ) {
|
||||
add_filter( 'woocommerce_validate_postcode', [ $this, 'maybe_skip_postcode_validation' ], 10, 3 );
|
||||
}
|
||||
|
||||
$request_data = $request->get_json_params();
|
||||
if ( isset( $request_data['shipping_address'] ) ) {
|
||||
$request->set_param( 'shipping_address', $this->transform_ece_address_state_data( $request_data['shipping_address'] ) );
|
||||
// on the "update customer" route, GooglePay/Apple pay might provide redacted postcode data.
|
||||
// we need to modify the zip code to ensure that shipping zone identification still works.
|
||||
if ( $is_update_customer_route ) {
|
||||
$request->set_param( 'shipping_address', $this->transform_ece_address_postcode_data( $request_data['shipping_address'] ) );
|
||||
}
|
||||
}
|
||||
if ( isset( $request_data['billing_address'] ) ) {
|
||||
$request->set_param( 'billing_address', $this->transform_ece_address_state_data( $request_data['billing_address'] ) );
|
||||
// on the "update customer" route, GooglePay/Apple pay might provide redacted postcode data.
|
||||
// we need to modify the zip code to ensure that shipping zone identification still works.
|
||||
if ( $is_update_customer_route ) {
|
||||
$request->set_param( 'billing_address', $this->transform_ece_address_postcode_data( $request_data['billing_address'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows certain "redacted" postcodes for some countries to bypass WC core validation.
|
||||
*
|
||||
* @param bool $valid Whether the postcode is valid.
|
||||
* @param string $postcode The postcode in question.
|
||||
* @param string $country The country for the postcode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function maybe_skip_postcode_validation( $valid, $postcode, $country ) {
|
||||
if ( ! in_array( $country, [ Country_Code::UNITED_KINGDOM, Country_Code::CANADA ], true ) ) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
// We padded the string with `0` in the `get_normalized_postal_code` method.
|
||||
// It's a flimsy check, but better than nothing.
|
||||
// Plus, this check is only made for the scenarios outlined in the `tokenized_cart_store_api_address_normalization` method.
|
||||
if ( substr( $postcode, - 1 ) === '0' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a GooglePay/ApplePay state address data fields into values that are valid for WooCommerce.
|
||||
*
|
||||
* @param array $address The address to normalize from the GooglePay/ApplePay request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function transform_ece_address_state_data( $address ) {
|
||||
$country = $address['country'] ?? '';
|
||||
if ( empty( $country ) ) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
// States from Apple Pay or Google Pay are in long format, we need their short format..
|
||||
$state = $address['state'] ?? '';
|
||||
if ( ! empty( $state ) ) {
|
||||
$address['state'] = $this->express_checkout_button_helper->get_normalized_state( $state, $country );
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a GooglePay/ApplePay postcode address data fields into values that are valid for WooCommerce.
|
||||
*
|
||||
* @param array $address The address to normalize from the GooglePay/ApplePay request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function transform_ece_address_postcode_data( $address ) {
|
||||
$country = $address['country'] ?? '';
|
||||
if ( empty( $country ) ) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
// Normalizes postal code in case of redacted data from Apple Pay or Google Pay.
|
||||
$postcode = $address['postcode'] ?? '';
|
||||
if ( ! empty( $postcode ) ) {
|
||||
$address['postcode'] = $this->express_checkout_button_helper->get_normalized_postal_code( $postcode, $country );
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_Express_Checkout_Button_Display_Handler
|
||||
*
|
||||
* @package WooCommerce\Payments
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Class WC_Payments_Express_Checkout_Button_Display_Handler
|
||||
*/
|
||||
class WC_Payments_Express_Checkout_Button_Display_Handler {
|
||||
|
||||
/**
|
||||
* WC_Payment_Gateway_WCPay instance.
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* Instance of WC_Payments_WooPay_Button_Handler, created in init function
|
||||
*
|
||||
* @var WC_Payments_WooPay_Button_Handler
|
||||
*/
|
||||
private $platform_checkout_button_handler;
|
||||
|
||||
/**
|
||||
* Instance of WC_Payments_Express_Checkout_Button_Handler, created in init function
|
||||
*
|
||||
* @var WC_Payments_Express_Checkout_Button_Handler
|
||||
*/
|
||||
private $express_checkout_button_handler;
|
||||
|
||||
/**
|
||||
* Express Checkout Helper instance.
|
||||
*
|
||||
* @var WC_Payments_Express_Checkout_Ajax_Handler
|
||||
*/
|
||||
private $express_checkout_ajax_handler;
|
||||
|
||||
/**
|
||||
* Express Checkout Helper instance.
|
||||
*
|
||||
* @var WC_Payments_Express_Checkout_Button_Helper
|
||||
*/
|
||||
private $express_checkout_helper;
|
||||
|
||||
/**
|
||||
* Initialize class actions.
|
||||
*
|
||||
* @param WC_Payment_Gateway_WCPay $gateway WCPay gateway.
|
||||
* @param WC_Payments_WooPay_Button_Handler $platform_checkout_button_handler Platform checkout button handler.
|
||||
* @param WC_Payments_Express_Checkout_Button_Handler $express_checkout_button_handler Express Checkout Element button handler.
|
||||
* @param WC_Payments_Express_Checkout_Ajax_Handler $express_checkout_ajax_handler Express checkout ajax handlers.
|
||||
* @param WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper Express checkout button helper.
|
||||
*/
|
||||
public function __construct(
|
||||
WC_Payment_Gateway_WCPay $gateway,
|
||||
WC_Payments_WooPay_Button_Handler $platform_checkout_button_handler,
|
||||
WC_Payments_Express_Checkout_Button_Handler $express_checkout_button_handler,
|
||||
WC_Payments_Express_Checkout_Ajax_Handler $express_checkout_ajax_handler,
|
||||
WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper
|
||||
) {
|
||||
$this->gateway = $gateway;
|
||||
$this->platform_checkout_button_handler = $platform_checkout_button_handler;
|
||||
$this->express_checkout_button_handler = $express_checkout_button_handler;
|
||||
$this->express_checkout_ajax_handler = $express_checkout_ajax_handler;
|
||||
$this->express_checkout_helper = $express_checkout_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this class, its dependencies, and its hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
$this->platform_checkout_button_handler->init();
|
||||
$this->express_checkout_button_handler->init();
|
||||
|
||||
$is_woopay_enabled = WC_Payments_Features::is_woopay_enabled();
|
||||
$is_payment_request_enabled = 'yes' === $this->gateway->get_option( 'payment_request' );
|
||||
|
||||
if ( $is_woopay_enabled || $is_payment_request_enabled ) {
|
||||
add_action( 'wc_ajax_wcpay_add_to_cart', [ $this->express_checkout_ajax_handler, 'ajax_add_to_cart' ] );
|
||||
|
||||
add_action( 'woocommerce_after_add_to_cart_form', [ $this, 'display_express_checkout_buttons' ], 1 );
|
||||
add_action( 'woocommerce_proceed_to_checkout', [ $this, 'display_express_checkout_buttons' ], 21 );
|
||||
add_action( 'woocommerce_checkout_before_customer_details', [ $this, 'display_express_checkout_buttons' ], 1 );
|
||||
add_action( 'woocommerce_pay_order_before_payment', [ $this, 'display_express_checkout_buttons' ], 1 );
|
||||
}
|
||||
|
||||
add_filter( 'wcpay_tracks_event_properties', [ $this, 'record_all_ece_tracks_events' ], 10, 2 );
|
||||
|
||||
if ( $this->is_pay_for_order_flow_supported() ) {
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'add_pay_for_order_params_to_js_config' ], 5 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display express checkout separator only when express buttons are displayed.
|
||||
*
|
||||
* @param bool $separator_starts_hidden Whether the separator should start hidden.
|
||||
* @return void
|
||||
*/
|
||||
public function display_express_checkout_separator_if_necessary( $separator_starts_hidden = false ) {
|
||||
if ( $this->express_checkout_helper->is_checkout() ) {
|
||||
?>
|
||||
<p id="wcpay-express-checkout-button-separator" style="margin-top:1.5em;text-align:center;<?php echo $separator_starts_hidden ? 'display:none;' : ''; ?>">— <?php esc_html_e( 'OR', 'woocommerce-payments' ); ?> —</p>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display express checkout separator only when express buttons are displayed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display_express_checkout_buttons() {
|
||||
$should_show_woopay = $this->platform_checkout_button_handler->should_show_woopay_button();
|
||||
$should_show_express_checkout_button = $this->express_checkout_helper->should_show_express_checkout_button();
|
||||
|
||||
// When Express Checkout button is enabled, we need the separator markup on the page, but hidden in case the browser doesn't have any express payment methods to display.
|
||||
// More details: https://github.com/Automattic/woocommerce-payments/pull/5399#discussion_r1073633776.
|
||||
$separator_starts_hidden = ! $should_show_woopay;
|
||||
if ( $should_show_woopay || $should_show_express_checkout_button ) {
|
||||
?>
|
||||
<div class='wcpay-express-checkout-wrapper' >
|
||||
<?php
|
||||
if ( ! $this->express_checkout_helper->is_pay_for_order_page() || $this->is_pay_for_order_flow_supported() ) {
|
||||
$this->platform_checkout_button_handler->display_woopay_button_html();
|
||||
}
|
||||
|
||||
$this->express_checkout_button_handler->display_express_checkout_button_html();
|
||||
|
||||
if ( is_cart() ) {
|
||||
add_action( 'woocommerce_after_cart', [ $this, 'add_order_attribution_inputs' ], 1 );
|
||||
} else {
|
||||
$this->add_order_attribution_inputs();
|
||||
}
|
||||
|
||||
?>
|
||||
</div >
|
||||
<?php
|
||||
$this->display_express_checkout_separator_if_necessary( $separator_starts_hidden );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add order attribution inputs to the page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_order_attribution_inputs() {
|
||||
echo '<wc-order-attribution-inputs id="wcpay-express-checkout__order-attribution-inputs"></wc-order-attribution-inputs>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the pay-for-order flow is supported.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_pay_for_order_flow_supported() {
|
||||
return ( class_exists( '\Automattic\WooCommerce\Blocks\Package' ) && version_compare( \Automattic\WooCommerce\Blocks\Package::get_version(), '11.1.0', '>=' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooPay is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_woopay_enabled() {
|
||||
return $this->platform_checkout_button_handler->is_woopay_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Pay for order params to the JS config.
|
||||
*/
|
||||
public function add_pay_for_order_params_to_js_config() {
|
||||
global $wp;
|
||||
$order_id = $wp->query_vars['order-pay'] ?? null;
|
||||
|
||||
if ( ! $order_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification
|
||||
if ( isset( $_GET['pay_for_order'] ) && isset( $_GET['key'] ) && current_user_can( 'pay_for_order', $order_id ) ) {
|
||||
add_filter(
|
||||
'wcpay_payment_fields_js_config',
|
||||
function ( $js_config ) use ( $order ) {
|
||||
$session = wc()->session;
|
||||
$session_email = '';
|
||||
|
||||
if ( is_a( $session, WC_Session::class ) ) {
|
||||
$customer = $session->get( 'customer' );
|
||||
$session_email = is_array( $customer ) && isset( $customer['email'] ) ? $customer['email'] : '';
|
||||
}
|
||||
|
||||
$user_email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( filter_input( INPUT_POST, 'email', FILTER_SANITIZE_EMAIL ) ) ) : $session_email;
|
||||
|
||||
$js_config['order_id'] = $order->get_id();
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
$js_config['pay_for_order'] = sanitize_text_field( wp_unslash( $_GET['pay_for_order'] ) );
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
$js_config['key'] = sanitize_text_field( wp_unslash( $_GET['key'] ) );
|
||||
$js_config['billing_email'] = current_user_can( 'read_private_shop_orders' ) ||
|
||||
( get_current_user_id() !== 0 && $order->get_customer_id() === get_current_user_id() )
|
||||
? $order->get_billing_email()
|
||||
: $user_email;
|
||||
|
||||
return $js_config;
|
||||
}
|
||||
);
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification
|
||||
}
|
||||
|
||||
/**
|
||||
* Record all ECE tracks events by adding the track_on_all_stores flag to the event.
|
||||
*
|
||||
* @param array $properties Event properties.
|
||||
* @param string $event_name Event name.
|
||||
* @return array
|
||||
*/
|
||||
public function record_all_ece_tracks_events( $properties, $event_name ) {
|
||||
$tracked_events_prefixes = [
|
||||
'wcpay_applepay',
|
||||
'wcpay_gpay',
|
||||
];
|
||||
|
||||
foreach ( $tracked_events_prefixes as $prefix ) {
|
||||
if ( strpos( $event_name, $prefix ) === 0 ) {
|
||||
$properties['record_event_data']['track_on_all_stores'] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
}
|
||||
+499
@@ -0,0 +1,499 @@
|
||||
<?php
|
||||
/**
|
||||
* Class WC_Payments_Express_Checkout_Button_Handler
|
||||
* Adds support for Apple Pay, Google Pay and ECE API buttons.
|
||||
* Utilizes the Stripe Express Checkout Element to support checkout from the product detail and cart pages.
|
||||
*
|
||||
* @package WooCommerce\Payments
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use WCPay\Fraud_Prevention\Fraud_Prevention_Service;
|
||||
|
||||
/**
|
||||
* WC_Payments_Express_Checkout_Button_Handler class.
|
||||
*/
|
||||
class WC_Payments_Express_Checkout_Button_Handler {
|
||||
const BUTTON_LOCATIONS = 'payment_request_button_locations';
|
||||
const DEFAULT_BORDER_RADIUS_IN_PX = 4;
|
||||
|
||||
/**
|
||||
* WC_Payments_Account instance to get information about the account
|
||||
*
|
||||
* @var WC_Payments_Account
|
||||
*/
|
||||
private $account;
|
||||
|
||||
/**
|
||||
* WC_Payment_Gateway_WCPay instance.
|
||||
*
|
||||
* @var WC_Payment_Gateway_WCPay
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* Express Checkout Ajax Handle instance.
|
||||
*
|
||||
* @var WC_Payments_Express_Checkout_Button_Helper
|
||||
*/
|
||||
private $express_checkout_helper;
|
||||
|
||||
/**
|
||||
* Express Checkout Helper instance.
|
||||
*
|
||||
* @var WC_Payments_Express_Checkout_Ajax_Handler
|
||||
*/
|
||||
private $express_checkout_ajax_handler;
|
||||
|
||||
/**
|
||||
* Initialize class actions.
|
||||
*
|
||||
* @param WC_Payments_Account $account Account information.
|
||||
* @param WC_Payment_Gateway_WCPay $gateway WCPay gateway.
|
||||
* @param WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper Express checkout helper.
|
||||
* @param WC_Payments_Express_Checkout_Ajax_Handler $express_checkout_ajax_handler Express checkout ajax handler.
|
||||
*/
|
||||
public function __construct( WC_Payments_Account $account, WC_Payment_Gateway_WCPay $gateway, WC_Payments_Express_Checkout_Button_Helper $express_checkout_helper, WC_Payments_Express_Checkout_Ajax_Handler $express_checkout_ajax_handler ) {
|
||||
$this->account = $account;
|
||||
$this->gateway = $gateway;
|
||||
$this->express_checkout_helper = $express_checkout_helper;
|
||||
$this->express_checkout_ajax_handler = $express_checkout_ajax_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
// Checks if WCPay is enabled.
|
||||
if ( ! $this->gateway->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if Payment Request is enabled.
|
||||
if ( 'yes' !== $this->gateway->get_option( 'payment_request' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't load for change payment method page.
|
||||
if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'template_redirect', [ $this, 'set_session' ] );
|
||||
add_action( 'template_redirect', [ $this, 'handle_express_checkout_redirect' ] );
|
||||
add_filter( 'woocommerce_login_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 );
|
||||
add_filter( 'woocommerce_registration_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 );
|
||||
add_filter( 'woocommerce_cart_needs_shipping_address', [ $this, 'filter_cart_needs_shipping_address' ], 11, 1 );
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'scripts' ] );
|
||||
add_action( 'before_woocommerce_pay_form', [ $this, 'display_pay_for_order_page_html' ], 1 );
|
||||
add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 );
|
||||
add_action( 'woocommerce_checkout_order_processed', [ $this->express_checkout_helper, 'add_order_payment_method_title' ], 10, 2 );
|
||||
|
||||
$this->express_checkout_ajax_handler->init();
|
||||
|
||||
if ( is_admin() && current_user_can( 'manage_woocommerce' ) ) {
|
||||
$this->register_ece_data_for_block_editor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The settings for the `button` attribute - they depend on the "grouped settings" flag value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_button_settings() {
|
||||
$button_type = $this->gateway->get_option( 'payment_request_button_type' );
|
||||
$common_settings = $this->express_checkout_helper->get_common_button_settings();
|
||||
$express_checkout_button_settings = [
|
||||
// Default format is en_US.
|
||||
'locale' => apply_filters( 'wcpay_payment_request_button_locale', substr( get_locale(), 0, 2 ) ),
|
||||
'branded_type' => 'default' === $button_type ? 'short' : 'long',
|
||||
];
|
||||
|
||||
return array_merge( $common_settings, $express_checkout_button_settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings array for the user authentication dialog and redirection.
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function get_login_confirmation_settings() {
|
||||
if ( is_user_logged_in() || ! $this->is_authentication_required() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* translators: The text encapsulated in `**` can be replaced with "Apple Pay" or "Google Pay". Please translate this text, but don't remove the `**`. */
|
||||
$message = __( 'To complete your transaction with **the selected payment method**, you must log in or create an account with our site.', 'woocommerce-payments' );
|
||||
$redirect_url = add_query_arg(
|
||||
[
|
||||
'_wpnonce' => wp_create_nonce( 'wcpay-set-redirect-url' ),
|
||||
'wcpay_express_checkout_redirect_url' => rawurlencode( home_url( add_query_arg( [] ) ) ),
|
||||
// Current URL to redirect to after login.
|
||||
],
|
||||
home_url()
|
||||
);
|
||||
|
||||
return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- home_url passed in to add_query_arg.
|
||||
'message' => $message,
|
||||
'redirect_url' => $redirect_url,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether authentication is required for checkout.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_authentication_required() {
|
||||
// If guest checkout is disabled and account creation is not possible, authentication is required.
|
||||
if ( 'no' === get_option( 'woocommerce_enable_guest_checkout', 'yes' ) && ! $this->is_account_creation_possible() ) {
|
||||
return true;
|
||||
}
|
||||
// If cart contains subscription and account creation is not posible, authentication is required.
|
||||
if ( $this->has_subscription_product() && ! $this->is_account_creation_possible() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether cart contains a subscription product or this is a subscription product page.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has_subscription_product() {
|
||||
if ( ! class_exists( 'WC_Subscriptions_Product' ) || ! class_exists( 'WC_Subscriptions_Cart' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $this->express_checkout_helper->is_product() ) {
|
||||
$product = $this->express_checkout_helper->get_product();
|
||||
if ( WC_Subscriptions_Product::is_subscription( $product ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) {
|
||||
if ( WC_Subscriptions_Cart::cart_contains_subscription() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether account creation is possible during checkout.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_account_creation_possible() {
|
||||
$is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'no' );
|
||||
|
||||
// If a subscription is being purchased, check if account creation is allowed for subscriptions.
|
||||
if ( ! $is_signup_from_checkout_allowed && $this->has_subscription_product() ) {
|
||||
$is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_from_checkout_for_subscriptions', 'no' );
|
||||
}
|
||||
|
||||
// If automatically generate username/password are disabled, the Express Checkout API
|
||||
// can't include any of those fields, so account creation is not possible.
|
||||
return (
|
||||
$is_signup_from_checkout_allowed &&
|
||||
'yes' === get_option( 'woocommerce_registration_generate_username', 'yes' ) &&
|
||||
'yes' === get_option( 'woocommerce_registration_generate_password', 'yes' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load public scripts and styles.
|
||||
*/
|
||||
public function scripts() {
|
||||
// Don't load scripts if page is not supported.
|
||||
if ( ! $this->express_checkout_helper->should_show_express_checkout_button() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$express_checkout_params = [
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'wc_ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
|
||||
'stripe' => [
|
||||
'publishableKey' => $this->account->get_publishable_key( WC_Payments::mode()->is_test() ),
|
||||
'accountId' => $this->account->get_stripe_account_id(),
|
||||
'locale' => WC_Payments_Utils::convert_to_stripe_locale( get_locale() ),
|
||||
],
|
||||
'nonce' => [
|
||||
'get_cart_details' => wp_create_nonce( 'wcpay-get-cart-details' ),
|
||||
'shipping' => wp_create_nonce( 'wcpay-payment-request-shipping' ),
|
||||
'update_shipping' => wp_create_nonce( 'wcpay-update-shipping-method' ),
|
||||
'checkout' => wp_create_nonce( 'woocommerce-process_checkout' ),
|
||||
'add_to_cart' => wp_create_nonce( 'wcpay-add-to-cart' ),
|
||||
'empty_cart' => wp_create_nonce( 'wcpay-empty-cart' ),
|
||||
'get_selected_product_data' => wp_create_nonce( 'wcpay-get-selected-product-data' ),
|
||||
'platform_tracker' => wp_create_nonce( 'platform_tracks_nonce' ),
|
||||
'pay_for_order' => wp_create_nonce( 'pay_for_order' ),
|
||||
// needed to communicate via the Store API.
|
||||
'tokenized_cart_nonce' => wp_create_nonce( 'woopayments_tokenized_cart_nonce' ),
|
||||
'tokenized_cart_session_nonce' => wp_create_nonce( 'woopayments_tokenized_cart_session_nonce' ),
|
||||
'store_api_nonce' => wp_create_nonce( 'wc_store_api' ),
|
||||
],
|
||||
'checkout' => [
|
||||
'currency_code' => strtolower( get_woocommerce_currency() ),
|
||||
'currency_decimals' => WC_Payments::get_localization_service()->get_currency_format( get_woocommerce_currency() )['num_decimals'],
|
||||
'country_code' => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
|
||||
'needs_shipping' => WC()->cart->needs_shipping(),
|
||||
// Defaults to 'required' to match how core initializes this option.
|
||||
'needs_payer_phone' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ),
|
||||
'allowed_shipping_countries' => array_keys( WC()->countries->get_shipping_countries() ?? [] ),
|
||||
],
|
||||
'button' => $this->get_button_settings(),
|
||||
'login_confirmation' => $this->get_login_confirmation_settings(),
|
||||
'button_context' => $this->express_checkout_helper->get_button_context(),
|
||||
'has_block' => has_block( 'woocommerce/cart' ) || has_block( 'woocommerce/checkout' ),
|
||||
'product' => $this->express_checkout_helper->get_product_data(),
|
||||
'total_label' => $this->express_checkout_helper->get_total_label(),
|
||||
];
|
||||
|
||||
if ( WC_Payments_Features::is_tokenized_cart_ece_enabled() ) {
|
||||
WC_Payments::register_script_with_dependencies(
|
||||
'WCPAY_EXPRESS_CHECKOUT_ECE',
|
||||
'dist/tokenized-express-checkout',
|
||||
[
|
||||
'jquery',
|
||||
'stripe',
|
||||
]
|
||||
);
|
||||
|
||||
WC_Payments_Utils::enqueue_style(
|
||||
'WCPAY_EXPRESS_CHECKOUT_ECE',
|
||||
plugins_url( 'dist/tokenized-express-checkout.css', WCPAY_PLUGIN_FILE ),
|
||||
[],
|
||||
WC_Payments::get_file_version( 'dist/tokenized-express-checkout.css' )
|
||||
);
|
||||
} else {
|
||||
WC_Payments::register_script_with_dependencies(
|
||||
'WCPAY_EXPRESS_CHECKOUT_ECE',
|
||||
'dist/express-checkout',
|
||||
[
|
||||
'jquery',
|
||||
'stripe',
|
||||
]
|
||||
);
|
||||
|
||||
WC_Payments_Utils::enqueue_style(
|
||||
'WCPAY_EXPRESS_CHECKOUT_ECE',
|
||||
plugins_url( 'dist/express-checkout.css', WCPAY_PLUGIN_FILE ),
|
||||
[],
|
||||
WC_Payments::get_file_version( 'dist/express-checkout.css' )
|
||||
);
|
||||
}
|
||||
|
||||
wp_localize_script( 'WCPAY_EXPRESS_CHECKOUT_ECE', 'wcpayExpressCheckoutParams', $express_checkout_params );
|
||||
|
||||
wp_set_script_translations( 'WCPAY_EXPRESS_CHECKOUT_ECE', 'woocommerce-payments' );
|
||||
|
||||
wp_enqueue_script( 'WCPAY_EXPRESS_CHECKOUT_ECE' );
|
||||
|
||||
Fraud_Prevention_Service::maybe_append_fraud_prevention_token();
|
||||
|
||||
$gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
if ( isset( $gateways['woocommerce_payments'] ) ) {
|
||||
WC_Payments::get_wc_payments_checkout()->register_scripts();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the express checkout button.
|
||||
*/
|
||||
public function display_express_checkout_button_html() {
|
||||
if ( ! $this->express_checkout_helper->should_show_express_checkout_button() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div id="wcpay-express-checkout-element" style="display: none;"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the necessary HTML for the Pay for Order page.
|
||||
*
|
||||
* @param WC_Order $order The order that needs payment.
|
||||
*/
|
||||
public function display_pay_for_order_page_html( $order ) {
|
||||
$currency = get_woocommerce_currency();
|
||||
|
||||
$data = [];
|
||||
$items = [];
|
||||
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
if ( method_exists( $item, 'get_total' ) ) {
|
||||
$items[] = [
|
||||
'label' => $item->get_name(),
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $item->get_total(), $currency ),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $order->get_total_tax() ) {
|
||||
$items[] = [
|
||||
'label' => __( 'Tax', 'woocommerce-payments' ),
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $order->get_total_tax(), $currency ),
|
||||
];
|
||||
}
|
||||
|
||||
if ( $order->get_shipping_total() ) {
|
||||
$shipping_label = sprintf(
|
||||
// Translators: %s is the name of the shipping method.
|
||||
__( 'Shipping (%s)', 'woocommerce-payments' ),
|
||||
$order->get_shipping_method()
|
||||
);
|
||||
|
||||
$items[] = [
|
||||
'label' => $shipping_label,
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $order->get_shipping_total(), $currency ),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ( $order->get_fees() as $fee ) {
|
||||
$items[] = [
|
||||
'label' => $fee->get_name(),
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $fee->get_amount(), $currency ),
|
||||
];
|
||||
}
|
||||
|
||||
$data['order'] = $order->get_id();
|
||||
$data['displayItems'] = $items;
|
||||
$data['needs_shipping'] = false; // This should be already entered/prepared.
|
||||
$data['total'] = [
|
||||
'label' => apply_filters( 'wcpay_payment_request_total_label', $this->express_checkout_helper->get_total_label() ),
|
||||
'amount' => WC_Payments_Utils::prepare_amount( $order->get_total(), $currency ),
|
||||
'pending' => true,
|
||||
];
|
||||
|
||||
wp_localize_script( 'WCPAY_EXPRESS_CHECKOUT_ECE', 'wcpayECEPayForOrderParams', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the WC customer session if one is not set.
|
||||
* This is needed so nonces can be verified by AJAX Request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_session() {
|
||||
// Don't set session cookies on product pages to allow for caching when express checkout
|
||||
// buttons are disabled. But keep cookies if there is already an active WC session in place.
|
||||
if (
|
||||
! ( $this->express_checkout_helper->is_product() && $this->express_checkout_helper->should_show_express_checkout_button() )
|
||||
|| ( isset( WC()->session ) && WC()->session->has_session() )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
WC()->session->set_customer_session_cookie( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles express checkout redirect when the redirect dialog "Continue" button is clicked.
|
||||
*/
|
||||
public function handle_express_checkout_redirect() {
|
||||
if (
|
||||
! empty( $_GET['wcpay_express_checkout_redirect_url'] )
|
||||
&& ! empty( $_GET['_wpnonce'] )
|
||||
&& wp_verify_nonce( $_GET['_wpnonce'], 'wcpay-set-redirect-url' ) // @codingStandardsIgnoreLine
|
||||
) {
|
||||
$url = rawurldecode( esc_url_raw( wp_unslash( $_GET['wcpay_express_checkout_redirect_url'] ) ) );
|
||||
// Sets a redirect URL cookie for 10 minutes, which we will redirect to after authentication.
|
||||
// Users will have a 10 minute timeout to login/create account, otherwise redirect URL expires.
|
||||
wc_setcookie( 'wcpay_express_checkout_redirect_url', $url, time() + MINUTE_IN_SECONDS * 10 );
|
||||
// Redirects to "my-account" page.
|
||||
wp_safe_redirect( get_permalink( get_option( 'woocommerce_myaccount_page_id' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the login redirect URL.
|
||||
*
|
||||
* @param string $redirect Default redirect URL.
|
||||
*
|
||||
* @return string Redirect URL.
|
||||
*/
|
||||
public function get_login_redirect_url( $redirect ) {
|
||||
$url = esc_url_raw( wp_unslash( $_COOKIE['wcpay_express_checkout_redirect_url'] ?? '' ) );
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
return $redirect;
|
||||
}
|
||||
wc_setcookie( 'wcpay_express_checkout_redirect_url', '' );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether to filter the cart needs shipping address.
|
||||
*
|
||||
* @param boolean $needs_shipping_address Whether the cart needs a shipping address.
|
||||
*/
|
||||
public function filter_cart_needs_shipping_address( $needs_shipping_address ) {
|
||||
if ( $this->has_subscription_product() && wc_get_shipping_method_count( true, true ) === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $needs_shipping_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the gateway title to reflect the button type used.
|
||||
*
|
||||
* @param string $title Gateway title.
|
||||
* @param string $id Gateway ID.
|
||||
*/
|
||||
public function filter_gateway_title( $title, $id ) {
|
||||
if ( 'woocommerce_payments' !== $id || ! is_admin() ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
$order = $this->express_checkout_helper->get_current_order();
|
||||
$method_title = is_object( $order ) ? $order->get_payment_method_title() : '';
|
||||
|
||||
if ( ! empty( $method_title ) ) {
|
||||
if (
|
||||
strpos( $method_title, 'Apple Pay' ) === 0
|
||||
|| strpos( $method_title, 'Google Pay' ) === 0
|
||||
|| strpos( $method_title, 'Payment Request' ) === 0 // Legacy PRB title.
|
||||
) {
|
||||
return $method_title;
|
||||
}
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ECE data to `wcSettings` to allow it to be accessed by the front-end JS script in the Block editor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_ece_data_for_block_editor() {
|
||||
$data_registry = Package::container()->get( AssetDataRegistry::class );
|
||||
|
||||
if ( $data_registry->exists( 'ece_data' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data_registry->add(
|
||||
'ece_data',
|
||||
[
|
||||
'button' => $this->get_button_settings(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+1253
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user