PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/woocommerce-gateway-stripe/includes/class-wc-stripe-payment-tokens.php

https://gitlab.com/campus-academy/krowkaramel
PHP | 398 lines | 236 code | 43 blank | 119 comment | 55 complexity | 8ddaff44ff45488850f1fe3d68a915b5 MD5 | raw file
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit;
  4. }
  5. /**
  6. * Handles and process WC payment tokens API.
  7. * Seen in checkout page and my account->add payment method page.
  8. *
  9. * @since 4.0.0
  10. */
  11. class WC_Stripe_Payment_Tokens {
  12. private static $_this;
  13. /**
  14. * Constructor.
  15. *
  16. * @since 4.0.0
  17. * @version 4.0.0
  18. */
  19. public function __construct() {
  20. self::$_this = $this;
  21. add_filter( 'woocommerce_get_customer_payment_tokens', [ $this, 'woocommerce_get_customer_payment_tokens' ], 10, 3 );
  22. add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'get_account_saved_payment_methods_list_item_sepa' ], 10, 2 );
  23. add_filter( 'woocommerce_get_credit_card_type_label', [ $this, 'normalize_sepa_label' ] );
  24. add_action( 'woocommerce_payment_token_deleted', [ $this, 'woocommerce_payment_token_deleted' ], 10, 2 );
  25. add_action( 'woocommerce_payment_token_set_default', [ $this, 'woocommerce_payment_token_set_default' ] );
  26. }
  27. /**
  28. * Public access to instance object.
  29. *
  30. * @since 4.0.0
  31. * @version 4.0.0
  32. */
  33. public static function get_instance() {
  34. return self::$_this;
  35. }
  36. /**
  37. * Normalizes the SEPA IBAN label on My Account page.
  38. *
  39. * @since 4.0.0
  40. * @version 4.0.0
  41. * @param string $label
  42. * @return string $label
  43. */
  44. public function normalize_sepa_label( $label ) {
  45. if ( 'sepa iban' === strtolower( $label ) ) {
  46. return 'SEPA IBAN';
  47. }
  48. return $label;
  49. }
  50. /**
  51. * Extract the payment token from the provided request.
  52. *
  53. * TODO: Once php requirement is bumped to >= 7.1.0 set return type to ?\WC_Payment_Token
  54. * since the return type is nullable, as per
  55. * https://www.php.net/manual/en/functions.returning-values.php#functions.returning-values.type-declaration
  56. *
  57. * @param array $request Associative array containing payment request information.
  58. *
  59. * @return \WC_Payment_Token|NULL
  60. */
  61. public static function get_token_from_request( array $request ) {
  62. $payment_method = ! is_null( $request['payment_method'] ) ? $request['payment_method'] : null;
  63. $token_request_key = 'wc-' . $payment_method . '-payment-token';
  64. if (
  65. ! isset( $request[ $token_request_key ] ) ||
  66. 'new' === $request[ $token_request_key ]
  67. ) {
  68. return null;
  69. }
  70. //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
  71. $token = \WC_Payment_Tokens::get( wc_clean( $request[ $token_request_key ] ) );
  72. // If the token doesn't belong to this gateway or the current user it's invalid.
  73. if ( ! $token || $payment_method !== $token->get_gateway_id() || $token->get_user_id() !== get_current_user_id() ) {
  74. return null;
  75. }
  76. return $token;
  77. }
  78. /**
  79. * Checks if customer has saved payment methods.
  80. *
  81. * @since 4.1.0
  82. * @param int $customer_id
  83. * @return bool
  84. */
  85. public static function customer_has_saved_methods( $customer_id ) {
  86. $gateways = [ 'stripe', 'stripe_sepa' ];
  87. if ( empty( $customer_id ) ) {
  88. return false;
  89. }
  90. $has_token = false;
  91. foreach ( $gateways as $gateway ) {
  92. $tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, $gateway );
  93. if ( ! empty( $tokens ) ) {
  94. $has_token = true;
  95. break;
  96. }
  97. }
  98. return $has_token;
  99. }
  100. /**
  101. * Gets saved tokens from Stripe, if they don't already exist in WooCommerce.
  102. *
  103. * @param array $tokens Array of tokens
  104. * @param string $user_id WC User ID
  105. * @param string $gateway_id WC Gateway ID
  106. *
  107. * @return array
  108. */
  109. public function woocommerce_get_customer_payment_tokens( $tokens, $user_id, $gateway_id ) {
  110. if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
  111. return $this->woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, $gateway_id );
  112. } else {
  113. return $this->woocommerce_get_customer_payment_tokens_legacy( $tokens, $user_id, $gateway_id );
  114. }
  115. }
  116. /**
  117. * Gets saved tokens from Sources API if they don't already exist in WooCommerce.
  118. *
  119. * @since 3.1.0
  120. * @version 4.0.0
  121. * @param array $tokens
  122. * @return array
  123. */
  124. public function woocommerce_get_customer_payment_tokens_legacy( $tokens, $customer_id, $gateway_id ) {
  125. if ( is_user_logged_in() && class_exists( 'WC_Payment_Token_CC' ) ) {
  126. $stored_tokens = [];
  127. foreach ( $tokens as $token ) {
  128. $stored_tokens[ $token->get_token() ] = $token;
  129. }
  130. if ( 'stripe' === $gateway_id ) {
  131. $stripe_customer = new WC_Stripe_Customer( $customer_id );
  132. $stripe_sources = $stripe_customer->get_sources();
  133. foreach ( $stripe_sources as $source ) {
  134. if ( isset( $source->type ) && 'card' === $source->type ) {
  135. if ( ! isset( $stored_tokens[ $source->id ] ) ) {
  136. $token = new WC_Payment_Token_CC();
  137. $token->set_token( $source->id );
  138. $token->set_gateway_id( 'stripe' );
  139. if ( 'source' === $source->object && 'card' === $source->type ) {
  140. $token->set_card_type( strtolower( $source->card->brand ) );
  141. $token->set_last4( $source->card->last4 );
  142. $token->set_expiry_month( $source->card->exp_month );
  143. $token->set_expiry_year( $source->card->exp_year );
  144. }
  145. $token->set_user_id( $customer_id );
  146. $token->save();
  147. $tokens[ $token->get_id() ] = $token;
  148. } else {
  149. unset( $stored_tokens[ $source->id ] );
  150. }
  151. } else {
  152. if ( ! isset( $stored_tokens[ $source->id ] ) && 'card' === $source->object ) {
  153. $token = new WC_Payment_Token_CC();
  154. $token->set_token( $source->id );
  155. $token->set_gateway_id( 'stripe' );
  156. $token->set_card_type( strtolower( $source->brand ) );
  157. $token->set_last4( $source->last4 );
  158. $token->set_expiry_month( $source->exp_month );
  159. $token->set_expiry_year( $source->exp_year );
  160. $token->set_user_id( $customer_id );
  161. $token->save();
  162. $tokens[ $token->get_id() ] = $token;
  163. } else {
  164. unset( $stored_tokens[ $source->id ] );
  165. }
  166. }
  167. }
  168. }
  169. if ( 'stripe_sepa' === $gateway_id ) {
  170. $stripe_customer = new WC_Stripe_Customer( $customer_id );
  171. $stripe_sources = $stripe_customer->get_sources();
  172. foreach ( $stripe_sources as $source ) {
  173. if ( isset( $source->type ) && 'sepa_debit' === $source->type ) {
  174. if ( ! isset( $stored_tokens[ $source->id ] ) ) {
  175. $token = new WC_Payment_Token_SEPA();
  176. $token->set_token( $source->id );
  177. $token->set_gateway_id( 'stripe_sepa' );
  178. $token->set_last4( $source->sepa_debit->last4 );
  179. $token->set_user_id( $customer_id );
  180. $token->save();
  181. $tokens[ $token->get_id() ] = $token;
  182. } else {
  183. unset( $stored_tokens[ $source->id ] );
  184. }
  185. }
  186. }
  187. }
  188. }
  189. return $tokens;
  190. }
  191. /**
  192. * Gets saved tokens from Intentions API if they don't already exist in WooCommerce.
  193. *
  194. * @param array $tokens Array of tokens
  195. * @param string $user_id WC User ID
  196. * @param string $gateway_id WC Gateway ID
  197. *
  198. * @return array
  199. */
  200. public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, $gateway_id ) {
  201. if ( ( ! empty( $gateway_id ) && WC_Stripe_UPE_Payment_Gateway::ID !== $gateway_id ) || ! is_user_logged_in() ) {
  202. return $tokens;
  203. }
  204. if ( count( $tokens ) >= get_option( 'posts_per_page' ) ) {
  205. // The tokens data store is not paginated and only the first "post_per_page" (defaults to 10) tokens are retrieved.
  206. // Having 10 saved credit cards is considered an unsupported edge case, new ones that have been stored in Stripe won't be added.
  207. return $tokens;
  208. }
  209. $gateway = new WC_Stripe_UPE_Payment_Gateway();
  210. $reusable_payment_methods = array_filter( $gateway->get_upe_enabled_payment_method_ids(), [ $gateway, 'is_enabled_for_saved_payments' ] );
  211. $customer = new WC_Stripe_Customer( $user_id );
  212. $remaining_tokens = [];
  213. foreach ( $tokens as $token ) {
  214. if ( WC_Stripe_UPE_Payment_Gateway::ID === $token->get_gateway_id() ) {
  215. $payment_method_type = $this->get_payment_method_type_from_token( $token );
  216. if ( ! in_array( $payment_method_type, $reusable_payment_methods, true ) ) {
  217. // Remove saved token from list, if payment method is not enabled.
  218. unset( $tokens[ $token->get_id() ] );
  219. } else {
  220. // Store relevant existing tokens here.
  221. // We will use this list to check whether these methods still exist on Stripe's side.
  222. $remaining_tokens[ $token->get_token() ] = $token;
  223. }
  224. }
  225. }
  226. $retrievable_payment_method_types = [];
  227. foreach ( $reusable_payment_methods as $payment_method_id ) {
  228. $upe_payment_method = $gateway->payment_methods[ $payment_method_id ];
  229. if ( ! in_array( $upe_payment_method->get_retrievable_type(), $retrievable_payment_method_types, true ) ) {
  230. $retrievable_payment_method_types[] = $upe_payment_method->get_retrievable_type();
  231. }
  232. }
  233. foreach ( $retrievable_payment_method_types as $payment_method_id ) {
  234. $payment_methods = $customer->get_payment_methods( $payment_method_id );
  235. // Prevent unnecessary recursion, WC_Payment_Token::save() ends up calling 'woocommerce_get_customer_payment_tokens' in some cases.
  236. remove_action( 'woocommerce_get_customer_payment_tokens', [ $this, 'woocommerce_get_customer_payment_tokens' ], 10, 3 );
  237. foreach ( $payment_methods as $payment_method ) {
  238. if ( ! isset( $remaining_tokens[ $payment_method->id ] ) ) {
  239. $payment_method_type = $this->get_original_payment_method_type( $payment_method );
  240. if ( ! in_array( $payment_method_type, $reusable_payment_methods, true ) ) {
  241. continue;
  242. }
  243. // Create new token for new payment method and add to list.
  244. $upe_payment_method = $gateway->payment_methods[ $payment_method_type ];
  245. $token = $upe_payment_method->create_payment_token_for_user( $user_id, $payment_method );
  246. $tokens[ $token->get_id() ] = $token;
  247. } else {
  248. // Count that existing token for payment method is still present on Stripe.
  249. // Remaining IDs in $remaining_tokens no longer exist with Stripe and will be eliminated.
  250. unset( $remaining_tokens[ $payment_method->id ] );
  251. }
  252. }
  253. add_action( 'woocommerce_get_customer_payment_tokens', [ $this, 'woocommerce_get_customer_payment_tokens' ], 10, 3 );
  254. }
  255. // Eliminate remaining payment methods no longer known by Stripe.
  256. // Prevent unnecessary recursion, when deleting tokens.
  257. remove_action( 'woocommerce_payment_token_deleted', [ $this, 'woocommerce_payment_token_deleted' ], 10, 2 );
  258. foreach ( $remaining_tokens as $token ) {
  259. unset( $tokens[ $token->get_id() ] );
  260. $token->delete();
  261. }
  262. add_action( 'woocommerce_payment_token_deleted', [ $this, 'woocommerce_payment_token_deleted' ], 10, 2 );
  263. return $tokens;
  264. }
  265. /**
  266. * Returns original type of payment method from Stripe payment method response,
  267. * after checking whether payment method is SEPA method generated from another type.
  268. *
  269. * @param object $payment_method Stripe payment method JSON object.
  270. *
  271. * @return string Payment method type/ID
  272. */
  273. private function get_original_payment_method_type( $payment_method ) {
  274. if ( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID === $payment_method->type ) {
  275. if ( ! is_null( $payment_method->sepa_debit->generated_from->charge ) ) {
  276. return $payment_method->sepa_debit->generated_from->charge->payment_method_details->type;
  277. }
  278. if ( ! is_null( $payment_method->sepa_debit->generated_from->setup_attempt ) ) {
  279. return $payment_method->sepa_debit->generated_from->setup_attempt->payment_method_details->type;
  280. }
  281. }
  282. return $payment_method->type;
  283. }
  284. /**
  285. * Returns original Stripe payment method type from payment token
  286. *
  287. * @param WC_Payment_Token $payment_token WC Payment Token (CC or SEPA)
  288. *
  289. * @return string
  290. */
  291. private function get_payment_method_type_from_token( $payment_token ) {
  292. $type = $payment_token->get_type();
  293. if ( 'CC' === $type ) {
  294. return 'card';
  295. } elseif ( 'sepa' === $type ) {
  296. return $payment_token->get_payment_method_type();
  297. } else {
  298. return $type;
  299. }
  300. }
  301. /**
  302. * Controls the output for SEPA on the my account page.
  303. *
  304. * @since 4.0.0
  305. * @version 4.0.0
  306. * @param array $item Individual list item from woocommerce_saved_payment_methods_list
  307. * @param WC_Payment_Token $payment_token The payment token associated with this method entry
  308. * @return array Filtered item
  309. */
  310. public function get_account_saved_payment_methods_list_item_sepa( $item, $payment_token ) {
  311. if ( 'sepa' === strtolower( $payment_token->get_type() ) ) {
  312. $item['method']['last4'] = $payment_token->get_last4();
  313. $item['method']['brand'] = esc_html__( 'SEPA IBAN', 'woocommerce-gateway-stripe' );
  314. }
  315. return $item;
  316. }
  317. /**
  318. * Delete token from Stripe.
  319. *
  320. * @since 3.1.0
  321. * @version 4.0.0
  322. */
  323. public function woocommerce_payment_token_deleted( $token_id, $token ) {
  324. $stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
  325. if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
  326. if ( WC_Stripe_UPE_Payment_Gateway::ID === $token->get_gateway_id() ) {
  327. $stripe_customer->detach_payment_method( $token->get_token() );
  328. }
  329. } else {
  330. if ( 'stripe' === $token->get_gateway_id() || 'stripe_sepa' === $token->get_gateway_id() ) {
  331. $stripe_customer->delete_source( $token->get_token() );
  332. }
  333. }
  334. }
  335. /**
  336. * Set as default in Stripe.
  337. *
  338. * @since 3.1.0
  339. * @version 4.0.0
  340. */
  341. public function woocommerce_payment_token_set_default( $token_id ) {
  342. $token = WC_Payment_Tokens::get( $token_id );
  343. $stripe_customer = new WC_Stripe_Customer( get_current_user_id() );
  344. if ( WC_Stripe_Feature_Flags::is_upe_checkout_enabled() ) {
  345. if ( WC_Stripe_UPE_Payment_Gateway::ID === $token->get_gateway_id() ) {
  346. $stripe_customer->set_default_payment_method( $token->get_token() );
  347. }
  348. } else {
  349. if ( 'stripe' === $token->get_gateway_id() || 'stripe_sepa' === $token->get_gateway_id() ) {
  350. $stripe_customer->set_default_source( $token->get_token() );
  351. }
  352. }
  353. }
  354. }
  355. new WC_Stripe_Payment_Tokens();