dmitry-kulikov/yii2-braintree

View on GitHub
src/BraintreeForm.php

Summary

Maintainability
D
1 day
Test Coverage
<?php

/**
 * @author Anton Tuyakhov <atuyakhov@gmail.com>
 */

namespace tuyakhov\braintree;

use Braintree\Customer;
use Braintree\Error\ValidationErrorCollection;
use Braintree\MerchantAccount;
use Braintree\Plan;
use Braintree\ResourceCollection;
use Braintree\Result\Error;
use Braintree\Subscription;
use Braintree\WebhookNotification;
use Yii;
use yii\base\Model;

class BraintreeForm extends Model
{
    public $amount;
    public $orderId;
    public $paymentMethodToken;
    public $planId;

    public $creditCard_number;
    public $creditCard_cvv;
    public $creditCard_expirationMonth;
    public $creditCard_expirationYear;
    public $creditCard_expirationDate;
    public $creditCard_cardholderName;

    public $customer_firstName;
    public $customer_lastName;
    public $customer_company;
    public $customer_phone;
    public $customer_fax;
    public $customer_website;
    public $customer_email;

    public $billing_firstName;
    public $billing_lastName;
    public $billing_company;
    public $billing_streetAddress;
    public $billing_extendedAddress;
    public $billing_locality;
    public $billing_region;
    public $billing_postalCode;
    public $billing_countryCodeAlpha2;

    public $shipping_firstName;
    public $shipping_lastName;
    public $shipping_company;
    public $shipping_streetAddress;
    public $shipping_extendedAddress;
    public $shipping_locality;
    public $shipping_region;
    public $shipping_postalCode;
    public $shipping_countryCodeAlpha2;

    public $customerId;

    /**
     * @var Error last error from Braintree
     */
    public $lastError;

    /**
     * {@inheritdoc}
     */
    public function rules(): array
    {
        return [
            [['amount'], 'number'],

            [['customer_email'], 'email'],

            [
                ['customerId'],
                'required',
                'on' => 'address',
            ],
            [
                ['creditCard_number', 'creditCard_expirationDate', 'creditCard_cvv', 'customerId'],
                'required',
                'on' => 'creditCard',
            ],
            [
                ['customer_firstName', 'customer_lastName'],
                'required',
                'on' => 'customer',
            ],
            [
                ['amount', 'creditCard_number', 'creditCard_expirationDate', 'creditCard_cvv'],
                'required',
                'on' => 'sale',
            ],
            [
                ['amount', 'paymentMethodToken'],
                'required',
                'on' => 'saleFromVault',
            ],

            [
                [
                    'creditCard_expirationMonth',
                    'creditCard_expirationYear',
                    'creditCard_expirationDate',
                    'creditCard_cardholderName',
                    'customer_firstName',
                    'customer_lastName',
                    'customer_company',
                    'customer_phone',
                    'customer_fax',
                    'customer_website',
                    'billing_firstName',
                    'billing_lastName',
                    'billing_company',
                    'billing_streetAddress',
                    'billing_extendedAddress',
                    'billing_locality',
                    'billing_region',
                    'billing_postalCode',
                    'billing_countryCodeAlpha2',
                    'shipping_firstName',
                    'shipping_lastName',
                    'shipping_company',
                    'shipping_streetAddress',
                    'shipping_extendedAddress',
                    'shipping_locality',
                    'shipping_region',
                    'shipping_postalCode',
                    'shipping_countryCodeAlpha2',
                ],
                'safe',
            ],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels(): array
    {
        return [
            'amount' => 'Amount',
            'orderId' => 'Order ID',
            'creditCard_number' => 'Credit Card Number',
            'creditCard_cvv' => 'Security Code',
            'creditCard_expirationMonth' => 'Expiration Month (MM)',
            'creditCard_expirationYear' => 'Expiration Year (YYYY)',
            'creditCard_expirationDate' => 'Expiration Date (MM/YYYY)',
            'creditCard_cardholderName' => 'Name on Card',
            'customer_firstName' => 'First Name',
            'customer_lastName' => 'Last Name',
            'customer_company' => 'Company Name',
            'customer_phone' => 'Phone Number',
            'customer_fax' => 'Fax Number',
            'customer_website' => 'Website',
            'customer_email' => 'Email',
            'billing_firstName' => 'First Name',
            'billing_lastName' => 'Last Name',
            'billing_company' => 'Company Name',
            'billing_streetAddress' => 'Street Address',
            'billing_extendedAddress' => 'Extended Address',
            'billing_locality' => 'City/Locality',
            'billing_region' => 'State/Region',
            'billing_postalCode' => 'ZIP/Postal Code',
            'billing_countryCodeAlpha2' => 'Country',
            'shipping_firstName' => 'First Name',
            'shipping_lastName' => 'Last Name',
            'shipping_company' => 'Company Name',
            'shipping_streetAddress' => 'Street Address',
            'shipping_extendedAddress' => 'Extended Address',
            'shipping_locality' => 'City/Locality',
            'shipping_region' => 'State/Region',
            'shipping_postalCode' => 'ZIP/Postal Code',
            'shipping_countryCodeAlpha2' => 'Country',
        ];
    }

    /**
     * @return null|Braintree
     */
    public static function getBraintree(): ?Braintree
    {
        return Yii::$app->get('braintree');
    }

    public function getValuesFromAttributes(): array
    {
        $values = [];
        foreach ($this->attributes as $key => $val) {
            if (!is_object($val) && !is_null($val) && strlen($val) > 0) {
                if (strpos($key, '_') === false) {
                    $values[$key] = $val;
                } else {
                    $pieces = explode('_', $key);
                    $values[$pieces[0]][$pieces[1]] = $val;
                }
            }
        }

        return $values;
    }

    public function send()
    {
        static::getBraintree()->setOptions($this->getValuesFromAttributes());
        $scenario = $this->getScenario();

        return $this->$scenario();
    }

    /**
     * Methods "sale" and "saleFromVault" are the same and differ only when called through method "send"
     * because of applied scenario.
     * @return false|array
     * @see saleFromVault
     * @see send
     */
    public function sale()
    {
        return $this->saleInternal();
    }

    /**
     * Methods "sale" and "saleFromVault" are the same and differ only when called through method "send"
     * because of applied scenario.
     * @return false|array
     * @see sale
     * @see send
     */
    public function saleFromVault()
    {
        return $this->saleInternal();
    }

    /**
     * @return false|array
     */
    protected function saleInternal()
    {
        $result = static::getBraintree()->sale();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);

            return false;
        }

        return $result;
    }

    /**
     * @return false|array
     */
    public function saleWithServiceFee($merchantAccountId, $amount, $paymentMethodNonce, $serviceFeeAmount)
    {
        $result = static::getBraintree()->saleWithServiceFee(
            $merchantAccountId,
            $amount,
            $paymentMethodNonce,
            $serviceFeeAmount
        );
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);

            return false;
        }

        return $result;
    }

    public function saleWithPaymentNonce($amount, $paymentMethodNonce)
    {
        $result = static::getBraintree()->saleWithPaymentNonce($amount, $paymentMethodNonce);
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);

            return false;
        }

        return $result;
    }

    public function createPaymentMethod($customerId, $paymentNonce, $makeDefault = false, $options = []): array
    {
        if ($makeDefault) {
            $options = array_merge($options, ['makeDefault' => $makeDefault]);
        }
        $result = static::getBraintree()->setOptions(
            [
                'paymentMethod' => [
                    'customerId' => $customerId,
                    'paymentMethodNonce' => $paymentNonce,
                    'options' => $options,
                ],
            ]
        )->savePaymentMethod();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);
        }

        return $result;
    }

    /**
     * @param string $paymentMethodToken
     * @param array $params example:
     * [
     *     'billingAddress' => [
     *         'firstName' => 'Drew',
     *         'lastName' => 'Smith',
     *         'company' => 'Smith Co.',
     *         'streetAddress' => '1 E Main St',
     *         'region' => 'IL',
     *         'postalCode' => '60622',
     *     ],
     * ]
     * @param array $options example:
     * [
     *     'makeDefault' => true,
     *     'verifyCard' => true,
     * ]
     * @return array
     */
    public function updatePaymentMethod(string $paymentMethodToken, array $params = [], array $options = []): array
    {
        $paymentMethodOptions = array_merge($params, ['options' => $options]);
        $result = static::getBraintree()->setOptions(
            [
                'paymentMethodToken' => $paymentMethodToken,
                'paymentMethod' => $paymentMethodOptions,
            ]
        )->updatePaymentMethod();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);
        }

        return $result;
    }

    public function deletePaymentMethod($paymentMethodToken): array
    {
        $result = static::getBraintree()->setOptions(
            [
                'paymentMethodToken' => $paymentMethodToken,
            ]
        )->deletePaymentMethod();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);
        }

        return $result;
    }

    public function customer()
    {
        $result = static::getBraintree()->saveCustomer();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);

            return false;
        }

        return $result;
    }

    /**
     * @param string $paymentNonce
     * @param array $options credit card options, example:
     * [
     *     'verifyCard' => true,
     * ]
     * @return array
     */
    public function createCustomerWithPaymentMethod(string $paymentNonce, array $options = []): array
    {
        $result = static::getBraintree()->setOptions(
            [
                'customer' => [
                    'firstName' => $this->customer_firstName,
                    'lastName' => $this->customer_lastName,
                    'paymentMethodNonce' => $paymentNonce,
                    'creditCard' => ['options' => $options],
                ],
            ]
        )->saveCustomer();

        if ($result['status']) {
            $result['customerId'] = $result['result']->customer->id;
            $result['paymentMethodToken'] = $result['result']->customer->paymentMethods[0]->token;
        }

        return $result;
    }

    public function creditCard()
    {
        $result = static::getBraintree()->saveCreditCard();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);

            return false;
        }

        return $result;
    }

    public function address()
    {
        $result = static::getBraintree()->saveAddress();
        if ($result['status'] === false) {
            $this->addErrorFromResponse($result['result']);

            return false;
        }

        return $result;
    }

    /**
     * @param bool $allowCaching whether to allow caching the result of retrieving of data from Braintree;
     * when this parameter is true (default), if data was retrieved before,
     * result will be directly returned when calling this method;
     * if this parameter is false, this method will always perform request to Braintree to obtain the up-to-date data;
     * note that this caching is effective only within the same HTTP request
     * @return Plan[]
     */
    public static function getAllPlans(bool $allowCaching = true): array
    {
        return static::getBraintree()->getAllPlans($allowCaching);
    }

    /**
     * @param bool $allowCaching whether to allow caching the result of retrieving of data from Braintree;
     * when this parameter is true (default), if data was retrieved before,
     * result will be directly returned when calling this method;
     * if this parameter is false, this method will always perform request to Braintree to obtain the up-to-date data;
     * note that this caching is effective only within the same HTTP request
     * @return array
     */
    public static function getPlanIds(bool $allowCaching = true): array
    {
        return static::getBraintree()->getPlanIds($allowCaching);
    }

    /**
     * @param string $planId
     * @param bool $allowCaching whether to allow caching the result of retrieving of data from Braintree;
     * when this parameter is true (default), if data was retrieved before,
     * result will be directly returned when calling this method;
     * if this parameter is false, this method will always perform request to Braintree to obtain the up-to-date data;
     * note that this caching is effective only within the same HTTP request
     * @return null|Plan
     */
    public static function getPlanById(string $planId, bool $allowCaching = true): ?Plan
    {
        return static::getBraintree()->getPlanById($planId, $allowCaching);
    }

    public function searchTransaction($params = []): ResourceCollection
    {
        return static::getBraintree()->searchTransaction($params);
    }

    /**
     * @param string $merchantId
     * @return MerchantAccount
     */
    public function findMerchant(string $merchantId): MerchantAccount
    {
        return static::getBraintree()->findMerchant($merchantId);
    }

    /**
     * @param string $customerId
     * @return Customer
     */
    public function findCustomer(string $customerId): Customer
    {
        return static::getBraintree()->findCustomer($customerId);
    }

    /**
     * @param string $subscriptionId
     * @return Subscription
     */
    public function findSubscription(string $subscriptionId): Subscription
    {
        return static::getBraintree()->findSubscription($subscriptionId);
    }

    public function searchSubscription($params = []): ResourceCollection
    {
        return static::getBraintree()->searchSubscription($params);
    }

    /**
     * @param array $params
     * @return array
     */
    public function createSubscription(array $params = []): array
    {
        $params = array_merge(['paymentMethodToken' => $this->paymentMethodToken, 'planId' => $this->planId], $params);
        $result = static::getBraintree()->createSubscription($params);

        return ['status' => $result->success, 'result' => $result];
    }

    /**
     * Update subscription.
     * @param string $subscriptionId
     * @param array $params
     * @return array
     */
    public function updateSubscription(string $subscriptionId, array $params): array
    {
        $result = static::getBraintree()->updateSubscription($subscriptionId, $params);

        return ['status' => $result->success, 'result' => $result];
    }

    /**
     * Cancel subscription.
     * @param string $subscriptionId
     * @return array
     */
    public function cancelSubscription(string $subscriptionId): array
    {
        $result = static::getBraintree()->cancelSubscription($subscriptionId);

        return ['status' => $result->success, 'result' => $result];
    }

    public function retryChargeSubscription(string $subscriptionId, $amount)
    {
        $retryResult = static::getBraintree()->retryChargeSubscription($subscriptionId, $amount);
        if (!$retryResult->success) {
            $this->addErrorFromResponse($retryResult);

            return false;
        }

        return $retryResult;
    }

    public function parseWebhookNotification(string $signature, $payload): WebhookNotification
    {
        return static::getBraintree()->parseWebhookNotification($signature, $payload);
    }

    /**
     * This method adds an error from the Braintree response to the form.
     * @param Error $result
     */
    public function addErrorFromResponse(Error $result)
    {
        $this->lastError = $result;
        $errors = $result->errors;
        foreach ($errors->shallowAll() as $error) {
            $this->addError('creditCard_number', $error->message);
        }
        /** @var ValidationErrorCollection $transactionErrors */
        $transactionErrors = $errors->forKey('transaction');
        if (isset($transactionErrors)) {
            foreach ($transactionErrors->shallowAll() as $error) {
                $this->addError('creditCard_number', $error->message);
            }
            $values = $this->getValuesFromAttributes();
            foreach (array_keys($values) as $key) {
                /** @var ValidationErrorCollection $keyErrors */
                $keyErrors = $transactionErrors->forKey($key);
                if (isset($keyErrors)) {
                    foreach ($keyErrors->shallowAll() as $error) {
                        $this->addError($key . '_' . $error->attribute, $error->message);
                    }
                }
            }
        }
        if (!$this->hasErrors()) {
            $this->addError('creditCard_number', $result->message);
        }
    }
}