Skip to content

Commit

Permalink
Create backend classes for express checkout element (#3429)
Browse files Browse the repository at this point in the history
* create 'WC_Stripe_Express_Checkout_Element' class

* register script for shortcode checkout

* move ajax functions to separate class

* move helper functions to a separate class

* include and initialize express checkout classes

* make functions public in the helper class
  • Loading branch information
Mayisha authored Sep 16, 2024
1 parent 97112f2 commit 62827e1
Show file tree
Hide file tree
Showing 8 changed files with 1,878 additions and 8 deletions.
6 changes: 3 additions & 3 deletions client/blocks/express-checkout/express-checkout.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global wc_stripe_payment_request_params */
/* global wc_stripe_express_checkout_params */

import React from 'react';
import { Elements, ExpressCheckoutElement } from '@stripe/react-stripe-js';
Expand All @@ -13,8 +13,8 @@ export const ExpressCheckout = ( props ) => {

const buttonOptions = {
buttonType: {
googlePay: wc_stripe_payment_request_params.button.type,
applePay: wc_stripe_payment_request_params.button.type,
googlePay: wc_stripe_express_checkout_params.button.type,
applePay: wc_stripe_express_checkout_params.button.type,
},
};

Expand Down
1 change: 1 addition & 0 deletions client/entrypoints/express-checkout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// express checkout element integration for shortcode goes here.
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* WC_Stripe_Express_Checkout_Ajax_Handler class.
*/
class WC_Stripe_Express_Checkout_Ajax_Handler {
/**
* WC_Stripe_Express_Checkout_Helper instance.
*
* @var WC_Stripe_Express_Checkout_Helper
*/
private $express_checkout_helper;

/**
* Constructor.
*
* @param WC_Stripe_Express_Checkout_Helper $express_checkout_helper Express checkout helper.
*/
public function __construct( WC_Stripe_Express_Checkout_Helper $express_checkout_helper ) {
$this->express_checkout_helper = $express_checkout_helper;
}

/**
* Initialize hooks.
*
* @return void
*/
public function init() {
add_action( 'wc_ajax_wc_stripe_get_cart_details', [ $this, 'ajax_get_cart_details' ] );
add_action( 'wc_ajax_wc_stripe_get_shipping_options', [ $this, 'ajax_get_shipping_options' ] );
add_action( 'wc_ajax_wc_stripe_update_shipping_method', [ $this, 'ajax_update_shipping_method' ] );
add_action( 'wc_ajax_wc_stripe_create_order', [ $this, 'ajax_create_order' ] );
add_action( 'wc_ajax_wc_stripe_add_to_cart', [ $this, 'ajax_add_to_cart' ] );
add_action( 'wc_ajax_wc_stripe_get_selected_product_data', [ $this, 'ajax_get_selected_product_data' ] );
add_action( 'wc_ajax_wc_stripe_clear_cart', [ $this, 'ajax_clear_cart' ] );
add_action( 'wc_ajax_wc_stripe_log_errors', [ $this, 'ajax_log_errors' ] );
}

/**
* Get cart details.
*/
public function ajax_get_cart_details() {
check_ajax_referer( 'wc-stripe-express-checkout', 'security' );

if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
define( 'WOOCOMMERCE_CART', true );
}

WC()->cart->calculate_totals();

$currency = get_woocommerce_currency();

// Set mandatory payment details.
$data = [
'shipping_required' => WC()->cart->needs_shipping(),
'order_data' => [
'currency' => strtolower( $currency ),
'country_code' => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
],
];

$data['order_data'] += $this->express_checkout_helper->build_display_items();

wp_send_json( $data );
}


/**
* Adds the current product to the cart. Used on product detail page.
*
* @return array $data Results of adding the product to the cart.
*/
public function ajax_add_to_cart() {
check_ajax_referer( 'wc-stripe-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'] ) : 0;
$qty = ! isset( $_POST['qty'] ) ? 1 : absint( $_POST['qty'] );
$product = wc_get_product( $product_id );
$product_type = $product->get_type();

// 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(), $qty, $variation_id, $attributes );
}

if ( in_array( $product_type, [ 'simple', 'variation', 'subscription', 'subscription_variation' ], true ) ) {
WC()->cart->add_to_cart( $product->get_id(), $qty );
}

WC()->cart->calculate_totals();

$data = [];
$data += $this->express_checkout_helper->build_display_items();
$data['result'] = 'success';

// @phpstan-ignore-next-line (return statement is added)
wp_send_json( $data );
}

/**
* Clears cart.
*/
public function ajax_clear_cart() {
check_ajax_referer( 'wc-stripe-clear-cart', 'security' );

WC()->cart->empty_cart();
exit;
}

/**
* 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( 'wc-stripe-express-checkout-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' => 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_helper->get_shipping_options( $shipping_address, $should_show_itemized_view );
wp_send_json( $data );
}

/**
* Update shipping method.
*/
public function ajax_update_shipping_method() {
check_ajax_referer( 'wc-stripe-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_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 = [];
$data += $this->express_checkout_helper->build_display_items( $should_show_itemized_view );
$data['result'] = 'success';

wp_send_json( $data );
}

/**
* Gets the selected product data.
*
* @return array $data The selected product data.
*/
public function ajax_get_selected_product_data() {
check_ajax_referer( 'wc-stripe-get-selected-product-data', 'security' );

try { // @phpstan-ignore-line (return statement is added)
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
$qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id );
$addon_value = isset( $_POST['addon_value'] ) ? max( floatval( $_POST['addon_value'] ), 0 ) : 0;
$product = wc_get_product( $product_id );
$variation_id = null;

if ( ! is_a( $product, 'WC_Product' ) ) {
/* translators: 1) The product Id */
throw new Exception( sprintf( __( 'Product with the ID (%1$s) cannot be found.', 'woocommerce-gateway-stripe' ), $product_id ) );
}

if ( in_array( $product->get_type(), [ 'variable', 'variable-subscription' ], true ) && 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 );
}
}

if ( $this->express_checkout_helper->is_invalid_subscription_product( $product, true ) ) {
throw new Exception( __( 'The chosen subscription product is not supported.', 'woocommerce-gateway-stripe' ) );
}

// Force quantity to 1 if sold individually and check for existing item in cart.
if ( $product->is_sold_individually() ) {
$qty = apply_filters( 'wc_stripe_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-gateway-stripe' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) );
}

$total = $qty * $this->express_checkout_helper->get_product_price( $product ) + $addon_value;

$quantity_label = 1 < $qty ? ' (x' . $qty . ')' : '';

$data = [];
$items = [];

$items[] = [
'label' => $product->get_name() . $quantity_label,
'amount' => WC_Stripe_Helper::get_stripe_amount( $total ),
];

if ( wc_tax_enabled() ) {
$items[] = [
'label' => __( 'Tax', 'woocommerce-gateway-stripe' ),
'amount' => 0,
'pending' => true,
];
}

if ( wc_shipping_enabled() && $product->needs_shipping() ) {
$items[] = [
'label' => __( 'Shipping', 'woocommerce-gateway-stripe' ),
'amount' => 0,
'pending' => true,
];

$data['shippingOptions'] = [
'id' => 'pending',
'label' => __( 'Pending', 'woocommerce-gateway-stripe' ),
'detail' => '',
'amount' => 0,
];
}

$data['displayItems'] = $items;
$data['total'] = [
'label' => $this->express_checkout_helper->get_total_label(),
'amount' => WC_Stripe_Helper::get_stripe_amount( $total ),
];

$data['requestShipping'] = ( wc_shipping_enabled() && $product->needs_shipping() );
$data['currency'] = strtolower( get_woocommerce_currency() );
$data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 );

wp_send_json( $data );
} catch ( Exception $e ) {
wp_send_json( [ 'error' => wp_strip_all_tags( $e->getMessage() ) ] );
}
}

/**
* Create order. Security is handled by WC.
*/
public function ajax_create_order() {
if ( WC()->cart->is_empty() ) {
wp_send_json_error( __( 'Empty cart', 'woocommerce-gateway-stripe' ) );
}

if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
define( 'WOOCOMMERCE_CHECKOUT', true );
}

// Normalizes billing and shipping state values.
$this->express_checkout_helper->normalize_state();

// In case the state is required, but is missing, add a more descriptive error notice.
$this->express_checkout_helper->validate_state();

WC()->checkout()->process_checkout();

die( 0 );
}

/**
* Log errors coming from express checkout elements
*/
public function ajax_log_errors() {
check_ajax_referer( 'wc-stripe-log-errors', 'security' );

$errors = isset( $_POST['errors'] ) ? wc_clean( wp_unslash( $_POST['errors'] ) ) : '';

WC_Stripe_Logger::log( $errors );

exit;
}
}
Loading

0 comments on commit 62827e1

Please sign in to comment.