BraintreeForm::updateSubscription()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
/**
4
 * @author Anton Tuyakhov <[email protected]>
5
 */
6
7
namespace tuyakhov\braintree;
8
9
use Braintree\Customer;
10
use Braintree\Error\ValidationErrorCollection;
11
use Braintree\MerchantAccount;
12
use Braintree\Plan;
13
use Braintree\ResourceCollection;
14
use Braintree\Result\Error;
15
use Braintree\Subscription;
16
use Braintree\WebhookNotification;
17
use Yii;
18
use yii\base\Model;
19
20
class BraintreeForm extends Model
21
{
22
    public $amount;
23
    public $orderId;
24
    public $paymentMethodToken;
25
    public $planId;
26
27
    public $creditCard_number;
28
    public $creditCard_cvv;
29
    public $creditCard_expirationMonth;
30
    public $creditCard_expirationYear;
31
    public $creditCard_expirationDate;
32
    public $creditCard_cardholderName;
33
34
    public $customer_firstName;
35
    public $customer_lastName;
36
    public $customer_company;
37
    public $customer_phone;
38
    public $customer_fax;
39
    public $customer_website;
40
    public $customer_email;
41
42
    public $billing_firstName;
43
    public $billing_lastName;
44
    public $billing_company;
45
    public $billing_streetAddress;
46
    public $billing_extendedAddress;
47
    public $billing_locality;
48
    public $billing_region;
49
    public $billing_postalCode;
50
    public $billing_countryCodeAlpha2;
51
52
    public $shipping_firstName;
53
    public $shipping_lastName;
54
    public $shipping_company;
55
    public $shipping_streetAddress;
56
    public $shipping_extendedAddress;
57
    public $shipping_locality;
58
    public $shipping_region;
59
    public $shipping_postalCode;
60
    public $shipping_countryCodeAlpha2;
61
62
    public $customerId;
63
64
    /**
65
     * @var Error last error from Braintree
66
     */
67
    public $lastError;
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 5
    public function rules(): array
73
    {
74
        return [
75 5
            [['amount'], 'number'],
76
77
            [['customer_email'], 'email'],
78
79
            [
80
                ['customerId'],
81
                'required',
82
                'on' => 'address',
83
            ],
84
            [
85
                ['creditCard_number', 'creditCard_expirationDate', 'creditCard_cvv', 'customerId'],
86
                'required',
87
                'on' => 'creditCard',
88
            ],
89
            [
90
                ['customer_firstName', 'customer_lastName'],
91
                'required',
92
                'on' => 'customer',
93
            ],
94
            [
95
                ['amount', 'creditCard_number', 'creditCard_expirationDate', 'creditCard_cvv'],
96
                'required',
97
                'on' => 'sale',
98
            ],
99
            [
100
                ['amount', 'paymentMethodToken'],
101
                'required',
102
                'on' => 'saleFromVault',
103
            ],
104
105
            [
106
                [
107
                    'creditCard_expirationMonth',
108
                    'creditCard_expirationYear',
109
                    'creditCard_expirationDate',
110
                    'creditCard_cardholderName',
111
                    'customer_firstName',
112
                    'customer_lastName',
113
                    'customer_company',
114
                    'customer_phone',
115
                    'customer_fax',
116
                    'customer_website',
117
                    'billing_firstName',
118
                    'billing_lastName',
119
                    'billing_company',
120
                    'billing_streetAddress',
121
                    'billing_extendedAddress',
122
                    'billing_locality',
123
                    'billing_region',
124
                    'billing_postalCode',
125
                    'billing_countryCodeAlpha2',
126
                    'shipping_firstName',
127
                    'shipping_lastName',
128
                    'shipping_company',
129
                    'shipping_streetAddress',
130
                    'shipping_extendedAddress',
131
                    'shipping_locality',
132
                    'shipping_region',
133
                    'shipping_postalCode',
134
                    'shipping_countryCodeAlpha2',
135
                ],
136
                'safe',
137
            ],
138
        ];
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function attributeLabels(): array
145
    {
146
        return [
147
            'amount' => 'Amount',
148
            'orderId' => 'Order ID',
149
            'creditCard_number' => 'Credit Card Number',
150
            'creditCard_cvv' => 'Security Code',
151
            'creditCard_expirationMonth' => 'Expiration Month (MM)',
152
            'creditCard_expirationYear' => 'Expiration Year (YYYY)',
153
            'creditCard_expirationDate' => 'Expiration Date (MM/YYYY)',
154
            'creditCard_cardholderName' => 'Name on Card',
155
            'customer_firstName' => 'First Name',
156
            'customer_lastName' => 'Last Name',
157
            'customer_company' => 'Company Name',
158
            'customer_phone' => 'Phone Number',
159
            'customer_fax' => 'Fax Number',
160
            'customer_website' => 'Website',
161
            'customer_email' => 'Email',
162
            'billing_firstName' => 'First Name',
163
            'billing_lastName' => 'Last Name',
164
            'billing_company' => 'Company Name',
165
            'billing_streetAddress' => 'Street Address',
166
            'billing_extendedAddress' => 'Extended Address',
167
            'billing_locality' => 'City/Locality',
168
            'billing_region' => 'State/Region',
169
            'billing_postalCode' => 'ZIP/Postal Code',
170
            'billing_countryCodeAlpha2' => 'Country',
171
            'shipping_firstName' => 'First Name',
172
            'shipping_lastName' => 'Last Name',
173
            'shipping_company' => 'Company Name',
174
            'shipping_streetAddress' => 'Street Address',
175
            'shipping_extendedAddress' => 'Extended Address',
176
            'shipping_locality' => 'City/Locality',
177
            'shipping_region' => 'State/Region',
178
            'shipping_postalCode' => 'ZIP/Postal Code',
179
            'shipping_countryCodeAlpha2' => 'Country',
180
        ];
181
    }
182
183
    /**
184
     * @return null|Braintree
185
     */
186 8
    public static function getBraintree(): ?Braintree
187
    {
188 8
        return Yii::$app->get('braintree');
0 ignored issues
show
Bug Best Practice introduced by
The expression return Yii::app->get('braintree') could return the type mixed which is incompatible with the type-hinted return null|tuyakhov\braintree\Braintree. Consider adding an additional type-check to rule them out.
Loading history...
189
    }
190
191 5
    public function getValuesFromAttributes(): array
192
    {
193 5
        $values = [];
194 5
        foreach ($this->attributes as $key => $val) {
195 5
            if (!is_object($val) && !is_null($val) && strlen($val) > 0) {
196 5
                if (strpos($key, '_') === false) {
197 4
                    $values[$key] = $val;
198
                } else {
199 4
                    $pieces = explode('_', $key);
200 4
                    $values[$pieces[0]][$pieces[1]] = $val;
201
                }
202
            }
203
        }
204
205 5
        return $values;
206
    }
207
208 5
    public function send()
209
    {
210 5
        static::getBraintree()->setOptions($this->getValuesFromAttributes());
211 5
        $scenario = $this->getScenario();
212
213 5
        return $this->$scenario();
214
    }
215
216
    /**
217
     * Methods "sale" and "saleFromVault" are the same and differ only when called through method "send"
218
     * because of applied scenario.
219
     * @return false|array
220
     * @see saleFromVault
221
     * @see send
222
     */
223 2
    public function sale()
224
    {
225 2
        return $this->saleInternal();
226
    }
227
228
    /**
229
     * Methods "sale" and "saleFromVault" are the same and differ only when called through method "send"
230
     * because of applied scenario.
231
     * @return false|array
232
     * @see sale
233
     * @see send
234
     */
235 1
    public function saleFromVault()
236
    {
237 1
        return $this->saleInternal();
238
    }
239
240
    /**
241
     * @return false|array
242
     */
243 3
    protected function saleInternal()
244
    {
245 3
        $result = static::getBraintree()->sale();
246 3
        if ($result['status'] === false) {
247 1
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
It seems like $result['result'] can also be of type Braintree\Result\Successful; however, parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse() does only seem to accept Braintree\Result\Error, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

247
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
248
249 1
            return false;
250
        }
251
252 2
        return $result;
253
    }
254
255
    /**
256
     * @return false|array
257
     */
258
    public function saleWithServiceFee($merchantAccountId, $amount, $paymentMethodNonce, $serviceFeeAmount)
259
    {
260
        $result = static::getBraintree()->saleWithServiceFee(
261
            $merchantAccountId,
262
            $amount,
263
            $paymentMethodNonce,
264
            $serviceFeeAmount
265
        );
266
        if ($result['status'] === false) {
267
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
It seems like $result['result'] can also be of type Braintree\Result\Successful; however, parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse() does only seem to accept Braintree\Result\Error, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

267
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
268
269
            return false;
270
        }
271
272
        return $result;
273
    }
274
275
    public function saleWithPaymentNonce($amount, $paymentMethodNonce)
276
    {
277
        $result = static::getBraintree()->saleWithPaymentNonce($amount, $paymentMethodNonce);
278
        if ($result['status'] === false) {
279
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
It seems like $result['result'] can also be of type Braintree\Result\Successful; however, parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse() does only seem to accept Braintree\Result\Error, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

279
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
280
281
            return false;
282
        }
283
284
        return $result;
285
    }
286
287
    public function createPaymentMethod($customerId, $paymentNonce, $makeDefault = false, $options = []): array
288
    {
289
        if ($makeDefault) {
290
            $options = array_merge($options, ['makeDefault' => $makeDefault]);
291
        }
292
        $result = static::getBraintree()->setOptions(
293
            [
294
                'paymentMethod' => [
295
                    'customerId' => $customerId,
296
                    'paymentMethodNonce' => $paymentNonce,
297
                    'options' => $options,
298
                ],
299
            ]
300
        )->savePaymentMethod();
301
        if ($result['status'] === false) {
302
            $this->addErrorFromResponse($result['result']);
303
        }
304
305
        return $result;
306
    }
307
308
    /**
309
     * @param string $paymentMethodToken
310
     * @param array $params example:
311
     * [
312
     *     'billingAddress' => [
313
     *         'firstName' => 'Drew',
314
     *         'lastName' => 'Smith',
315
     *         'company' => 'Smith Co.',
316
     *         'streetAddress' => '1 E Main St',
317
     *         'region' => 'IL',
318
     *         'postalCode' => '60622',
319
     *     ],
320
     * ]
321
     * @param array $options example:
322
     * [
323
     *     'makeDefault' => true,
324
     *     'verifyCard' => true,
325
     * ]
326
     * @return array
327
     */
328
    public function updatePaymentMethod(string $paymentMethodToken, array $params = [], array $options = []): array
329
    {
330
        $paymentMethodOptions = array_merge($params, ['options' => $options]);
331
        $result = static::getBraintree()->setOptions(
332
            [
333
                'paymentMethodToken' => $paymentMethodToken,
334
                'paymentMethod' => $paymentMethodOptions,
335
            ]
336
        )->updatePaymentMethod();
337
        if ($result['status'] === false) {
338
            $this->addErrorFromResponse($result['result']);
339
        }
340
341
        return $result;
342
    }
343
344
    public function deletePaymentMethod($paymentMethodToken): array
345
    {
346
        $result = static::getBraintree()->setOptions(
347
            [
348
                'paymentMethodToken' => $paymentMethodToken,
349
            ]
350
        )->deletePaymentMethod();
351
        if ($result['status'] === false) {
352
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
$result['result'] of type Braintree\Result\Successful is incompatible with the type Braintree\Result\Error expected by parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

352
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
353
        }
354
355
        return $result;
356
    }
357
358 1
    public function customer()
359
    {
360 1
        $result = static::getBraintree()->saveCustomer();
361 1
        if ($result['status'] === false) {
362
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
It seems like $result['result'] can also be of type Braintree\Result\Successful; however, parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse() does only seem to accept Braintree\Result\Error, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

362
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
363
364
            return false;
365
        }
366
367 1
        return $result;
368
    }
369
370
    /**
371
     * @param string $paymentNonce
372
     * @param array $options credit card options, example:
373
     * [
374
     *     'verifyCard' => true,
375
     * ]
376
     * @return array
377
     */
378
    public function createCustomerWithPaymentMethod(string $paymentNonce, array $options = []): array
379
    {
380
        $result = static::getBraintree()->setOptions(
381
            [
382
                'customer' => [
383
                    'firstName' => $this->customer_firstName,
384
                    'lastName' => $this->customer_lastName,
385
                    'paymentMethodNonce' => $paymentNonce,
386
                    'creditCard' => ['options' => $options],
387
                ],
388
            ]
389
        )->saveCustomer();
390
391
        if ($result['status']) {
392
            $result['customerId'] = $result['result']->customer->id;
393
            $result['paymentMethodToken'] = $result['result']->customer->paymentMethods[0]->token;
394
        }
395
396
        return $result;
397
    }
398
399 1
    public function creditCard()
400
    {
401 1
        $result = static::getBraintree()->saveCreditCard();
402 1
        if ($result['status'] === false) {
403
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
It seems like $result['result'] can also be of type Braintree\Result\Successful; however, parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse() does only seem to accept Braintree\Result\Error, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

403
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
404
405
            return false;
406
        }
407
408 1
        return $result;
409
    }
410
411
    public function address()
412
    {
413
        $result = static::getBraintree()->saveAddress();
414
        if ($result['status'] === false) {
415
            $this->addErrorFromResponse($result['result']);
0 ignored issues
show
Bug introduced by
$result['result'] of type Braintree\Address is incompatible with the type Braintree\Result\Error expected by parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

415
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $result['result']);
Loading history...
416
417
            return false;
418
        }
419
420
        return $result;
421
    }
422
423
    /**
424
     * @param bool $allowCaching whether to allow caching the result of retrieving of data from Braintree;
425
     * when this parameter is true (default), if data was retrieved before,
426
     * result will be directly returned when calling this method;
427
     * if this parameter is false, this method will always perform request to Braintree to obtain the up-to-date data;
428
     * note that this caching is effective only within the same HTTP request
429
     * @return Plan[]
430
     */
431 1
    public static function getAllPlans(bool $allowCaching = true): array
432
    {
433 1
        return static::getBraintree()->getAllPlans($allowCaching);
434
    }
435
436
    /**
437
     * @param bool $allowCaching whether to allow caching the result of retrieving of data from Braintree;
438
     * when this parameter is true (default), if data was retrieved before,
439
     * result will be directly returned when calling this method;
440
     * if this parameter is false, this method will always perform request to Braintree to obtain the up-to-date data;
441
     * note that this caching is effective only within the same HTTP request
442
     * @return array
443
     */
444
    public static function getPlanIds(bool $allowCaching = true): array
445
    {
446
        return static::getBraintree()->getPlanIds($allowCaching);
447
    }
448
449
    /**
450
     * @param string $planId
451
     * @param bool $allowCaching whether to allow caching the result of retrieving of data from Braintree;
452
     * when this parameter is true (default), if data was retrieved before,
453
     * result will be directly returned when calling this method;
454
     * if this parameter is false, this method will always perform request to Braintree to obtain the up-to-date data;
455
     * note that this caching is effective only within the same HTTP request
456
     * @return null|Plan
457
     */
458
    public static function getPlanById(string $planId, bool $allowCaching = true): ?Plan
459
    {
460
        return static::getBraintree()->getPlanById($planId, $allowCaching);
461
    }
462
463
    public function searchTransaction($params = []): ResourceCollection
464
    {
465
        return static::getBraintree()->searchTransaction($params);
466
    }
467
468
    /**
469
     * @param string $merchantId
470
     * @return MerchantAccount
471
     */
472 1
    public function findMerchant(string $merchantId): MerchantAccount
473
    {
474 1
        return static::getBraintree()->findMerchant($merchantId);
475
    }
476
477
    /**
478
     * @param string $customerId
479
     * @return Customer
480
     */
481
    public function findCustomer(string $customerId): Customer
482
    {
483
        return static::getBraintree()->findCustomer($customerId);
484
    }
485
486
    /**
487
     * @param string $subscriptionId
488
     * @return Subscription
489
     */
490
    public function findSubscription(string $subscriptionId): Subscription
491
    {
492
        return static::getBraintree()->findSubscription($subscriptionId);
493
    }
494
495 1
    public function searchSubscription($params = []): ResourceCollection
496
    {
497 1
        return static::getBraintree()->searchSubscription($params);
498
    }
499
500
    /**
501
     * @param array $params
502
     * @return array
503
     */
504
    public function createSubscription(array $params = []): array
505
    {
506
        $params = array_merge(['paymentMethodToken' => $this->paymentMethodToken, 'planId' => $this->planId], $params);
507
        $result = static::getBraintree()->createSubscription($params);
508
509
        return ['status' => $result->success, 'result' => $result];
510
    }
511
512
    /**
513
     * Update subscription.
514
     * @param string $subscriptionId
515
     * @param array $params
516
     * @return array
517
     */
518
    public function updateSubscription(string $subscriptionId, array $params): array
519
    {
520
        $result = static::getBraintree()->updateSubscription($subscriptionId, $params);
521
522
        return ['status' => $result->success, 'result' => $result];
523
    }
524
525
    /**
526
     * Cancel subscription.
527
     * @param string $subscriptionId
528
     * @return array
529
     */
530
    public function cancelSubscription(string $subscriptionId): array
531
    {
532
        $result = static::getBraintree()->cancelSubscription($subscriptionId);
533
534
        return ['status' => $result->success, 'result' => $result];
535
    }
536
537
    public function retryChargeSubscription(string $subscriptionId, $amount)
538
    {
539
        $retryResult = static::getBraintree()->retryChargeSubscription($subscriptionId, $amount);
540
        if (!$retryResult->success) {
541
            $this->addErrorFromResponse($retryResult);
0 ignored issues
show
Bug introduced by
It seems like $retryResult can also be of type Braintree\Result\Successful; however, parameter $result of tuyakhov\braintree\Brain...:addErrorFromResponse() does only seem to accept Braintree\Result\Error, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

541
            $this->addErrorFromResponse(/** @scrutinizer ignore-type */ $retryResult);
Loading history...
542
543
            return false;
544
        }
545
546
        return $retryResult;
547
    }
548
549
    public function parseWebhookNotification(string $signature, $payload): WebhookNotification
550
    {
551
        return static::getBraintree()->parseWebhookNotification($signature, $payload);
552
    }
553
554
    /**
555
     * This method adds an error from the Braintree response to the form.
556
     * @param Error $result
557
     */
558 1
    public function addErrorFromResponse(Error $result)
559
    {
560 1
        $this->lastError = $result;
561 1
        $errors = $result->errors;
562 1
        foreach ($errors->shallowAll() as $error) {
563
            $this->addError('creditCard_number', $error->message);
564
        }
565
        /** @var ValidationErrorCollection $transactionErrors */
566 1
        $transactionErrors = $errors->forKey('transaction');
567 1
        if (isset($transactionErrors)) {
568 1
            foreach ($transactionErrors->shallowAll() as $error) {
569 1
                $this->addError('creditCard_number', $error->message);
570
            }
571 1
            $values = $this->getValuesFromAttributes();
572 1
            foreach (array_keys($values) as $key) {
573
                /** @var ValidationErrorCollection $keyErrors */
574 1
                $keyErrors = $transactionErrors->forKey($key);
575 1
                if (isset($keyErrors)) {
576 1
                    foreach ($keyErrors->shallowAll() as $error) {
577 1
                        $this->addError($key . '_' . $error->attribute, $error->message);
578
                    }
579
                }
580
            }
581
        }
582 1
        if (!$this->hasErrors()) {
583
            $this->addError('creditCard_number', $result->message);
584
        }
585 1
    }
586
}
587