Completed
Push — master ( 6d8164...bf7249 )
by Roy
03:26
created

WC_Stripe_Customer::create_customer()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 26
nc 9
nop 1
dl 0
loc 42
rs 8.439
c 0
b 0
f 0
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( get_user_meta( $user_id, '_stripe_customer_id', true ) );
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]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
53
	 */
54
	public function set_id( $id ) {
55
		$this->id = wc_clean( $id );
56
	}
57
58
	/**
59
	 * User ID in WordPress.
60
	 * @return int
61
	 */
62
	public function get_user_id() {
63
		return absint( $this->user_id );
64
	}
65
66
	/**
67
	 * Set User ID used by WordPress.
68
	 * @param int $user_id
69
	 */
70
	public function set_user_id( $user_id ) {
71
		$this->user_id = absint( $user_id );
72
	}
73
74
	/**
75
	 * Get user object.
76
	 * @return WP_User
77
	 */
78
	protected function get_user() {
79
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
80
	}
81
82
	/**
83
	 * Store data from the Stripe API about this customer
84
	 */
85
	public function set_customer_data( $data ) {
86
		$this->customer_data = $data;
87
	}
88
89
	/**
90
	 * Get data from the Stripe API about this customer
91
	 */
92
	public function get_customer_data() {
93
		$this->customer_data = get_transient( 'stripe_customer_' . $this->get_id() );
94
95
		if ( empty( $this->customer_data ) && $this->get_id() && false === $this->customer_data ) {
96
			$response = WC_Stripe_API::request( array(), 'customers/' . $this->get_id() );
97
98
			if ( empty( $response->error ) ) {
99
				$this->set_customer_data( $response );
100
				set_transient( 'stripe_customer_' . $this->get_id(), $response, HOUR_IN_SECONDS * 48 );
101
			}
102
		}
103
104
		return $this->customer_data;
105
	}
106
107
	/**
108
	 * Get default card/source
109
	 * @return string
110
	 */
111
	public function get_default_source() {
112
		$data   = $this->get_customer_data();
113
		$source = '';
114
115
		if ( $data ) {
116
			$source = $data->default_source;
117
		}
118
119
		return $source;
120
	}
121
122
	/**
123
	 * Create a customer via API.
124
	 * @param array $args
125
	 * @return WP_Error|int
126
	 */
127
	public function create_customer( $args = array() ) {
128
		$billing_email = filter_var( $_POST['billing_email'], FILTER_SANITIZE_EMAIL );
129
		$user = $this->get_user();
130
131
		if ( $user ) {
132
			$billing_first_name = get_user_meta( $user->ID, 'billing_first_name', true );
133
			$billing_last_name  = get_user_meta( $user->ID, 'billing_last_name', true );
134
135
			$defaults = array(
136
				'email'       => $user->user_email,
137
				'description' => $billing_first_name . ' ' . $billing_last_name,
138
			);
139
		} else {
140
			$defaults = array(
141
				'email'       => ! empty( $billing_email ) ? $billing_email : '',
142
				'description' => '',
143
			);
144
		}
145
146
		$metadata = array();
147
148
		$defaults['metadata'] = apply_filters( 'wc_stripe_customer_metadata', $metadata, $user );
149
150
		$args     = wp_parse_args( $args, $defaults );
151
		$response = WC_Stripe_API::request( apply_filters( 'wc_stripe_create_customer_args', $args ), 'customers' );
152
153
		if ( ! empty( $response->error ) ) {
154
			throw new Exception( $response->error->message );
155
		}
156
157
		$this->set_id( $response->id );
158
		$this->clear_cache();
159
		$this->set_customer_data( $response );
160
161
		if ( $this->get_user_id() ) {
162
			update_user_meta( $this->get_user_id(), '_stripe_customer_id', $response->id );
163
		}
164
165
		do_action( 'woocommerce_stripe_add_customer', $args, $response );
166
167
		return $response->id;
168
	}
169
170
	/**
171
	 * Add a source for this stripe customer.
172
	 * @param string $source_id
173
	 * @param bool $retry
174
	 * @return WP_Error|int
175
	 */
176
	public function add_source( $source_id, $retry = true ) {
0 ignored issues
show
Unused Code introduced by
The parameter $retry is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
177
		if ( ! $this->get_id() ) {
178
			$this->create_customer();
179
		}
180
181
		$response = WC_Stripe_API::request( array(
0 ignored issues
show
Bug Compatibility introduced by
The expression \WC_Stripe_API::request(...get_id() . '/sources'); of type array|WP_Error adds the type array to the return on line 194 which is incompatible with the return type documented by WC_Stripe_Customer::add_source of type WP_Error|integer.
Loading history...
182
			'source' => $source_id,
183
		), 'customers/' . $this->get_id() . '/sources' );
184
185
		if ( ! empty( $response->error ) ) {
186
			// It is possible the WC user once was linked to a customer on Stripe
187
			// but no longer exists. Instead of failing, lets try to create a
188
			// new customer.
189
			if ( preg_match( '/No such customer/i', $response->error->message ) ) {
190
				delete_user_meta( $this->get_user_id(), '_stripe_customer_id' );
191
				$this->create_customer();
192
				return $this->add_source( $source_id, false );
193
			} else {
194
				return $response;
195
			}
196
		} elseif ( empty( $response->id ) ) {
197
			return new WP_Error( 'error', __( 'Unable to add payment source.', 'woocommerce-gateway-stripe' ) );
198
		}
199
200
		// Add token to WooCommerce.
201
		if ( $this->get_user_id() && class_exists( 'WC_Payment_Token_CC' ) ) {
202
			if ( ! empty( $response->type ) ) {
203
				switch ( $response->type ) {
204
					case 'alipay':
205
						break;
206
					case 'sepa_debit':
207
						$wc_token = new WC_Payment_Token_SEPA();
208
						$wc_token->set_token( $response->id );
209
						$wc_token->set_gateway_id( 'stripe_sepa' );
210
						$wc_token->set_last4( $response->sepa_debit->last4 );
211
						break;
212
					default:
213
						if ( 'source' === $response->object && 'card' === $response->type ) {
214
							$wc_token = new WC_Payment_Token_CC();
215
							$wc_token->set_token( $response->id );
216
							$wc_token->set_gateway_id( 'stripe' );
217
							$wc_token->set_card_type( strtolower( $response->card->brand ) );
218
							$wc_token->set_last4( $response->card->last4 );
219
							$wc_token->set_expiry_month( $response->card->exp_month );
220
							$wc_token->set_expiry_year( $response->card->exp_year );
221
						}
222
						break;
223
				}
224
			} else {
225
				// Legacy.
226
				$wc_token = new WC_Payment_Token_CC();
227
				$wc_token->set_token( $response->id );
228
				$wc_token->set_gateway_id( 'stripe' );
229
				$wc_token->set_card_type( strtolower( $response->brand ) );
230
				$wc_token->set_last4( $response->last4 );
231
				$wc_token->set_expiry_month( $response->exp_month );
232
				$wc_token->set_expiry_year( $response->exp_year );
233
			}
234
235
			$wc_token->set_user_id( $this->get_user_id() );
0 ignored issues
show
Bug introduced by
The variable $wc_token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
236
			$wc_token->save();
237
		}
238
239
		$this->clear_cache();
240
241
		do_action( 'woocommerce_stripe_add_source', $this->get_id(), $wc_token, $response, $source_id );
242
243
		return $response->id;
244
	}
245
246
	/**
247
	 * Get a customers saved sources using their Stripe ID. Cached.
248
	 *
249
	 * @param  string $customer_id
0 ignored issues
show
Bug introduced by
There is no parameter named $customer_id. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
250
	 * @return array
251
	 */
252
	public function get_sources() {
253
		$sources = get_transient( 'stripe_sources_' . $this->get_id() );
254
255
		if ( false === $sources ) {
256
			$response = WC_Stripe_API::request( array(
257
				'limit'       => 100,
258
			), 'customers/' . $this->get_id() . '/sources', 'GET' );
259
260
			if ( ! empty( $response->error ) ) {
261
				return array();
262
			}
263
264
			if ( is_array( $response->data ) ) {
265
				$sources = $response->data;
266
			}
267
268
			set_transient( 'stripe_sources_' . $this->get_id(), $sources, HOUR_IN_SECONDS * 24 );
269
		}
270
271
		return empty( $sources ) ? array() : $sources;
272
	}
273
274
	/**
275
	 * Delete a source from stripe.
276
	 * @param string $source_id
277
	 */
278 View Code Duplication
	public function delete_source( $source_id ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
279
		$response = WC_Stripe_API::request( array(), 'customers/' . $this->get_id() . '/sources/' . sanitize_text_field( $source_id ), 'DELETE' );
280
281
		$this->clear_cache();
282
283
		if ( empty( $response->error ) ) {
284
			do_action( 'wc_stripe_delete_source', $this->get_id(), $response );
285
286
			return true;
287
		}
288
289
		return false;
290
	}
291
292
	/**
293
	 * Set default source in Stripe
294
	 * @param string $source_id
295
	 */
296 View Code Duplication
	public function set_default_source( $source_id ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
297
		$response = WC_Stripe_API::request( array(
298
			'default_source' => sanitize_text_field( $source_id ),
299
		), 'customers/' . $this->get_id(), 'POST' );
300
301
		$this->clear_cache();
302
303
		if ( empty( $response->error ) ) {
304
			do_action( 'wc_stripe_set_default_source', $this->get_id(), $response );
305
306
			return true;
307
		}
308
309
		return false;
310
	}
311
312
	/**
313
	 * Deletes caches for this users cards.
314
	 */
315
	public function clear_cache() {
316
		delete_transient( 'stripe_sources_' . $this->get_id() );
317
		delete_transient( 'stripe_customer_' . $this->get_id() );
318
		$this->customer_data = array();
319
	}
320
}
321