src/BraintreeForm.php
<?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);
}
}
}