asp_main = AcceptStripePayments::get_instance(); if ( false !== $post_id ) { //let's try to load item from product $this->post_id = $post_id; $this->load_from_product(); $btn_uniq_id = isset( $_GET['btn_uniq_id'] ) ? sanitize_text_field( stripslashes ( $_GET['btn_uniq_id'] ) ) : ''; if ( ! empty( $btn_uniq_id ) ) { ASP_Debug_Logger::log( 'Using the btn_uniq_id parameter. Value of btn_uniq_id: ' . $btn_uniq_id, true ); $this->sess = ASP_Session::get_instance(); $this->overriden_data = $this->sess->get_transient_data( 'overriden_data_' . $btn_uniq_id ); } } } private function in_cents( $amount ) { if ( ! $this->zero_cent ) { $amount = round( $amount * 100, 2 ); } else { $amount = round( $amount, 0 ); } return $amount; } private function from_cents( $amount ) { if ( ! $this->zero_cent ) { $amount = round( $amount / 100, 2 ); } else { $amount = round( $amount, 0 ); } return $amount; } public function get_last_error() { return $this->last_error; } public function get_product_id() { return $this->post_id; } public function get_button_text() { $button_text = get_post_meta( $this->post_id, 'asp_product_button_text', true ); if ( empty( $button_text ) ) { $button_text = $this->asp_main->get_setting( 'button_text' ); } return $button_text; } public function get_button_class() { $button_class = get_post_meta( $this->post_id, 'asp_product_button_class', true ); return $button_class; } public function get_stock_items() { $stock_items = get_post_meta( $this->post_id, 'asp_product_stock_items', true ); return $stock_items; } public function stock_control_enabled() { $stock_control_enabled = get_post_meta( $this->post_id, 'asp_product_enable_stock', true ); return $stock_control_enabled; } public function get_tax() { if ( false !== $this->overriden_data && isset( $this->overriden_data['tax'] ) ) { return $this->overriden_data['tax']; } if ( ! empty( $this->tax ) ) { return $this->tax; } $this->tax = get_post_meta( $this->post_id, 'asp_product_tax', true ); if ( empty( $this->tax ) ) { $this->tax = 0; } return $this->tax; } public function set_tax( $tax ) { $this->tax = $tax; } public function get_shipping( $in_cents = false ) { if ( ! isset( $this->shipping ) ) { $this->shipping = get_post_meta( $this->post_id, 'asp_product_shipping', true ); } if ( empty( $this->shipping ) ) { $this->shipping = 0; } if ( $in_cents ) { return $this->in_cents( $this->shipping ); } return $this->shipping; } public function set_shipping( $shipping, $in_cents = false ) { $this->shipping = $in_cents ? $this->from_cents( $shipping ) : $shipping; } public function get_items_total( $in_cents = false, $with_discount = false ) { $items_total = 0; if ( ! empty( $this->items ) ) { foreach ( $this->items as $item ) { $items_total += $item['price']; } } else { return 0; } if ( $with_discount && $this->coupon && 'perc' === $this->coupon['discount_type'] ) { $items_total = $this->apply_discount_to_amount( $items_total, false ); } return $in_cents ? $this->in_cents( $items_total ) : $items_total; } public function get_items() { return $this->items; } public function add_item( $name, $price ) { $this->items[] = array( 'name' => $name, 'price' => floatval( $price ), ); } public function get_thumb() { $this->thumb = get_post_meta( $this->post_id, 'asp_product_thumbnail', true ); return $this->thumb; } public function get_name() { $this->name = $this->post->post_title; return $this->name; } public function get_description() { $this->description = get_post_meta( $this->post_id, 'asp_product_description', true ); return $this->description; } public function get_quantity() { $this->quantity = get_post_meta( $this->post_id, 'asp_product_quantity', true ); if ( ! is_numeric( $this->quantity ) ) { $this->quantity = absint( $this->quantity ); } if ( empty( $this->quantity ) ) { $this->quantity = 1; } if ( $this->cust_quantity ) { $this->quantity = $this->cust_quantity; } return $this->quantity; } public function get_coupon_discount_amount() { $price = $this->get_price(); $items_total = $this->get_items_total(); $discount_amount = $this->get_discount_amount( $price + $items_total ); return $discount_amount; } public function get_price( $in_cents = false, $price_with_discount = false ) { if ( is_null( $this->price ) ) { $this->price = get_post_meta( $this->post_id, 'asp_product_price', true ); $this->price = empty( $this->price ) ? 0 : round( $this->price * 100 ) / 100; } if ( $price_with_discount && $this->coupon ) { $discount_amount = $this->get_discount_amount( $this->price, $in_cents ); $this->price_with_discount = $this->price - $discount_amount; } if ( $in_cents ) { if ( $price_with_discount && $this->coupon ) { return $this->in_cents( $this->price_with_discount ); } return $this->in_cents( $this->price ); } if ( $price_with_discount && $this->coupon ) { return $this->price_with_discount; } return $this->price; } public function get_surcharge() { $surcharge_amount = get_post_meta( $this->post_id, 'asp_surcharge_amount', true ); return !empty($surcharge_amount) && is_numeric($surcharge_amount) ? $surcharge_amount : 0; } public function get_surcharge_label() { $surcharge_label = get_post_meta( $this->post_id, 'asp_surcharge_label', true ); return !empty($surcharge_label) ? $surcharge_label : __("Surcharge", 'stripe-payments'); } public function get_surcharge_type() { $surcharge_type = get_post_meta( $this->post_id, 'asp_surcharge_type', true ); return empty($surcharge_type) ? 'flat' : $surcharge_type; } /** * Calculate the total surcharge amount based on the surcharge type and amount. * * @param bool $in_cents Whether to return the result in cents. * * @return float|int */ public function calculate_total_surcharge(bool $in_cents = false) { $surcharge_amount = $this->get_surcharge(); $surcharge_type = $this->get_surcharge_type(); if ( $surcharge_type == 'perc' ){ $calculate_from_amount = $this->get_total(); $surcharge_percentage = floatval($surcharge_amount); $amount = ( $calculate_from_amount / 100 ) * $surcharge_percentage; } else { $amount = floatval($surcharge_amount); } return $in_cents ? $this->in_cents( $amount ) : $amount; } /** * Returns min amount for donation product * * @param bool $in_cents Return amount in cents if set to `true` * * @return integer|float */ public function get_min_amount( $in_cents = false ) { $min_amount = get_post_meta( $this->post_id, 'asp_product_min_amount', true ); $min_amount = empty( $min_amount ) ? 0 : $min_amount; return $in_cents ? $this->in_cents( $min_amount ) : $min_amount; } /** * Returns product type * * @return string */ public function get_type() { $type = get_post_meta( $this->post_id, 'asp_product_type', true ); return $type; } public function apply_discount_to_amount( $amount, $in_cents = false ) { if ( $this->coupon ) { if ( 'perc' === $this->coupon['discount_type'] ) { $perc = AcceptStripePayments::is_zero_cents( $this->get_currency() ) ? 0 : 2; $discount_amount = round( $amount * ( $this->coupon['discount'] / 100 ), $perc ); } else { $discount_amount = $this->coupon['discount']; if ( $in_cents && ! AcceptStripePayments::is_zero_cents( $this->get_currency() ) ) { $discount_amount = $discount_amount * 100; } } if ( $in_cents ) { $discount_amount = round( $discount_amount, 0 ); } $amount = $amount - $discount_amount; } return $amount; } public function get_discount_amount( $total, $in_cents = false ) { $discount_amount = 0; if ( $this->coupon ) { if ( 'perc' === $this->coupon['discount_type'] ) { $perc = AcceptStripePayments::is_zero_cents( $this->get_currency() ) ? 0 : 2; $discount_amount = round( $total * ( $this->coupon['discount'] / 100 ), $perc ); } else { $discount_amount = $this->coupon['discount']; if ( ! $this->coupon['per_order'] ) { $discount_amount = $discount_amount * $this->get_quantity(); } if ( $in_cents && ! AcceptStripePayments::is_zero_cents( $this->get_currency() ) ) { $discount_amount = $discount_amount * 100; } } if ( $in_cents ) { $discount_amount = round( $discount_amount, 0 ); } $this->coupon['discountAmount'] = $discount_amount; } return $discount_amount; } /* * Returns the total amount for the product (including tax, shipping, item quantity, and discount if any) * * @param bool $in_cents Return amount in cents if set to `true` */ public function get_total( $in_cents = false ) { $total = $this->get_price( $in_cents ); $items_total = $this->get_items_total( $in_cents ); $total += $items_total; $total = $total * $this->get_quantity(); $total = $total - $this->get_discount_amount( $total, $in_cents ); if ( $this->get_tax() ) { $total = $total + $this->get_tax_amount( $in_cents, true ); } $shipping = $this->get_shipping( $in_cents ); if ( ! empty( $shipping ) ) { $total = $total + $this->get_shipping( $in_cents ); } if ( $total < 0 ) { $total = 0; } return $total; } public function set_price( $price, $in_cents = false ) { //workaround for zero-cents currencies for Sub addon 2.0.10 or less $this->zero_cent = AcceptStripePayments::is_zero_cents( $this->get_currency() ); if ( $this->zero_cent && class_exists( 'ASPSUB_main' ) && isset( $this->plan ) && version_compare( ASPSUB_main::ADDON_VER, '2.0.10' ) <= 0 ) { $price = $price * 100; } //end workaround if ( $in_cents ) { $price = $this->from_cents( $price ); } $this->price = $price; } public function set_quantity( $quantity ) { $this->cust_quantity = $quantity; } public function get_tax_amount( $in_cents = false, $price_with_discount = false ) { $total = $this->get_price( false ); $items_total = $this->get_items_total( false ); $total += $items_total; $total = $total * $this->get_quantity(); $total = $total - $this->get_discount_amount( $total, false ); $this->tax_amount = AcceptStripePayments::get_tax_amount( $total, $this->get_tax(), $this->zero_cent ); if ( $in_cents ) { return $this->in_cents( $this->tax_amount ); } return $this->tax_amount; } public function get_currency() { if ( empty( $this->currency ) ) { $this->currency = get_post_meta( $this->post_id, 'asp_product_currency', true ); $this->currency = empty( $this->currency ) ? $this->asp_main->get_setting( 'currency_code' ) : $this->currency; } $this->zero_cent = AcceptStripePayments::is_zero_cents( $this->currency ); return $this->currency; } public function set_currency( $curr ) { $this->currency = $curr; $this->zero_cent = AcceptStripePayments::is_zero_cents( $curr ); } public function is_variable() { return $this->get_price() === 0; } public function is_currency_variable() { if ( $this->get_type() !== 'donation' ) { return false; } $currency_variable = get_post_meta( $this->post_id, 'asp_product_currency_variable', true ); return $currency_variable; } public function get_redir_url() { $url = get_post_meta( $this->post_id, 'asp_product_thankyou_page', true ); if ( empty( $url ) ) { $url = $this->asp_main->get_setting( 'checkout_url' ); } return $url; } public function collect_billing_addr() { return get_post_meta( $this->post_id, 'asp_product_collect_billing_addr', true ); } public function gen_item_data() { $ret = array(); $item_info = array( 'name' => $this->get_name(), 'amount' => $this->get_price( true ), 'currency' => $this->get_currency(), 'quantity' => $this->get_quantity(), ); $thumb = $this->get_thumb(); if ( ! empty( $thumb ) ) { $item_info['images'] = array( $this->get_thumb() ); } $descr = $this->get_description(); if ( ! empty( $descr ) ) { $item_info['description'] = $this->get_description(); } $ret[] = $item_info; $tax = $this->get_tax(); if ( ! empty( $tax ) ) { $tax_str = apply_filters( 'asp_customize_text_msg', __( 'Tax', 'stripe-payments' ), 'tax_str' ); $tax_info = array( 'name' => sprintf( '%s (%s%%)', $tax_str, $this->get_tax() ), 'amount' => $this->get_tax_amount( true ), 'currency' => $this->get_currency(), 'quantity' => $this->get_quantity(), ); $ret[] = $tax_info; } $ship = $this->get_shipping(); if ( ! empty( $ship ) ) { $ship_str = apply_filters( 'asp_customize_text_msg', __( 'Shipping', 'stripe-payments' ), 'shipping_str' ); $tax_info = array( 'name' => sprintf( '%s', $ship_str ), 'amount' => $this->get_shipping( true ), 'currency' => $this->get_currency(), 'quantity' => $this->get_quantity(), ); $ret[] = $tax_info; } return $ret; } public function get_coupon() { return $this->coupon; } private function load_coupon( $coupon_code ) { //let's find coupon $coupon = get_posts( array( 'meta_key' => 'asp_coupon_code', 'meta_value' => $coupon_code, 'posts_per_page' => 1, 'offset' => 0, 'post_type' => 'asp_coupons', ) ); wp_reset_postdata(); if ( empty( $coupon ) ) { //coupon not found $this->last_error = __( 'Coupon not found.', 'stripe-payments' ); return false; } $coupon = $coupon[0]; //check if coupon is active if ( ! get_post_meta( $coupon->ID, 'asp_coupon_active', true ) ) { $this->last_error = __( 'Coupon is not active.', 'stripe-payments' ); return false; } //check if coupon start date has come $start_date = get_post_meta( $coupon->ID, 'asp_coupon_start_date', true ); if ( empty( $start_date ) || strtotime( $start_date ) > time() ) { $this->last_error = __( 'Coupon is not available yet.', 'stripe-payments' ); return false; } //check if coupon has expired $exp_date = get_post_meta( $coupon->ID, 'asp_coupon_exp_date', true ); if ( ! empty( $exp_date ) && strtotime( $exp_date ) < time() ) { $this->last_error = __( 'Coupon has expired.', 'stripe-payments' ); return false; } //check if redemption limit is reached $red_limit = get_post_meta( $coupon->ID, 'asp_coupon_red_limit', true ); $red_count = get_post_meta( $coupon->ID, 'asp_coupon_red_count', true ); if ( ! empty( $red_limit ) && intval( $red_count ) >= intval( $red_limit ) ) { $this->last_error = __( 'Coupon redemption limit is reached.', 'stripe-payments' ); return false; } $per_order = get_post_meta( $coupon->ID, 'asp_coupon_per_order', true ); $this->coupon = array( 'code' => $coupon_code, 'id' => $coupon->ID, 'discount' => get_post_meta( $coupon->ID, 'asp_coupon_discount', true ), 'discount_type' => get_post_meta( $coupon->ID, 'asp_coupon_discount_type', true ), 'per_order' => empty( $per_order ) ? 0 : 1, ); return true; } public function check_coupon( $coupon_code = false ) { if ( ! $coupon_code ) { $this->last_error = 'No coupon code provided'; return false; } $coupon_code = trim( $coupon_code ); $this->load_coupon( $coupon_code ); if ( ! $this->coupon ) { return false; } //check if coupon is allowed for the product $only_for_allowed_products = get_post_meta( $this->coupon['id'], 'asp_coupon_only_for_allowed_products', true ); if ( $only_for_allowed_products ) { $allowed_products = get_post_meta( $this->coupon['id'], 'asp_coupon_allowed_products', true ); if ( is_array( $allowed_products ) && ! in_array( strval( $this->post_id ), $allowed_products, true ) ) { $this->last_error = __( 'Coupon is not allowed for this product.', 'stripe-payments' ); $this->coupon = false; return false; } } return true; } public function get_download_url() { $post_url = isset( $_POST['asp_item_url'] ) ? sanitize_text_field( stripslashes ( $_POST['asp_item_url'] ) ) : ''; if ( $post_url ) { $item_url = $post_url; } else { $item_url = get_post_meta( $this->post_id, 'asp_product_upload', true ); $item_url = $item_url ? $item_url : ''; if ( ! $item_url ) { return ''; } $item_url = base64_encode( $item_url ); } $item_url = apply_filters( 'asp_item_url_process', $item_url, array( 'button_key' => $this->get_button_key(), 'product_id' => $this->post_id, ) ); $item_url = base64_decode( $item_url ); return $item_url; } public function get_button_key() { if ( ! $this->button_key ) { // ASP_Debug_Logger::log("Get Button Key - Name:". $this->get_name() . ", Price:", $this->get_price(true)); $this->button_key = md5( htmlspecialchars_decode( $this->get_name() ) . $this->get_price( true ) ); } return $this->button_key; } public function is_physical_product() { if(!empty($this->get_meta('asp_is_physical_product'))) { return true; } else if (!empty($this->get_meta('asp_product_shipping'))) { return true; } else if (!empty($this->get_meta('asp_product_shipping_variations'))) { return true; } return false; } public function load_from_product( $post_id = false ) { if ( false === $post_id ) { $post_id = $this->post_id; } if ( false === $post_id ) { $this->last_error = __( 'No product ID provided.', 'stripe-payments' ); return false; } $post_id = intval( $post_id ); $this->post = get_post( $post_id ); if ( ! $this->post || ( get_post_type( $post_id ) !== ASPMain::$products_slug && get_post_type( $post_id ) !== ASPMain::$temp_prod_slug ) ) { // translators: %d is product id $this->last_error = sprintf( __( "Can't find product with ID %d", 'stripe-payments' ), $post_id ); ASP_Debug_Logger::log( "Can't find product with ID: " . $post_id, false ); return false; } $this->zero_cent = AcceptStripePayments::is_zero_cents( $this->get_currency() ); return true; } public function get_meta( $meta, $single = true ) { return get_post_meta( $this->post_id, $meta, $single ); } /** * Calculate regional shipping amount by the provided region. * * @param $shipping_region array * @param $in_cents bool * * @return float|int */ public function calculate_regional_shipping_amount($shipping_region, $in_cents = false) { $regional_shipping_amount = 0; $configured_variations = $this->get_meta('asp_product_shipping_variations'); // Evaluate variable shipping if enabled. if (is_array($configured_variations) && count($configured_variations)) { // Iterate through the available shipping variation option to find if there is amy matching. foreach ($configured_variations as $variation) { $variation['amount'] = (float) $variation['amount']; $location = strtolower($variation['loc']); switch ($variation['type']) { case '0': if (!empty($shipping_region['country']) && $location == strtolower($shipping_region['country']) ) // Matched by country, get shipping amount for this location. $regional_shipping_amount += $variation['amount']; break; case '1': if (!empty($shipping_region['state']) && $location == strtolower($shipping_region['state']) ) // Matched by state, get shipping amount for this location. $regional_shipping_amount += $variation['amount']; break; case '2': if (!empty($shipping_region['city']) && $location == strtolower($shipping_region['city']) ) // Matched by city, get shipping amount for this location. $regional_shipping_amount += $variation['amount']; break; default: break; } } } return $in_cents ? $this->in_cents($regional_shipping_amount) : $regional_shipping_amount; } /** * Validates the total checkout amount for a product against amounts from ajax. * * @param int $amount The price to validate. * @param int $quantity Product quantity. * @param array $custom_inputs Custom input from customer such as applied coupon code, billing/shipping details, price variation etc. * * @return bool TRUE if validation is successful, FALSE otherwise. */ public function validate_total_amount( $amount, $quantity, $custom_inputs) { //ASP_Debug_Logger::log("Amount submitted by customer (in cents): ". $amount, true); $product_price = (float) $this->get_meta('asp_product_price'); $quantity = (int) $quantity; $product_price_in_cents = $this->in_cents($product_price); //ASP_Debug_Logger::log("Raw product price: ". $product_price_in_cents , true); $this->set_price($product_price_in_cents, true); $this->set_quantity( $quantity ); $price_variation = $custom_inputs['price_variation']; if ( isset($price_variation) && !empty( $price_variation ) ) { $variations_groups = $this->get_meta('asp_variations_groups'); $variations_names = $this->get_meta('asp_variations_names'); $variations_prices = $this->get_meta('asp_variations_prices'); $construct_final_price = ! empty( $this->get_meta('asp_product_hide_amount_input') ); $pvars = explode(',', $price_variation ); // Hold data like this: ['-', ....] $variation_price = 0; foreach ($pvars as $pvar_str) { $separator = '_'; // Use the last index of the separator to explode the $pvar_str. This is to avoid collision if $pvar_str contains (from the group name) more than one $separator. $last_separator_index = strrpos($pvar_str, $separator); $option_group = substr($pvar_str, 0, $last_separator_index); // Group name; $applied_option = (int) substr($pvar_str, $last_separator_index + strlen($separator));// Applied option $group_index = array_search($option_group, $variations_groups); $applied_price = (float) $variations_prices[$group_index][$applied_option]; $applied_price = $this->in_cents( $applied_price ); $variation_price += $applied_price; // ASP_Debug_Logger::log("Applied variation price for ". $variations_names[$group_index][$applied_option]. ' : ' .$applied_price, true); } // ASP_Debug_Logger::log("Applied total variation price : ". $variation_price, true); $price_with_applied_variation = 0; if ($construct_final_price) { $price_with_applied_variation = $variation_price; }else{ $price_with_applied_variation = $variation_price + $product_price_in_cents; } $this->set_price($price_with_applied_variation, true); // ASP_Debug_Logger::log("Applied total variation price with base price : ". $price_with_applied_variation, true); } // ASP_Debug_Logger::log("Amount current total (after price variation): ". $this->get_total(true), true); $tax_variations_type = $this->get_meta('asp_product_tax_variations_type'); $tax_variations_arr = $this->get_meta('asp_product_tax_variations'); $collect_billing_addr_enabled = $this->get_meta('asp_product_collect_billing_addr'); $collect_shipping_addr_enabled = $this->get_meta('asp_product_collect_shipping_addr'); // ASP_Debug_Logger::log("Applied tax amount (before tax variation apply): ". $this->get_tax_amount(true), true); // Evaluate variable tax if enabled. if (is_array($tax_variations_arr) && count($tax_variations_arr) && $collect_billing_addr_enabled === '1') { $applied_tax_percentage = 0; $tax_region = ($tax_variations_type === 's' && $collect_shipping_addr_enabled === '1') ? $custom_inputs['shipping_details']['address'] : $custom_inputs['billing_details']['address']; // Iterate through the available tax variation option to find if there is amy matching. foreach ($tax_variations_arr as $tax_variation) { $tax_variation['amount'] = (float) $tax_variation['amount']; switch ($tax_variation['loc']) { case $tax_region['city']: // Matched by city, get tax amount for this location. $applied_tax_percentage += $tax_variation['amount']; break; case $tax_region['state']: // Matched by state, get tax amount for this location. $applied_tax_percentage += $tax_variation['amount']; break; case $tax_region['country']: // Matched by country, get tax amount for this location. $applied_tax_percentage += $tax_variation['amount']; break; } } if ($applied_tax_percentage > 0) { $this->set_tax($applied_tax_percentage); } } //ASP_Debug_Logger::log("Applied tax amount (after tax variation): ". $this->get_tax_amount(true), true); //ASP_Debug_Logger::log("Amount current total (after tax variation): ". $this->get_total(true), true); $base_shipping_amount = get_post_meta( $this->post_id, 'asp_product_shipping', true ); if (empty($base_shipping_amount)){ $base_shipping_amount = 0; } else { $base_shipping_amount = $this->in_cents(floatval($base_shipping_amount)); } $shipping_amount = $base_shipping_amount; $collect_shipping_addr_enabled = $this->get_meta('asp_product_collect_shipping_addr'); if ( $collect_shipping_addr_enabled == '1'){ $regional_shipping_amount = $this->calculate_regional_shipping_amount($custom_inputs['shipping_details']['address'], true); $shipping_amount += $regional_shipping_amount; } $this->set_shipping( $shipping_amount, true ); //ASP_Debug_Logger::log("Amount current total (after base shipping): ". $this->get_total(true), true); $coupon_code = $custom_inputs['coupon_code']; if ( !empty( $coupon_code ) ) { // Get the coupon and evaluate it (if submitted). if ( !$this->load_coupon( $coupon_code ) ) { // Invalid coupon. The 'load_coupon' method will handle appending appropriate error message. return false; }; } // Calculate the expected total amount. $expected_total_amount = $this->get_total(true); // Check if surcharge feature is enabled. if ( !empty($this->get_surcharge()) ){ // Surcharge is enabled. Calculate the surcharge amount then adjust the expected total. $surcharge_amount = round($this->calculate_total_surcharge(true));//This is in cents so round it to 0 decimal place. ASP_Debug_Logger::log("The surcharge feature is enabled. Applied surcharge amount (in cents): ". $surcharge_amount, true); $expected_total_amount += $surcharge_amount; } // Trigger a filter so addons can override it. $expected_total_amount = apply_filters('asp_pre_api_submission_expected_amount', $expected_total_amount, $amount, $this->post_id); // Check if the expected total amount matches the given amount. if ( $amount != $expected_total_amount ){ ASP_Debug_Logger::log("Pre-API Submission validation amount mismatch. Expected amount (in cents): ". $expected_total_amount . ", Submitted amount (in cents): " . $amount, false); // Set the last error message that will be displayed to the user. $mismatch_err_msg = __( "Price validation failed. The submitted amount does not match the product's configured price. ", 'stripe-payments' ); $mismatch_err_msg .= "Expected: " . $this->from_cents($expected_total_amount) . ", Submitted: " . $this->from_cents($amount); $this->last_error = $mismatch_err_msg; return false; } ASP_Debug_Logger::log("Pre-API Submission validation successful. Expected: " . $this->from_cents($expected_total_amount) . ", Submitted: " . $this->from_cents($amount), true); return true; } }