Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/woo-stripe-payment/assets/build/admin-modals.asset.php
+++ b/woo-stripe-payment/assets/build/admin-modals.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => '47be25d79a4662b1cf9b');
+<?php return array('dependencies' => array(), 'version' => '767f15e28656e3aa72c0');
--- a/woo-stripe-payment/assets/build/cart.asset.php
+++ b/woo-stripe-payment/assets/build/cart.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wc-stripe-actions', 'wc-stripe-utils', 'wp-api-fetch'), 'version' => '66085712207a41cc0b80');
+<?php return array('dependencies' => array('wc-stripe-actions', 'wc-stripe-utils', 'wp-api-fetch'), 'version' => 'd945b770f20b9f055af5');
--- a/woo-stripe-payment/assets/build/controllers.asset.php
+++ b/woo-stripe-payment/assets/build/controllers.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wc-stripe-core-vendors', 'jquery', 'wc-stripe-actions', 'wc-stripe-cart', 'wc-stripe-checkout-fields', 'wc-stripe-order', 'wc-stripe-product', 'wc-stripe-sdk', 'wc-stripe-utils', 'wp-api-fetch'), 'version' => '6d96c83421d072f20cfe');
+<?php return array('dependencies' => array('wc-stripe-core-vendors', 'jquery', 'wc-stripe-actions', 'wc-stripe-cart', 'wc-stripe-checkout-fields', 'wc-stripe-order', 'wc-stripe-product', 'wc-stripe-sdk', 'wc-stripe-utils', 'wp-api-fetch'), 'version' => 'd3a3a2623e7e493e9202');
--- a/woo-stripe-payment/assets/build/gateways.asset.php
+++ b/woo-stripe-payment/assets/build/gateways.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wc-stripe-core-vendors', 'jquery', 'wc-stripe-actions', 'wc-stripe-utils'), 'version' => '728007889b6fb22e587d');
+<?php return array('dependencies' => array('wc-stripe-core-vendors', 'jquery', 'wc-stripe-actions', 'wc-stripe-utils'), 'version' => '333c9ac00b10f6082a39');
--- a/woo-stripe-payment/assets/build/order.asset.php
+++ b/woo-stripe-payment/assets/build/order.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wc-stripe-actions', 'wc-stripe-utils', 'wp-api-fetch'), 'version' => '0f8b479b6e3400aa7624');
+<?php return array('dependencies' => array('wc-stripe-actions', 'wc-stripe-utils', 'wp-api-fetch'), 'version' => 'dd1a2138a44c44c0c6f9');
--- a/woo-stripe-payment/assets/build/product.asset.php
+++ b/woo-stripe-payment/assets/build/product.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('jquery', 'wc-stripe-actions', 'wc-stripe-utils'), 'version' => '5f0fb1ba530ac7fff21b');
+<?php return array('dependencies' => array('jquery', 'wc-stripe-actions', 'wc-stripe-utils'), 'version' => '29bd256440ff1204e592');
--- a/woo-stripe-payment/includes/abstract/abstract-wc-payment-token-stripe.php
+++ b/woo-stripe-payment/includes/abstract/abstract-wc-payment-token-stripe.php
@@ -163,6 +163,18 @@
$this->set_prop( 'brand', $value );
}
+ /**
+ * Relative path, from the plugin's assets directory, to this token's icon file.
+ * Subclasses whose brand value doesn't map to a real icon file (e.g. free-text bank
+ * names) should override this to return a static icon instead.
+ *
+ * @return string
+ * @since 4.0.8
+ */
+ public function get_icon_file() {
+ return sprintf( 'img/%s.svg', strtolower( $this->get_brand( 'edit' ) ) );
+ }
+
public function get_html_classes() {
return '';
}
--- a/woo-stripe-payment/includes/abstract/abstract-wc-stripe-payment.php
+++ b/woo-stripe-payment/includes/abstract/abstract-wc-stripe-payment.php
@@ -257,7 +257,11 @@
* @param WC_Order $order
*/
public function add_order_description( &$args, $order ) {
- $args['description'] = sprintf( __( 'Order %1$s from %2$s', 'woo-stripe-payment' ), $order->get_order_number(), get_bloginfo( 'name' ) );
+ $args['description'] = sprintf(
+ __( 'Order %1$s from %2$s', 'woo-stripe-payment' ),
+ $order->get_order_number(),
+ wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES )
+ );
}
/**
--- a/woo-stripe-payment/includes/admin/meta-boxes/views/html-order-pay.php
+++ b/woo-stripe-payment/includes/admin/meta-boxes/views/html-order-pay.php
@@ -58,7 +58,7 @@
class="wc-stripe-card-container show_if_nonce hide_if_token">
<div id="card-element"></div>
<div class="wc-stripe-save-payment-container">
- <input type="checkbox" name="stripe_cc_save_source_key"/>
+ <input type="checkbox" name="wc-stripe_cc-new-payment-method" value="true"/>
<span>
<?php esc_html_e( 'Save payment information for future purchases.', 'woo-stripe-payment' ) ?>
</span>
--- a/woo-stripe-payment/includes/tokens/class-wc-payment-token-stripe-ach.php
+++ b/woo-stripe-payment/includes/tokens/class-wc-payment-token-stripe-ach.php
@@ -100,6 +100,51 @@
return 'wc-stripe-ach';
}
+ /**
+ * Keys are snake_case keywords matched as substrings against a normalized (lowercased,
+ * non-alphanumeric runs collapsed to a single underscore) bank_name, rather than exact
+ * strings, since bank_name is a raw institution name from Stripe/Plaid with no stable
+ * format (e.g. "BANK OF AMERICA, N.A.", "PMORGAN CHASE BANK, NA",
+ * "FIRST NATIONAL BANK OF PENNSYLVANIA").
+ *
+ * @return array<string, string> keyword => relative icon path
+ * @since 4.0.8
+ */
+ public function get_bank_icon_map() {
+ return array(
+ 'bank_of_america' => 'img/ach/boa.png',
+ 'capital_one' => 'img/ach/capitalone.png',
+ 'chase' => 'img/ach/chase.png',
+ 'chime' => 'img/ach/chime.png',
+ 'citibank' => 'img/ach/citibank.png',
+ 'citizens' => 'img/ach/citizens.png',
+ 'navy_federal' => 'img/ach/navyfederal.png',
+ 'pnc' => 'img/ach/pnc.png',
+ 'us_bank' => 'img/ach/usbank.png',
+ 'wells_fargo' => 'img/ach/wellsfargo.png',
+ );
+ }
+
+ /**
+ * The brand for ACH tokens is the bank's raw name (e.g. "JP Morgan Chase"), which never
+ * matches an icon file, so the bank_name is matched against a curated set of known banks
+ * instead, falling back to a generic icon for anything unrecognized.
+ *
+ * @return string
+ * @since 4.0.8
+ */
+ public function get_icon_file() {
+ $bank_name = trim( preg_replace( '/[^a-z0-9]+/', '_', strtolower( (string) $this->get_bank_name( 'edit' ) ) ), '_' );
+
+ foreach ( $this->get_bank_icon_map() as $keyword => $icon_file ) {
+ if ( false !== strpos( $bank_name, $keyword ) ) {
+ return $icon_file;
+ }
+ }
+
+ return 'img/ach/default.svg';
+ }
+
public function get_basic_payment_method_title() {
return __( 'Bank Payment', 'woo-stripe-payment' );
}
--- a/woo-stripe-payment/includes/tokens/class-wc-payment-token-stripe-cc.php
+++ b/woo-stripe-payment/includes/tokens/class-wc-payment-token-stripe-cc.php
@@ -89,6 +89,10 @@
return sprintf( '%s', str_replace( ' ', '', strtolower( $this->get_prop( 'brand' ) ) ) );
}
+ public function get_icon_file() {
+ return sprintf( 'img/cards/%s.svg', strtolower( $this->get_brand( 'edit' ) ) );
+ }
+
public function get_card_type( $context = 'view' ) {
return $this->get_brand( $context );
}
--- a/woo-stripe-payment/includes/wc-stripe-webhook-functions.php
+++ b/woo-stripe-payment/includes/wc-stripe-webhook-functions.php
@@ -106,8 +106,14 @@
* @var PaymentPluginsStripeClientStripeClient $client
*/
$client = wc_stripe_get_container()->get( PaymentPluginsStripeClientStripeClient::class );
- $response = $client->mode( $order )->refunds->all( array( 'charge' => $charge->id ) );
- $refunds = $response->data;
+ $response = $client->mode( $charge )->refunds->all( array( 'charge' => $charge->id ) );
+ if ( is_wp_error( $response ) ) {
+ throw new Exception( sprintf( 'Could not retrieve refunds for charge %s. Error: %s', $charge->id, $response->get_error_message() ) );
+ }
+ $refunds = $response->data;
+ if ( empty( $refunds ) || ! is_array( $refunds ) ) {
+ return;
+ }
usort( $refunds, function ( $a, $b ) {
// sort so refund with most recent created timestamp is first
return $a->created < $b->created ? 1 : - 1;
--- a/woo-stripe-payment/packages/subscriptions/src/Controllers/PaymentIntent.php
+++ b/woo-stripe-payment/packages/subscriptions/src/Controllers/PaymentIntent.php
@@ -74,6 +74,8 @@
$bool = true;
} elseif ( $this->request->cart_contains_subscription() ) {
$bool = true;
+ } elseif ( $this->request->is_product_page_with_subscription() ) {
+ $bool = true;
}
}
--- a/woo-stripe-payment/packages/subscriptions/src/FrontendRequests.php
+++ b/woo-stripe-payment/packages/subscriptions/src/FrontendRequests.php
@@ -3,6 +3,7 @@
namespace PaymentPluginsStripeWooCommerceSubscriptions;
use PaymentPluginsStripeContextHandler;
+use PaymentPluginsStripeUtilitiesProductUtils;
/**
* @package PaymentPluginsWooCommerceSubscriptionsStripe
@@ -60,6 +61,24 @@
return WC()->cart && ( WC_Subscriptions_Cart::cart_contains_subscription() || wcs_cart_contains_renewal() );
}
+ /**
+ * True when viewing a single product page for a subscription product. Needed because
+ * cart_contains_subscription() alone misses the case where the cart is empty (or contains
+ * unrelated items) and the product about to be purchased - e.g. via the product page's own
+ * Buy Now / express checkout buttons - is itself a subscription.
+ *
+ * @return bool
+ * @since 4.0.8
+ */
+ public function is_product_page_with_subscription() {
+ if ( ! $this->context_handler->is_product() ) {
+ return false;
+ }
+ $product = ProductUtils::get_queried_product();
+
+ return $product instanceof WC_Product && WC_Subscriptions_Product::is_subscription( $product );
+ }
+
public function is_order_pay_with_subscription() {
if ( ! $this->context_handler->is_order_pay() ) {
return false;
--- a/woo-stripe-payment/src/Payments/Gateways/AbstractGateway.php
+++ b/woo-stripe-payment/src/Payments/Gateways/AbstractGateway.php
@@ -179,11 +179,8 @@
* @return string
*/
public function get_saved_payment_method_option_html( $token, $checked = true ) {
- $brand = strtolower( $token->get_brand( 'edit' ) );
- $icon_dir = $token instanceof WC_Payment_Token_Stripe_CC ? 'img/cards' : 'img';
- $icon_file = sprintf( '%s/%s.svg', $icon_dir, $brand );
- $icon_url = $this->assets->assets_url( $icon_file );
- $classes = 'woocommerce-SavedPaymentMethods-token wc-stripe-saved-method';
+ $icon_url = $this->assets->assets_url( $token->get_icon_file() );
+ $classes = 'woocommerce-SavedPaymentMethods-token wc-stripe-saved-method';
if ( $checked ) {
$classes .= ' selected';
}
--- a/woo-stripe-payment/src/Rest/Routes/V1/Webhook.php
+++ b/woo-stripe-payment/src/Rest/Routes/V1/Webhook.php
@@ -48,6 +48,11 @@
$webhook_id = stripe_wc()->api_settings->get_option( $webhook_id_key );
$webhook_secret = stripe_wc()->api_settings->get_option( 'webhook_secret_' . $mode );
+ if ( empty( $webhook_secret ) ) {
+ wc_stripe_log_error( sprintf( 'Webhook secret is not configured for %s mode. Rejecting webhook notification.', $mode ) );
+ throw new Exception( __( 'Not authorized.', 'woo-stripe-payment' ), 401 );
+ }
+
// If the webhook ID exists and doesn't match the ID from the notification, skip processing.
// This handles Stripe accounts with multiple webhooks configured.
if ( $webhook_id && isset( $json_payload['data']['object']['metadata']['webhook_id'] ) && $webhook_id !== $json_payload['data']['object']['metadata']['webhook_id'] ) {
--- a/woo-stripe-payment/src/Transformers/DataTransformer.php
+++ b/woo-stripe-payment/src/Transformers/DataTransformer.php
@@ -21,19 +21,21 @@
*/
public function transform_cart( $cart ) {
$currency = get_woocommerce_currency();
+ $packages = $this->get_cart_shipping_packages( $cart );
$data = [
'total' => NumberUtil::round( $cart->get_total( 'float' ), 2 ),
'subtotal' => NumberUtil::round( $cart->get_subtotal(), 2 ),
'totalCents' => wc_stripe_add_number_precision( $cart->get_total( 'float' ), $currency ),
'subtotalCents' => wc_stripe_add_number_precision( $cart->get_subtotal(), $currency ),
+ 'needsPayment' => $cart->needs_payment(),
'needsShipping' => $cart->needs_shipping(),
'isEmpty' => $cart->is_empty(),
'currency' => $currency,
'countryCode' => WC()->countries ? WC()->countries->get_base_country() : wc_get_base_location()['country'],
'lineItems' => $this->get_line_items_from_cart( $cart ),
- 'shippingOptions' => $this->get_shipping_options_from_cart( $cart ),
- 'selectedShippingMethod' => $this->get_selected_shipping_method()
+ 'shippingOptions' => $this->get_shipping_options_from_packages( $cart, $packages ),
+ 'selectedShippingMethod' => $this->get_selected_shipping_method( $packages )
];
return apply_filters( 'wc_stripe_cart_data', $data, $cart );
@@ -119,6 +121,7 @@
'currency' => $order->get_currency(),
'total' => NumberUtil::round( $order->get_total(), 2 ),
'totalCents' => wc_stripe_add_number_precision( $order->get_total(), $order->get_currency() ),
+ 'needsPayment' => $order->needs_payment(),
'lineItems' => $this->get_line_items_from_order( $order ),
'shippingOptions' => [],
'billing_address' => [
@@ -149,19 +152,19 @@
public function transform_customer( $customer ) {
return [
'billing_address' => [
- 'name' => sprintf(
+ 'name' => sprintf(
'%1$s %2$s',
$customer->get_billing_first_name(),
$customer->get_billing_last_name()
),
- 'email' => $customer->get_billing_email(),
- 'phone' => $customer->get_billing_phone(),
- 'address_1' => $customer->get_billing_address_1(),
- 'address_2' => $customer->get_billing_address_2(),
- 'city' => $customer->get_billing_city(),
- 'state' => $customer->get_billing_state(),
- 'postcode' => $customer->get_billing_postcode(),
- 'country' => $customer->get_billing_country(),
+ 'email' => $customer->get_billing_email(),
+ 'phone' => $customer->get_billing_phone(),
+ 'address_1' => $customer->get_billing_address_1(),
+ 'address_2' => $customer->get_billing_address_2(),
+ 'city' => $customer->get_billing_city(),
+ 'state' => $customer->get_billing_state(),
+ 'postcode' => $customer->get_billing_postcode(),
+ 'country' => $customer->get_billing_country(),
],
];
}
@@ -341,23 +344,42 @@
}
/**
- * Get generic shipping options from cart
+ * Get the shipping packages for the cart, so they can be shared between
+ * get_shipping_options_from_packages() and get_selected_shipping_method() - both need to
+ * agree on the same package indices, or the "selected" shipping option id sent to wallets
+ * like Google Pay won't match any of the ids in the offered shippingOptions list.
*
* @param WC_Cart|null $cart
*
* @return array
*/
- private function get_shipping_options_from_cart( $cart ) {
+ private function get_cart_shipping_packages( $cart ) {
if ( ! $cart || ! $cart->needs_shipping() ) {
return [];
}
- $options = [];
$packages = WC()->shipping()->get_packages();
if ( empty( $packages ) ) {
$packages = WC()->shipping()->calculate_shipping( $cart->get_shipping_packages() );
}
- $packages = apply_filters( 'wc_stripe_cart_shipping_packages', $packages );
+
+ return apply_filters( 'wc_stripe_cart_shipping_packages', $packages );
+ }
+
+ /**
+ * Get generic shipping options from cart
+ *
+ * @param WC_Cart|null $cart
+ * @param array $packages
+ *
+ * @return array
+ */
+ private function get_shipping_options_from_packages( $cart, $packages ) {
+ if ( empty( $packages ) ) {
+ return [];
+ }
+
+ $options = [];
$incl_tax = wc_tax_enabled() && $cart->display_prices_including_tax();
$currency = get_woocommerce_currency();
@@ -397,14 +419,20 @@
/**
* Get selected shipping method ID
*
+ * @param array $packages
+ *
* @return string
*/
- private function get_selected_shipping_method() {
- if ( ! WC()->session ) {
+ private function get_selected_shipping_method( $packages ) {
+ if ( ! WC()->session || empty( $packages ) ) {
return '';
}
$chosen_methods = WC()->session->get( 'chosen_shipping_methods', [] );
+ // Only consider entries that correspond to an actual package built above - e.g. WooCommerce
+ // Subscriptions also stores chosen methods for its own recurring cart packages under this
+ // same session key, keyed differently than the numeric package indices used here.
+ $chosen_methods = array_intersect_key( $chosen_methods, $packages );
foreach ( $chosen_methods as $idx => $method ) {
return sprintf( '%s:%s', $idx, $method );
--- a/woo-stripe-payment/stripe-payments.php
+++ b/woo-stripe-payment/stripe-payments.php
@@ -3,7 +3,7 @@
* Plugin Name: Payment Plugins for Stripe WooCommerce
* Plugin URI: https://paymentplugins.com/documentation/stripe/
* Description: Accept Credit Cards, Google Pay, Apple Pay, ACH, Klarna and more using Stripe.
- * Version: 4.0.7
+ * Version: 4.0.8
* Author: Payment Plugins, support@paymentplugins.com
* Text Domain: woo-stripe-payment
* Domain Path: /i18n/languages/
@@ -19,5 +19,5 @@
require_once( dirname( __FILE__ ) . '/vendor/autoload.php' );
PaymentPluginsStripePluginValidation::is_valid( function () {
- new PaymentPluginsStripePlugin( '4.0.7', __FILE__ );
+ new PaymentPluginsStripePlugin( '4.0.8', __FILE__ );
} );
No newline at end of file
--- a/woo-stripe-payment/vendor/composer/installed.php
+++ b/woo-stripe-payment/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => '__root__',
- 'pretty_version' => '4.0.7.x-dev',
- 'version' => '4.0.7.9999999-dev',
- 'reference' => '9c27ffe5b306fbab52c8af31c56c7f55a08ff6eb',
+ 'pretty_version' => '4.0.8.x-dev',
+ 'version' => '4.0.8.9999999-dev',
+ 'reference' => '7cf856a5e67e62a9d4b429f6d8b1825f0e182834',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -11,9 +11,9 @@
),
'versions' => array(
'__root__' => array(
- 'pretty_version' => '4.0.7.x-dev',
- 'version' => '4.0.7.9999999-dev',
- 'reference' => '9c27ffe5b306fbab52c8af31c56c7f55a08ff6eb',
+ 'pretty_version' => '4.0.8.x-dev',
+ 'version' => '4.0.8.9999999-dev',
+ 'reference' => '7cf856a5e67e62a9d4b429f6d8b1825f0e182834',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),