/wp-content/plugins/woocommerce-payments/includes/subscriptions/class-wc-payments-subscription-change-payment-method-handler.php

https://gitlab.com/remyvianne/krowkaramel · PHP · 219 lines · 112 code · 33 blank · 74 comment · 26 complexity · e0bf1128508c68f164c7dd53ba97431a MD5 · raw file

  1. <?php
  2. /**
  3. * Class WC_Payments_Subscription_Change_Payment_Method
  4. *
  5. * @package WooCommerce\Payments
  6. */
  7. defined( 'ABSPATH' ) || exit;
  8. /**
  9. * Class handling any WCPay subscription change payment method functionality.
  10. */
  11. class WC_Payments_Subscription_Change_Payment_Method_Handler {
  12. /**
  13. * Constructor.
  14. */
  15. public function __construct() {
  16. // Add an "Update card" action to all WCPay billing subscriptions with a failed renewal order.
  17. add_filter( 'wcs_view_subscription_actions', [ $this, 'update_subscription_change_payment_button' ], 15, 2 );
  18. add_filter( 'woocommerce_can_subscription_be_updated_to_new-payment-method', [ $this, 'can_update_payment_method' ], 15, 2 );
  19. // Override the pay for order link on the order to redirect to a change payment method page.
  20. add_filter( 'woocommerce_my_account_my_orders_actions', [ $this, 'update_order_pay_button' ], 15, 2 );
  21. // Filter elements/messaging on the "Change payment method" page to reflect updating a WCPay billing card.
  22. add_filter( 'woocommerce_subscriptions_change_payment_method_page_title', [ $this, 'change_payment_method_page_title' ], 10, 2 );
  23. add_filter( 'woocommerce_subscriptions_change_payment_method_page_notice_message', [ $this, 'change_payment_method_page_notice' ], 10, 2 );
  24. // Fallback to redirecting all pay for order pages for WCPay billing invoices to the update card page.
  25. add_action( 'template_redirect', [ $this, 'redirect_pay_for_order_to_update_payment_method' ] );
  26. add_filter( 'woocommerce_change_payment_button_text', [ $this, 'change_payment_method_form_submit_text' ] );
  27. }
  28. /**
  29. * Replaces the default change payment method action for WC Pay subscriptions when the subscription needs a new payment method after a failed attempt.
  30. *
  31. * @param array $actions The My Account > View Subscription actions.
  32. * @param WC_Subscription $subscription The subscription object.
  33. *
  34. * @return array The subscription actions.
  35. */
  36. public function update_subscription_change_payment_button( $actions, $subscription ) {
  37. if ( $this->does_subscription_need_payment_updated( $subscription ) ) {
  38. // Override any existing button on $actions['change_payment_method'] to show "Update Card" button.
  39. $actions['change_payment_method'] = [
  40. 'url' => $this->get_subscription_update_payment_url( $subscription ),
  41. 'name' => __( 'Update payment method', 'woocommerce-payments' ),
  42. ];
  43. }
  44. return $actions;
  45. }
  46. /**
  47. * Updates the 'Pay' link displayed on the My Account > Orders or from a subscriptions related orders table, to make sure customers are directed to update their card.
  48. *
  49. * @param array $actions Order actions.
  50. * @param WC_Order $order The WC Order object.
  51. *
  52. * @return array The order actions.
  53. */
  54. public function update_order_pay_button( $actions, $order ) {
  55. // If the order isn't payable, there's nothing to update.
  56. if ( ! isset( $actions['pay'] ) ) {
  57. return $actions;
  58. }
  59. $invoice_id = WC_Payments_Invoice_Service::get_order_invoice_id( $order );
  60. $updated_pay_action = false;
  61. // Don't show the default pay link for any WC Pay Subscription order because we don't want customer paying for them.
  62. if ( $invoice_id ) {
  63. $subscriptions = wcs_get_subscriptions_for_order( $order, [ 'order_type' => 'any' ] );
  64. if ( ! empty( $subscriptions ) ) {
  65. $subscription = array_pop( $subscriptions );
  66. if ( $subscription && WC_Payments_Invoice_Service::get_pending_invoice_id( $subscription ) ) {
  67. $actions['pay']['url'] = $this->get_subscription_update_payment_url( $subscription );
  68. $updated_pay_action = true;
  69. }
  70. }
  71. if ( ! $updated_pay_action ) {
  72. unset( $actions['pay'] );
  73. }
  74. }
  75. return $actions;
  76. }
  77. /**
  78. * Filters subscription `can_be_updated_to( 'new-payment-method' )` calls to allow customers to update their subscription's payment method.
  79. *
  80. * @param bool $can_update Whether the subscription's payment method can be updated.
  81. * @param WC_Subscription $subscription The WC Subscription object.
  82. *
  83. * @return bool Whether the subscription's payment method can be updated.
  84. */
  85. public function can_update_payment_method( bool $can_update, WC_Subscription $subscription ) {
  86. return $this->does_subscription_need_payment_updated( $subscription ) ? true : $can_update;
  87. }
  88. /**
  89. * Redirects customers to update their payment method rather than pay for a WC Pay Subscription's failed order.
  90. */
  91. public function redirect_pay_for_order_to_update_payment_method() {
  92. global $wp;
  93. if ( isset( $_GET['pay_for_order'], $_GET['key'] ) && empty( $_GET['change_payment_method'] ) && ( isset( $_GET['order_id'] ) || isset( $wp->query_vars['order-pay'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  94. // Check if the order is linked to a billing invoice.
  95. $order_id = ( isset( $wp->query_vars['order-pay'] ) ) ? absint( $wp->query_vars['order-pay'] ) : absint( $_GET['order_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  96. $order = wc_get_order( $order_id );
  97. if ( $order && $order instanceof WC_Order ) {
  98. $invoice_id = WC_Payments_Invoice_Service::get_order_invoice_id( $order );
  99. if ( $invoice_id ) {
  100. $subscriptions = wcs_get_subscriptions_for_order( $order, [ 'order_type' => 'any' ] );
  101. if ( ! empty( $subscriptions ) ) {
  102. $subscription = array_pop( $subscriptions );
  103. if ( $subscription && WC_Payments_Invoice_Service::get_pending_invoice_id( $subscription ) ) {
  104. wp_safe_redirect( $this->get_subscription_update_payment_url( $subscription ) );
  105. exit;
  106. }
  107. }
  108. }
  109. }
  110. }
  111. }
  112. /**
  113. * Modifies the change payment method page title (and page breadcrumbs) when updating card details for WC Pay subscriptions.
  114. *
  115. * @param string $title The default page title.
  116. * @param WC_Subscription $subscription The WC Subscription object.
  117. *
  118. * @return string The page title.
  119. */
  120. public function change_payment_method_page_title( string $title, WC_Subscription $subscription ) {
  121. if ( $this->does_subscription_need_payment_updated( $subscription ) ) {
  122. $title = __( 'Update payment details', 'woocommerce-payments' );
  123. }
  124. return $title;
  125. }
  126. /**
  127. * Modifies the message shown on the change payment method page.
  128. *
  129. * @param string $message The default customer notice shown on the change payment method page.
  130. * @param WC_Subscription $subscription The Subscription.
  131. *
  132. * @return string The customer notice shown on the change payment method page.
  133. */
  134. public function change_payment_method_page_notice( string $message, WC_Subscription $subscription ) {
  135. if ( $this->does_subscription_need_payment_updated( $subscription ) ) {
  136. $message = __( "Your subscription's last renewal failed payment. Please update your payment details so we can reattempt payment.", 'woocommerce-payments' );
  137. }
  138. return $message;
  139. }
  140. /**
  141. * Checks if a subscription needs to update it's WCPay payment method.
  142. *
  143. * @param WC_Subscription $subscription The WC Subscription object.
  144. * @return bool Whether the subscription's last order failed and needs a new updated payment method.
  145. */
  146. private function does_subscription_need_payment_updated( $subscription ) {
  147. // We're only interested in WC Pay subscriptions that are on hold due to a failed payment.
  148. if ( ! $subscription->has_status( 'on-hold' ) || ! WC_Payments_Subscription_Service::get_wcpay_subscription_id( $subscription ) ) {
  149. return false;
  150. }
  151. $last_order = $subscription->get_last_order( 'all', 'any' );
  152. return $last_order && $last_order->has_status( 'failed' );
  153. }
  154. /**
  155. * Generates the URL for the WC Pay Subscription's update payment method screen.
  156. *
  157. * @param WC_Subscription $subscription The WC Subscription object.
  158. * @return string The update payment method
  159. */
  160. private function get_subscription_update_payment_url( $subscription ) {
  161. return add_query_arg(
  162. [
  163. 'change_payment_method' => $subscription->get_id(),
  164. '_wpnonce' => wp_create_nonce(),
  165. ],
  166. $subscription->get_checkout_payment_url()
  167. );
  168. }
  169. /**
  170. * Modifies the change payment method form submit button to include language about retrying payment if there's a failed order.
  171. *
  172. * @param string $button_text The change subscription payment method button text.
  173. * @return string The change subscription payment method button text.
  174. */
  175. public function change_payment_method_form_submit_text( $button_text ) {
  176. if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
  177. $subscription = wcs_get_subscription( wc_clean( wp_unslash( $_GET['change_payment_method'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
  178. if ( $subscription && $this->does_subscription_need_payment_updated( $subscription ) ) {
  179. $button_text = __( 'Update and retry payment', 'woocommerce-payments' );
  180. }
  181. }
  182. return $button_text;
  183. }
  184. }