Test Failed
Push — travis/4010 ( cd7197 )
by Ravinder
12:19
created

Give_Stripe_Customer::get_id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Give - Stripe Customer
4
 *
5
 * @package    Give
6
 * @subpackage Stripe Core
7
 * @copyright  Copyright (c) 2019, GiveWP
8
 * @license    https://opensource.org/licenses/gpl-license GNU Public License
9
 */
10
11
// Exit if accessed directly.
12
if ( ! defined( 'ABSPATH' ) ) {
13
	exit;
14
}
15
16
/**
17
 * Class Give_Stripe_Customer.
18
 *
19
 * @since 2.5.0
20
 */
21
class Give_Stripe_Customer {
22
23
	/**
24
	 * Stripe Customer ID.
25
	 *
26
	 * @since  2.5.0
27
	 * @access private
28
	 *
29
	 * @var string
30
	 */
31
	private $id = '';
32
33
	/**
34
	 * Stripe Source ID.
35
	 *
36
	 * @since  2.5.0
37
	 * @access private
38
	 *
39
	 * @var string
40
	 */
41
	private $source_id = '';
42
43
	/**
44
	 * Donor Email.
45
	 *
46
	 * @since  2.5.0
47
	 * @access private
48
	 *
49
	 * @var string
50
	 */
51
	private $donor_email = '';
52
53
	/**
54
	 * Stripe Customer Data.
55
	 *
56
	 * @since  2.5.0
57
	 * @access private
58
	 *
59
	 * @var \Stripe\Customer
60
	 */
61
	public $customer_data = array();
62
63
	/**
64
	 * Stripe Gateway Object.
65
	 *
66
	 * @since  2.5.0
67
	 * @access public
68
	 *
69
	 * @var array|Give_Stripe_Gateway
70
	 */
71
	public $stripe_gateway = array();
72
73
	/**
74
	 * Attached Stripe Source to Customer.
75
	 *
76
	 * @since  2.5.0
77
	 * @access public
78
	 *
79
	 * @var array|\Stripe\Source
80
	 */
81
	public $attached_source = array();
82
83
	/**
84
	 * Check for card existence for customer.
85
	 *
86
	 * @since  2.5.0
87
	 * @access public
88
	 *
89
	 * @var bool
90
	 */
91
	public $is_card_exists = false;
92
93
	/**
94
	 * Give_Stripe_Customer constructor.
95
	 *
96
	 * @param string $email     Donor Email.
97
	 * @param string $source_id Stripe Source ID.
98
	 *
99
	 * @since  2.5.0
100
	 * @access public
101
	 */
102
	public function __construct( $email, $source_id = '' ) {
103
		$this->donor_email    = $email;
104
		$this->source_id      = $source_id;
105
		$this->stripe_gateway = new Give_Stripe_Gateway();
106
		$this->set_id( give_stripe_get_customer_id( $email ) );
107
		$this->get_or_create_customer();
108
	}
109
110
	/**
111
	 * Get Stripe customer ID.
112
	 *
113
	 * @since  2.5.0
114
	 * @access public
115
	 *
116
	 * @return string
117
	 */
118
	public function get_id() {
119
		return $this->id;
120
	}
121
122
	/**
123
	 * Set Stripe customer ID.
124
	 *
125
	 * @param string $id Stripe Customer ID.
126
	 *
127
	 * @since  2.5.0
128
	 * @access public
129
	 */
130
	public function set_id( $id ) {
131
		$this->id = give_clean( $id );
0 ignored issues
show
Documentation Bug introduced by
It seems like give_clean($id) can also be of type array. However, the property $id is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
132
	}
133
134
	/**
135
	 * Store data from the Stripe API about this customer
136
	 *
137
	 * @param /Stripe/Customer $data Stripe Customer Object.
0 ignored issues
show
Documentation introduced by
The doc-type /Stripe/Customer could not be parsed: Unknown type name "/Stripe/Customer" 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...
138
	 *
139
	 * @since  2.5.0
140
	 * @access public
141
	 */
142
	public function set_customer_data( $data ) {
143
		$this->customer_data = $data;
144
	}
145
146
	/**
147
	 * Get the Stripe customer object. If not found, create the customer with Stripe's API.
148
	 * Save the customer ID appropriately in the database.
149
	 *
150
	 * @since  2.5.0
151
	 * @access public
152
	 *
153
	 * @return bool|\Stripe\Customer
154
	 */
155
	public function get_or_create_customer() {
156
157
		$customer = false;
158
159
		// No customer ID found, look up based on the email.
160
		$stripe_customer_id = give_stripe_get_customer_id( $this->donor_email );
161
162
		// There is a customer ID. Check if it is active still in Stripe.
163
		if ( ! empty( $stripe_customer_id ) ) {
164
165
			// Set Application Info.
166
			give_stripe_set_app_info();
167
168
			try {
169
170
				// Retrieve the customer to ensure the customer has not been deleted.
171
				$customer = \Stripe\Customer::retrieve( $stripe_customer_id, give_stripe_get_connected_account_options() );
172
173
				if ( isset( $customer->deleted ) && $customer->deleted ) {
174
175
					// This customer was deleted.
176
					$customer = false;
177
				}
178
			} catch ( \Stripe\Error\InvalidRequest $e ) {
179
180
				$error_object = $e->getJsonBody();
181
182
				if ( $this->is_no_such_customer_error( $error_object['error'] ) ) {
183
					$customer = $this->create_customer();
184
				} else {
185
186
					// Record Log.
187
					give_stripe_record_log(
188
						__( 'Stripe - Customer Creation Error', 'give-stripe' ),
189
						$e->getMessage()
190
					);
191
				}
192
			} catch ( Exception $e ) {
193
				$customer = false;
194
			}
195
		}
196
197
		// Create the Stripe customer if not present.
198
		if ( empty( $customer ) ) {
199
			$customer = $this->create_customer();
200
		}
201
202
		$this->set_id( $customer->id );
203
		$this->set_customer_data( $customer );
204
205
		// Attach source to customer.
206
		$this->attach_source();
207
208
		return $customer;
209
210
	}
211
212
	/**
213
	 * Create a Customer in Stripe.
214
	 *
215
	 * @since  2.5.0
216
	 * @access public
217
	 *
218
	 * @return bool|\Stripe\Customer
219
	 */
220
	public function create_customer() {
221
222
		$customer     = false;
223
		$post_data    = give_clean( $_POST ); // WPCS: input var ok, sanitization ok, CSRF ok.
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
224
		$payment_mode = ! empty( $post_data['give-gateway'] ) ? $post_data['give-gateway'] : '';
225
226
		// Set Application Info.
227
		give_stripe_set_app_info();
228
229
		try {
230
231
			$metadata = array(
232
				'first_name' => $post_data['give_first'],
233
				'last_name'  => $post_data['give_last'],
234
				'created_by' => $post_data['give-form-title'],
235
			);
236
237
			// Add address to customer metadata if present.
238
			if ( ! empty( $post_data['billing_country'] ) ) {
239
				$metadata['address_line1']   = isset( $post_data['card_address'] ) ? $post_data['card_address'] : '';
240
				$metadata['address_line2']   = isset( $post_data['card_address_2'] ) ? $post_data['card_address_2'] : '';
241
				$metadata['address_city']    = isset( $post_data['card_city'] ) ? $post_data['card_city'] : '';
242
				$metadata['address_state']   = isset( $post_data['card_state'] ) ? $post_data['card_state'] : '';
243
				$metadata['address_country'] = isset( $post_data['billing_country'] ) ? $post_data['billing_country'] : '';
244
				$metadata['address_zip']     = isset( $post_data['card_zip'] ) ? $post_data['card_zip'] : '';
245
			}
246
247
			/**
248
			 * This filter will be used to modify customer arguments based on the need.
249
			 *
250
			 * @param array $args List of customer arguments from Stripe.
251
			 *
252
			 * @since 2.5.0
253
			 */
254
			$args = apply_filters( 'give_stripe_customer_args', array(
255
				'description' => sprintf(
256
					/* translators: %s Site URL */
257
					__( 'Stripe Customer generated by GiveWP via %s', 'give-stripe' ),
258
					get_bloginfo( 'url' )
259
				),
260
				'email'       => $this->donor_email,
261
				'metadata'    => apply_filters( 'give_stripe_customer_metadata', $metadata, $post_data ),
262
			) );
263
264
			// Create a customer first so we can retrieve them later for future payments.
265
			$customer = \Stripe\Customer::create( $args, give_stripe_get_connected_account_options() );
266
267
		} catch ( \Stripe\Error\Base $e ) {
268
			// Record Log.
269
			give_stripe_record_log(
270
				__( 'Stripe - Customer Creation Error', 'give' ),
271
				$e->getMessage()
272
			);
273
274
		} catch ( Exception $e ) {
275
			give_record_gateway_error(
276
				__( 'Stripe Error', 'give' ),
277
				sprintf(
278
					/* translators: %s Exception Message Body */
279
					__( 'The Stripe Gateway returned an error while creating the customer. Details: %s', 'give-stripe' ),
280
					$e->getMessage()
281
				)
282
			);
283
			give_set_error( 'stripe_error', __( 'An occurred while processing the donation with the gateway. Please try your donation again.', 'give-stripe' ) );
284
			give_send_back_to_checkout( "?payment-mode={$payment_mode}&form_id={$post_data['post_data']['give-form-id']}" );
285
		} // End try().
286
287
		if ( ! empty( $customer->id ) ) {
288
			// Return obj.
289
			return $customer;
290
		} else {
291
			return false;
292
		}
293
294
	}
295
296
	/**
297
	 * This function is used to attach source to the customer, if not exists.
298
	 *
299
	 * @since  2.5.0
300
	 * @access public
301
	 *
302
	 * @return void
303
	 */
304
	public function attach_source() {
305
306
		if ( ! empty( $this->source_id ) && ! empty( $this->customer_data ) ) {
307
308
			$card        = '';
309
			$card_exists = false;
310
			$all_sources = $this->customer_data->sources->all();
311
312
			// Fetch the new card or source object to match with customer attached card fingerprint.
313
			if ( give_stripe_is_checkout_enabled() ) {
314
				$token_details = $this->stripe_gateway->get_token_details( $this->source_id );
315
				$new_card = $token_details->card;
316
			} elseif( 'stripe_ach' === give_clean( $_POST['give-gateway'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
317
				$token_details = $this->stripe_gateway->get_token_details( $this->source_id );
318
				$new_card = $token_details->bank_account;
319
			} else {
320
				$source_details = $this->stripe_gateway->get_source_details( $this->source_id );
321
				$new_card = $source_details->card;
322
			}
323
324
			// Check to ensure that new card is already attached with customer or not.
325
			if ( count( $all_sources->data ) > 0 ) {
326
				foreach ( $all_sources->data as $source_item ) {
327
328
					if (
329
						( $this->is_card( $source_item->id ) && $source_item->fingerprint === $new_card->fingerprint ) ||
330
						(
331
							$source_item->card->fingerprint === $new_card->fingerprint &&
332
							( $this->is_source( $source_item->id ) || $this->is_bank_account( $source_item->id ))
333
						)
334
					) {
335
336
						// Set the existing card as default source.
337
						$this->customer_data->default_source = $source_item->id;
338
						$this->customer_data->save();
339
						$card                 = $source_item;
340
						$card_exists          = true;
341
						$this->is_card_exists = true;
342
						break;
343
					}
344
				}
345
			}
346
347
			// Create the card, if none found above.
348
			if ( ! $card_exists ) {
349
				try {
350
351
					$card = $this->customer_data->sources->create( array(
352
						'source' => $this->source_id,
353
					) );
354
355
					$this->customer_data->default_source = $card->id;
356
					$this->customer_data->save();
357
358
				} catch ( \Stripe\Error\Base $e ) {
359
360
					Give_Stripe_Logger::log_error( $e, 'stripe' );
361
362
				} catch ( Exception $e ) {
363
					give_record_gateway_error(
364
						__( 'Stripe Error', 'give' ),
365
						sprintf(
366
							/* translators: %s Exception Message Body */
367
							__( 'The Stripe Gateway returned an error while creating the customer. Details: %s', 'give' ),
368
							$e->getMessage()
369
						)
370
					);
371
					give_set_error( 'stripe_error', __( 'An occurred while processing the donation with the gateway. Please try your donation again.', 'give' ) );
372
					give_send_back_to_checkout( '?payment-mode=stripe' );
373
				}
374
			}
375
376
			// Return Card Details, if exists.
377
			if ( ! empty( $card->id ) ) {
378
				$this->attached_source = $card;
379 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
380
381
				give_set_error( 'stripe_error', __( 'An error occurred while processing the donation. Please try again.', 'give' ) );
382
				give_record_gateway_error( __( 'Stripe Error', 'give' ), __( 'An error occurred retrieving or creating the ', 'give' ) );
383
				give_send_back_to_checkout( '?payment-mode=stripe' );
384
385
				$this->attached_source = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array|object<Stripe\Source> of property $attached_source.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
386
			}
387
		} // End if().
388
	}
389
390
	/**
391
	 * This function will check whether the error says no such customer.
392
	 *
393
	 * @param \Stripe\Error\InvalidRequest $error Invalid Request Error.
394
	 *
395
	 * @since  2.5.0
396
	 * @access public
397
	 *
398
	 * @return bool
399
	 */
400
	public function is_no_such_customer_error( $error ) {
401
		return (
402
			$error &&
403
			'invalid_request_error' === $error['type'] &&
404
			preg_match( '/No such customer/i', $error['message'] )
405
		);
406
	}
407
408
	/**
409
	 * This function will check whether the ID provided is Card ID?
410
	 *
411
	 * @param string $id Card ID.
412
	 *
413
	 * @since  2.5.0
414
	 * @access public
415
	 *
416
	 * @return bool
417
	 */
418
	public function is_card( $id ) {
419
		return (
420
			$id &&
421
			preg_match( '/card_/i', $id )
422
		);
423
	}
424
425
	/**
426
	 * This function will check whether the ID provided is Source ID?
427
	 *
428
	 * @param string $id Source ID.
429
	 *
430
	 * @since  2.5.0
431
	 * @access public
432
	 *
433
	 * @return bool
434
	 */
435
	public function is_source( $id ) {
436
		return (
437
			$id &&
438
			preg_match( '/src_/i', $id )
439
		);
440
	}
441
442
	/**
443
	 * This function will check whether the ID provided is Bank Account ID?
444
	 *
445
	 * @param string $id Source ID.
446
	 *
447
	 * @since  2.5.0
448
	 * @access public
449
	 *
450
	 * @return bool
451
	 */
452
	public function is_bank_account( $id ) {
453
		return (
454
			$id &&
455
			preg_match( '/ba_/i', $id )
456
		);
457
	}
458
459
}
460