woocommerce /
woocommerce-gateway-stripe
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | if ( ! defined( 'ABSPATH' ) ) { |
||
| 3 | exit; |
||
| 4 | } |
||
| 5 | |||
| 6 | /** |
||
| 7 | * WC_Stripe_Customer class. |
||
| 8 | * |
||
| 9 | * Represents a Stripe Customer. |
||
| 10 | */ |
||
| 11 | class WC_Stripe_Customer { |
||
| 12 | |||
| 13 | /** |
||
| 14 | * Stripe customer ID |
||
| 15 | * @var string |
||
| 16 | */ |
||
| 17 | private $id = ''; |
||
| 18 | |||
| 19 | /** |
||
| 20 | * WP User ID |
||
| 21 | * @var integer |
||
| 22 | */ |
||
| 23 | private $user_id = 0; |
||
| 24 | |||
| 25 | /** |
||
| 26 | * Data from API |
||
| 27 | * @var array |
||
| 28 | */ |
||
| 29 | private $customer_data = array(); |
||
| 30 | |||
| 31 | /** |
||
| 32 | * Constructor |
||
| 33 | * @param int $user_id The WP user ID |
||
| 34 | */ |
||
| 35 | public function __construct( $user_id = 0 ) { |
||
| 36 | if ( $user_id ) { |
||
| 37 | $this->set_user_id( $user_id ); |
||
| 38 | $this->set_id( $this->get_id_from_meta( $user_id ) ); |
||
| 39 | } |
||
| 40 | } |
||
| 41 | |||
| 42 | /** |
||
| 43 | * Get Stripe customer ID. |
||
| 44 | * @return string |
||
| 45 | */ |
||
| 46 | public function get_id() { |
||
| 47 | return $this->id; |
||
| 48 | } |
||
| 49 | |||
| 50 | /** |
||
| 51 | * Set Stripe customer ID. |
||
| 52 | * @param [type] $id [description] |
||
| 53 | */ |
||
| 54 | public function set_id( $id ) { |
||
| 55 | // Backwards compat for customer ID stored in array format. (Pre 3.0) |
||
| 56 | if ( is_array( $id ) && isset( $id['customer_id'] ) ) { |
||
| 57 | $id = $id['customer_id']; |
||
| 58 | |||
| 59 | $this->update_id_in_meta( $id ); |
||
| 60 | } |
||
| 61 | |||
| 62 | $this->id = wc_clean( $id ); |
||
| 63 | } |
||
| 64 | |||
| 65 | /** |
||
| 66 | * User ID in WordPress. |
||
| 67 | * @return int |
||
| 68 | */ |
||
| 69 | public function get_user_id() { |
||
| 70 | return absint( $this->user_id ); |
||
| 71 | } |
||
| 72 | |||
| 73 | /** |
||
| 74 | * Set User ID used by WordPress. |
||
| 75 | * @param int $user_id |
||
| 76 | */ |
||
| 77 | public function set_user_id( $user_id ) { |
||
| 78 | $this->user_id = absint( $user_id ); |
||
| 79 | } |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Get user object. |
||
| 83 | * @return WP_User |
||
| 84 | */ |
||
| 85 | protected function get_user() { |
||
| 86 | return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false; |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * Store data from the Stripe API about this customer |
||
| 91 | */ |
||
| 92 | public function set_customer_data( $data ) { |
||
| 93 | $this->customer_data = $data; |
||
| 94 | } |
||
| 95 | |||
| 96 | /** |
||
| 97 | * Generates the customer request, used for both creating and updating customers. |
||
| 98 | * |
||
| 99 | * @param array $args Additional arguments (optional). |
||
| 100 | * @return array |
||
| 101 | */ |
||
| 102 | protected function generate_customer_request( $args = array() ) { |
||
| 103 | $billing_email = isset( $_POST['billing_email'] ) ? filter_var( $_POST['billing_email'], FILTER_SANITIZE_EMAIL ) : ''; |
||
| 104 | $user = $this->get_user(); |
||
| 105 | |||
| 106 | if ( $user ) { |
||
| 107 | $billing_first_name = get_user_meta( $user->ID, 'billing_first_name', true ); |
||
| 108 | $billing_last_name = get_user_meta( $user->ID, 'billing_last_name', true ); |
||
| 109 | |||
| 110 | // If billing first name does not exists try the user first name. |
||
| 111 | if ( empty( $billing_first_name ) ) { |
||
| 112 | $billing_first_name = get_user_meta( $user->ID, 'first_name', true ); |
||
| 113 | } |
||
| 114 | |||
| 115 | // If billing last name does not exists try the user last name. |
||
| 116 | if ( empty( $billing_last_name ) ) { |
||
| 117 | $billing_last_name = get_user_meta( $user->ID, 'last_name', true ); |
||
| 118 | } |
||
| 119 | |||
| 120 | // translators: %1$s First name, %2$s Second name, %3$s Username. |
||
| 121 | $description = sprintf( __( 'Name: %1$s %2$s, Username: %s', 'woocommerce-gateway-stripe' ), $billing_first_name, $billing_last_name, $user->user_login ); |
||
| 122 | |||
| 123 | $defaults = array( |
||
| 124 | 'email' => $user->user_email, |
||
| 125 | 'description' => $description, |
||
| 126 | ); |
||
| 127 | |||
| 128 | $billing_full_name = trim( $billing_first_name . ' ' . $billing_last_name ); |
||
| 129 | if ( ! empty( $billing_full_name ) ) { |
||
| 130 | $defaults['name'] = $billing_full_name; |
||
| 131 | } |
||
| 132 | } else { |
||
| 133 | $billing_first_name = isset( $_POST['billing_first_name'] ) ? filter_var( wp_unslash( $_POST['billing_first_name'] ), FILTER_SANITIZE_STRING ) : ''; // phpcs:ignore WordPress.Security.NonceVerification |
||
| 134 | $billing_last_name = isset( $_POST['billing_last_name'] ) ? filter_var( wp_unslash( $_POST['billing_last_name'] ), FILTER_SANITIZE_STRING ) : ''; // phpcs:ignore WordPress.Security.NonceVerification |
||
| 135 | |||
| 136 | // translators: %1$s First name, %2$s Second name. |
||
| 137 | $description = sprintf( __( 'Name: %1$s %2$s, Guest', 'woocommerce-gateway-stripe' ), $billing_first_name, $billing_last_name ); |
||
| 138 | |||
| 139 | $defaults = array( |
||
| 140 | 'email' => $billing_email, |
||
| 141 | 'description' => $description, |
||
| 142 | ); |
||
| 143 | |||
| 144 | $billing_full_name = trim( $billing_first_name . ' ' . $billing_last_name ); |
||
| 145 | if ( ! empty( $billing_full_name ) ) { |
||
| 146 | $defaults['name'] = $billing_full_name; |
||
| 147 | } |
||
| 148 | } |
||
| 149 | |||
| 150 | $metadata = array(); |
||
| 151 | $defaults['metadata'] = apply_filters( 'wc_stripe_customer_metadata', $metadata, $user ); |
||
| 152 | |||
| 153 | return wp_parse_args( $args, $defaults ); |
||
| 154 | } |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Create a customer via API. |
||
| 158 | * @param array $args |
||
| 159 | * @return WP_Error|int |
||
| 160 | */ |
||
| 161 | public function create_customer( $args = array() ) { |
||
| 162 | $args = $this->generate_customer_request( $args ); |
||
| 163 | $response = WC_Stripe_API::request( apply_filters( 'wc_stripe_create_customer_args', $args ), 'customers' ); |
||
| 164 | |||
| 165 | View Code Duplication | if ( ! empty( $response->error ) ) { |
|
|
0 ignored issues
–
show
|
|||
| 166 | throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message ); |
||
| 167 | } |
||
| 168 | |||
| 169 | $this->set_id( $response->id ); |
||
| 170 | $this->clear_cache(); |
||
| 171 | $this->set_customer_data( $response ); |
||
| 172 | |||
| 173 | if ( $this->get_user_id() ) { |
||
| 174 | $this->update_id_in_meta( $response->id ); |
||
| 175 | } |
||
| 176 | |||
| 177 | do_action( 'woocommerce_stripe_add_customer', $args, $response ); |
||
| 178 | |||
| 179 | return $response->id; |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * Updates the Stripe customer through the API. |
||
| 184 | * |
||
| 185 | * @param array $args Additional arguments for the request (optional). |
||
| 186 | * @param bool $is_retry Whether the current call is a retry (optional, defaults to false). If true, then an exception will be thrown instead of further retries on error. |
||
| 187 | * |
||
| 188 | * @return string Customer ID |
||
| 189 | * |
||
| 190 | * @throws WC_Stripe_Exception |
||
| 191 | */ |
||
| 192 | public function update_customer( $args = array(), $is_retry = false ) { |
||
| 193 | if ( empty( $this->get_id() ) ) { |
||
| 194 | throw new WC_Stripe_Exception( 'id_required_to_update_user', __( 'Attempting to update a Stripe customer without a customer ID.', 'woocommerce-gateway-stripe' ) ); |
||
| 195 | } |
||
| 196 | |||
| 197 | $args = $this->generate_customer_request( $args ); |
||
| 198 | $args = apply_filters( 'wc_stripe_update_customer_args', $args ); |
||
| 199 | $response = WC_Stripe_API::request( $args, 'customers/' . $this->get_id() ); |
||
| 200 | |||
| 201 | if ( ! empty( $response->error ) ) { |
||
| 202 | if ( $this->is_no_such_customer_error( $response->error ) && ! $is_retry ) { |
||
| 203 | // This can happen when switching the main Stripe account or importing users from another site. |
||
| 204 | // If not already retrying, recreate the customer and then try updating it again. |
||
| 205 | $this->recreate_customer(); |
||
| 206 | return $this->update_customer( $args, true ); |
||
| 207 | } |
||
| 208 | |||
| 209 | throw new WC_Stripe_Exception( print_r( $response, true ), $response->error->message ); |
||
| 210 | } |
||
| 211 | |||
| 212 | $this->clear_cache(); |
||
| 213 | $this->set_customer_data( $response ); |
||
| 214 | |||
| 215 | do_action( 'woocommerce_stripe_update_customer', $args, $response ); |
||
| 216 | |||
| 217 | return $this->get_id(); |
||
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * Checks to see if error is of invalid request |
||
| 222 | * error and it is no such customer. |
||
| 223 | * |
||
| 224 | * @since 4.1.2 |
||
| 225 | * @param array $error |
||
| 226 | */ |
||
| 227 | public function is_no_such_customer_error( $error ) { |
||
| 228 | return ( |
||
| 229 | $error && |
||
| 230 | 'invalid_request_error' === $error->type && |
||
| 231 | preg_match( '/No such customer/i', $error->message ) |
||
| 232 | ); |
||
| 233 | } |
||
| 234 | |||
| 235 | /** |
||
| 236 | * Add a source for this stripe customer. |
||
| 237 | * @param string $source_id |
||
| 238 | * @return WP_Error|int |
||
| 239 | */ |
||
| 240 | public function add_source( $source_id ) { |
||
| 241 | if ( ! $this->get_id() ) { |
||
| 242 | $this->set_id( $this->create_customer() ); |
||
| 243 | } |
||
| 244 | |||
| 245 | $response = WC_Stripe_API::request( |
||
| 246 | array( |
||
| 247 | 'source' => $source_id, |
||
| 248 | ), |
||
| 249 | 'customers/' . $this->get_id() . '/sources' |
||
| 250 | ); |
||
| 251 | |||
| 252 | $wc_token = false; |
||
| 253 | |||
| 254 | if ( ! empty( $response->error ) ) { |
||
| 255 | // It is possible the WC user once was linked to a customer on Stripe |
||
| 256 | // but no longer exists. Instead of failing, lets try to create a |
||
| 257 | // new customer. |
||
| 258 | if ( $this->is_no_such_customer_error( $response->error ) ) { |
||
| 259 | $this->recreate_customer(); |
||
| 260 | return $this->add_source( $source_id ); |
||
| 261 | } else { |
||
| 262 | return $response; |
||
|
0 ignored issues
–
show
The return type of
return $response; (stdClass|array) is incompatible with the return type documented by WC_Stripe_Customer::add_source of type WP_Error|integer.
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function Loading history...
|
|||
| 263 | } |
||
| 264 | } elseif ( empty( $response->id ) ) { |
||
| 265 | return new WP_Error( 'error', __( 'Unable to add payment source.', 'woocommerce-gateway-stripe' ) ); |
||
| 266 | } |
||
| 267 | |||
| 268 | // Add token to WooCommerce. |
||
| 269 | if ( $this->get_user_id() && class_exists( 'WC_Payment_Token_CC' ) ) { |
||
| 270 | if ( ! empty( $response->type ) ) { |
||
| 271 | switch ( $response->type ) { |
||
| 272 | case 'alipay': |
||
| 273 | break; |
||
| 274 | case 'sepa_debit': |
||
| 275 | $wc_token = new WC_Payment_Token_SEPA(); |
||
| 276 | $wc_token->set_token( $response->id ); |
||
| 277 | $wc_token->set_gateway_id( 'stripe_sepa' ); |
||
| 278 | $wc_token->set_last4( $response->sepa_debit->last4 ); |
||
| 279 | break; |
||
| 280 | default: |
||
| 281 | if ( 'source' === $response->object && 'card' === $response->type ) { |
||
| 282 | $wc_token = new WC_Payment_Token_CC(); |
||
| 283 | $wc_token->set_token( $response->id ); |
||
| 284 | $wc_token->set_gateway_id( 'stripe' ); |
||
| 285 | $wc_token->set_card_type( strtolower( $response->card->brand ) ); |
||
| 286 | $wc_token->set_last4( $response->card->last4 ); |
||
| 287 | $wc_token->set_expiry_month( $response->card->exp_month ); |
||
| 288 | $wc_token->set_expiry_year( $response->card->exp_year ); |
||
| 289 | } |
||
| 290 | break; |
||
| 291 | } |
||
| 292 | } else { |
||
| 293 | // Legacy. |
||
| 294 | $wc_token = new WC_Payment_Token_CC(); |
||
| 295 | $wc_token->set_token( $response->id ); |
||
| 296 | $wc_token->set_gateway_id( 'stripe' ); |
||
| 297 | $wc_token->set_card_type( strtolower( $response->brand ) ); |
||
| 298 | $wc_token->set_last4( $response->last4 ); |
||
| 299 | $wc_token->set_expiry_month( $response->exp_month ); |
||
| 300 | $wc_token->set_expiry_year( $response->exp_year ); |
||
| 301 | } |
||
| 302 | |||
| 303 | $wc_token->set_user_id( $this->get_user_id() ); |
||
| 304 | $wc_token->save(); |
||
| 305 | } |
||
| 306 | |||
| 307 | $this->clear_cache(); |
||
| 308 | |||
| 309 | do_action( 'woocommerce_stripe_add_source', $this->get_id(), $wc_token, $response, $source_id ); |
||
| 310 | |||
| 311 | return $response->id; |
||
| 312 | } |
||
| 313 | |||
| 314 | /** |
||
| 315 | * Get a customers saved sources using their Stripe ID. |
||
| 316 | * |
||
| 317 | * @param string $customer_id |
||
| 318 | * @return array |
||
| 319 | */ |
||
| 320 | public function get_sources() { |
||
| 321 | if ( ! $this->get_id() ) { |
||
| 322 | return array(); |
||
| 323 | } |
||
| 324 | |||
| 325 | $sources = get_transient( 'stripe_sources_' . $this->get_id() ); |
||
| 326 | |||
| 327 | if ( false === $sources ) { |
||
| 328 | $response = WC_Stripe_API::request( |
||
| 329 | array( |
||
| 330 | 'limit' => 100, |
||
| 331 | ), |
||
| 332 | 'customers/' . $this->get_id() . '/sources', |
||
| 333 | 'GET' |
||
| 334 | ); |
||
| 335 | |||
| 336 | if ( ! empty( $response->error ) ) { |
||
| 337 | return array(); |
||
| 338 | } |
||
| 339 | |||
| 340 | if ( is_array( $response->data ) ) { |
||
| 341 | $sources = $response->data; |
||
| 342 | } |
||
| 343 | |||
| 344 | set_transient( 'stripe_sources_' . $this->get_id(), $sources, DAY_IN_SECONDS ); |
||
| 345 | } |
||
| 346 | |||
| 347 | return empty( $sources ) ? array() : $sources; |
||
| 348 | } |
||
| 349 | |||
| 350 | /** |
||
| 351 | * Delete a source from stripe. |
||
| 352 | * @param string $source_id |
||
| 353 | */ |
||
| 354 | public function delete_source( $source_id ) { |
||
| 355 | if ( ! $this->get_id() ) { |
||
| 356 | return false; |
||
| 357 | } |
||
| 358 | |||
| 359 | $response = WC_Stripe_API::request( array(), 'customers/' . $this->get_id() . '/sources/' . sanitize_text_field( $source_id ), 'DELETE' ); |
||
| 360 | |||
| 361 | $this->clear_cache(); |
||
| 362 | |||
| 363 | if ( empty( $response->error ) ) { |
||
| 364 | do_action( 'wc_stripe_delete_source', $this->get_id(), $response ); |
||
| 365 | |||
| 366 | return true; |
||
| 367 | } |
||
| 368 | |||
| 369 | return false; |
||
| 370 | } |
||
| 371 | |||
| 372 | /** |
||
| 373 | * Set default source in Stripe |
||
| 374 | * @param string $source_id |
||
| 375 | */ |
||
| 376 | public function set_default_source( $source_id ) { |
||
| 377 | $response = WC_Stripe_API::request( |
||
| 378 | array( |
||
| 379 | 'default_source' => sanitize_text_field( $source_id ), |
||
| 380 | ), |
||
| 381 | 'customers/' . $this->get_id(), |
||
| 382 | 'POST' |
||
| 383 | ); |
||
| 384 | |||
| 385 | $this->clear_cache(); |
||
| 386 | |||
| 387 | if ( empty( $response->error ) ) { |
||
| 388 | do_action( 'wc_stripe_set_default_source', $this->get_id(), $response ); |
||
| 389 | |||
| 390 | return true; |
||
| 391 | } |
||
| 392 | |||
| 393 | return false; |
||
| 394 | } |
||
| 395 | |||
| 396 | /** |
||
| 397 | * Deletes caches for this users cards. |
||
| 398 | */ |
||
| 399 | public function clear_cache() { |
||
| 400 | delete_transient( 'stripe_sources_' . $this->get_id() ); |
||
| 401 | delete_transient( 'stripe_customer_' . $this->get_id() ); |
||
| 402 | $this->customer_data = array(); |
||
| 403 | } |
||
| 404 | |||
| 405 | /** |
||
| 406 | * Retrieves the Stripe Customer ID from the user meta. |
||
| 407 | * |
||
| 408 | * @param int $user_id The ID of the WordPress user. |
||
| 409 | * @return string|bool Either the Stripe ID or false. |
||
| 410 | */ |
||
| 411 | public function get_id_from_meta( $user_id ) { |
||
| 412 | return get_user_option( '_stripe_customer_id', $user_id ); |
||
| 413 | } |
||
| 414 | |||
| 415 | /** |
||
| 416 | * Updates the current user with the right Stripe ID in the meta table. |
||
| 417 | * |
||
| 418 | * @param string $id The Stripe customer ID. |
||
| 419 | */ |
||
| 420 | public function update_id_in_meta( $id ) { |
||
| 421 | update_user_option( $this->get_user_id(), '_stripe_customer_id', $id, false ); |
||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Deletes the user ID from the meta table with the right key. |
||
| 426 | */ |
||
| 427 | public function delete_id_from_meta() { |
||
| 428 | delete_user_option( $this->get_user_id(), '_stripe_customer_id', false ); |
||
| 429 | } |
||
| 430 | |||
| 431 | /** |
||
| 432 | * Recreates the customer for this user. |
||
| 433 | * |
||
| 434 | * @return string ID of the new Customer object. |
||
| 435 | */ |
||
| 436 | private function recreate_customer() { |
||
| 437 | $this->delete_id_from_meta(); |
||
| 438 | return $this->create_customer(); |
||
| 439 | } |
||
| 440 | } |
||
| 441 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.