diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b281d66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Created by .ignore support plugin (hsz.mobi) +.idea/ +.DS_Store diff --git a/README.md b/README.md index 991f90f..c83af9c 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,12 @@ Braintree Payments is a payment gateway provider owned by eBAY Inc, which allows * Supports both production and sandbox environments, enabling you to test payments before going live * Form entries will only be created when payment is successful * Quick and easy setup +* Tax Exempt form feed setting for non-profits +* Advanced Fraud Prevention integration ## Subscriptions -The plugin does not currently support Braintree Subscriptions. Keep a look out for it in a future version + +* Includes basic subscription functionality that searches for plans in the account and attempts to match the dollar amount of a plan to the amount specified. ## Upgrade Notice If you are updating from a version previous to 1.0, your existing feeds will not work. Please make sure you check all your feeds and ensure they function correctly. diff --git a/assets/js/braintree-data-processing.js b/assets/js/braintree-data-processing.js new file mode 100644 index 0000000..ad8a9da --- /dev/null +++ b/assets/js/braintree-data-processing.js @@ -0,0 +1,33 @@ +/** + * Used for Advanced Fraud Tools integration. + * + * @since 1.4.0 + */ +jQuery( function($) { + braintree.client.create({ + authorization: braintree_data_processing_strings.bt_magic + }, function (err, clientInstance) { + // Creation of any other components... + + // Inside of your client create callback... + braintree.dataCollector.create({ + client: clientInstance, + kount: true + }, function (err, dataCollectorInstance) { + if (err) { + // Handle error in data collector creation + return; + } + var deviceDataInput = jQuery("[name=" + braintree_data_processing_strings.bt_field + "]"); + + if (deviceDataInput == null) { + deviceDataInput = document.createElement('input'); + deviceDataInput.name = 'device_data'; + deviceDataInput.type = 'hidden'; + form.appendChild(deviceDataInput); + } + + deviceDataInput.val(dataCollectorInstance.deviceData); + }); + }); +}); diff --git a/gravity-forms-braintree.php b/gravity-forms-braintree.php index 45534e9..7fb990e 100644 --- a/gravity-forms-braintree.php +++ b/gravity-forms-braintree.php @@ -4,7 +4,7 @@ Plugin URI: http://plugify.io/ Description: Allow your customers to purchase goods and services through Gravity Forms via Braintree Payments Author: Plugify -Version: 1.1.1 +Version: 1.4.0 Author URI: http://plugify.io */ diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/lib/.DS_Store differ diff --git a/lib/Braintree.php b/lib/Braintree.php index e940d65..066107a 100644 --- a/lib/Braintree.php +++ b/lib/Braintree.php @@ -1,181 +1,24 @@ _attributes)) { - return $this->_attributes[$name]; - } - else { - trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE); - return null; - } - } - - /** - * used by isset() and empty() - * @access public - * @param string $name property name - * @return boolean - */ - public function __isset($name) - { - return array_key_exists($name, $this->_attributes); - } - - public function _set($key, $value) - { - $this->_attributes[$key] = $value; - } - /** - * - * @param string $className - * @param object $resultObj - * @return object returns the passed object if successful - * @throws Braintree_Exception_ValidationsFailed - */ - public static function returnObjectOrThrowException($className, $resultObj) - { - $resultObjName = Braintree_Util::cleanClassName($className); - if ($resultObj->success) { - return $resultObj->$resultObjName; - } else { - throw new Braintree_Exception_ValidationsFailed(); - } - } -} -require_once('Braintree/Modification.php'); -require_once('Braintree/Instance.php'); +require_once(__DIR__ . DIRECTORY_SEPARATOR . 'autoload.php'); -require_once('Braintree/Address.php'); -require_once('Braintree/AddOn.php'); -require_once('Braintree/Collection.php'); -require_once('Braintree/Configuration.php'); -require_once('Braintree/CreditCard.php'); -require_once('Braintree/Customer.php'); -require_once('Braintree/CustomerSearch.php'); -require_once('Braintree/DisbursementDetails.php'); -require_once('Braintree/Descriptor.php'); -require_once('Braintree/Digest.php'); -require_once('Braintree/Discount.php'); -require_once('Braintree/IsNode.php'); -require_once('Braintree/EqualityNode.php'); -require_once('Braintree/Exception.php'); -require_once('Braintree/Http.php'); -require_once('Braintree/KeyValueNode.php'); -require_once('Braintree/MerchantAccount.php'); -require_once('Braintree/MerchantAccount/BusinessDetails.php'); -require_once('Braintree/MerchantAccount/FundingDetails.php'); -require_once('Braintree/MerchantAccount/IndividualDetails.php'); -require_once('Braintree/MerchantAccount/AddressDetails.php'); -require_once('Braintree/MultipleValueNode.php'); -require_once('Braintree/MultipleValueOrTextNode.php'); -require_once('Braintree/PartialMatchNode.php'); -require_once('Braintree/Plan.php'); -require_once('Braintree/RangeNode.php'); -require_once('Braintree/ResourceCollection.php'); -require_once('Braintree/SettlementBatchSummary.php'); -require_once('Braintree/Subscription.php'); -require_once('Braintree/SubscriptionSearch.php'); -require_once('Braintree/TextNode.php'); -require_once('Braintree/Transaction.php'); -require_once('Braintree/Disbursement.php'); -require_once('Braintree/TransactionSearch.php'); -require_once('Braintree/TransparentRedirect.php'); -require_once('Braintree/Util.php'); -require_once('Braintree/Version.php'); -require_once('Braintree/Xml.php'); -require_once('Braintree/Error/Codes.php'); -require_once('Braintree/Error/ErrorCollection.php'); -require_once('Braintree/Error/Validation.php'); -require_once('Braintree/Error/ValidationErrorCollection.php'); -require_once('Braintree/Exception/Authentication.php'); -require_once('Braintree/Exception/Authorization.php'); -require_once('Braintree/Exception/Configuration.php'); -require_once('Braintree/Exception/DownForMaintenance.php'); -require_once('Braintree/Exception/ForgedQueryString.php'); -require_once('Braintree/Exception/InvalidSignature.php'); -require_once('Braintree/Exception/NotFound.php'); -require_once('Braintree/Exception/ServerError.php'); -require_once('Braintree/Exception/SSLCertificate.php'); -require_once('Braintree/Exception/SSLCaFileNotFound.php'); -require_once('Braintree/Exception/Unexpected.php'); -require_once('Braintree/Exception/UpgradeRequired.php'); -require_once('Braintree/Exception/ValidationsFailed.php'); -require_once('Braintree/Result/CreditCardVerification.php'); -require_once('Braintree/Result/Error.php'); -require_once('Braintree/Result/Successful.php'); -require_once('Braintree/Test/CreditCardNumbers.php'); -require_once('Braintree/Test/MerchantAccount.php'); -require_once('Braintree/Test/TransactionAmounts.php'); -require_once('Braintree/Test/VenmoSdk.php'); -require_once('Braintree/Transaction/AddressDetails.php'); -require_once('Braintree/Transaction/CreditCardDetails.php'); -require_once('Braintree/Transaction/CustomerDetails.php'); -require_once('Braintree/Transaction/StatusDetails.php'); -require_once('Braintree/Transaction/SubscriptionDetails.php'); -require_once('Braintree/WebhookNotification.php'); -require_once('Braintree/WebhookTesting.php'); -require_once('Braintree/Xml/Generator.php'); -require_once('Braintree/Xml/Parser.php'); -require_once('Braintree/CreditCardVerification.php'); -require_once('Braintree/CreditCardVerificationSearch.php'); -require_once('Braintree/PartnerMerchant.php'); - -if (version_compare(PHP_VERSION, '5.2.1', '<')) { - throw new Braintree_Exception('PHP version >= 5.2.1 required'); +if (version_compare(PHP_VERSION, '5.4.0', '<')) { + throw new Braintree_Exception('PHP version >= 5.4.0 required'); } - -function requireDependencies() { - $requiredExtensions = array('xmlwriter', 'SimpleXML', 'openssl', 'dom', 'hash', 'curl'); - foreach ($requiredExtensions AS $ext) { - if (!extension_loaded($ext)) { - throw new Braintree_Exception('The Braintree library requires the ' . $ext . ' extension.'); +class Braintree { + public static function requireDependencies() { + $requiredExtensions = ['xmlwriter', 'openssl', 'dom', 'hash', 'curl']; + foreach ($requiredExtensions AS $ext) { + if (!extension_loaded($ext)) { + throw new Braintree_Exception('The Braintree library requires the ' . $ext . ' extension.'); + } } } } -requireDependencies(); +Braintree::requireDependencies(); diff --git a/lib/Braintree/AccountUpdaterDailyReport.php b/lib/Braintree/AccountUpdaterDailyReport.php new file mode 100644 index 0000000..226bdfe --- /dev/null +++ b/lib/Braintree/AccountUpdaterDailyReport.php @@ -0,0 +1,43 @@ +_attributes = $disputeAttribs; + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + public function __toString() + { + $display = [ + 'reportDate', 'reportUrl' + ]; + + $displayAttributes = []; + foreach ($display AS $attrib) { + $displayAttributes[$attrib] = $this->$attrib; + } + return __CLASS__ . '[' . + Util::attributesToString($displayAttributes) .']'; + } +} +class_alias('Braintree\AccountUpdaterDailyReport', 'Braintree_AccountUpdaterDailyReport'); diff --git a/lib/Braintree/AchMandate.php b/lib/Braintree/AchMandate.php new file mode 100644 index 0000000..86eeea6 --- /dev/null +++ b/lib/Braintree/AchMandate.php @@ -0,0 +1,55 @@ +_attributes) . ']'; + } + + /** + * sets instance properties from an array of values + * + * @ignore + * @access protected + * @param array $achAttribs array of achMandate data + * @return void + */ + protected function _initialize($achAttribs) + { + // set the attributes + $this->_attributes = $achAttribs; + } + + /** + * factory method: returns an instance of AchMandate + * to the requesting method, with populated properties + * @ignore + * @return AchMandate + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + + } +} +class_alias('Braintree\AchMandate', 'Braintree_Mandate'); diff --git a/lib/Braintree/AddOn.php b/lib/Braintree/AddOn.php index 98a4c47..3eb5612 100644 --- a/lib/Braintree/AddOn.php +++ b/lib/Braintree/AddOn.php @@ -1,22 +1,43 @@ $response['addOns']); - - return Braintree_Util::extractAttributeAsArray( - $addOns, - 'addOn' - ); - } +namespace Braintree; +/** + * @property-read string $amount + * @property-read \DateTime $createdAt + * @property-read int|null $currentBillingCycle + * @property-read string $description + * @property-read string $id + * @property-read string|null $kind + * @property-read string $merchantId + * @property-read string $name + * @property-read boolean $neverExpires + * @property-read int|null $numberOfBillingCycles + * @property-read int|null $quantity + * @property-read \DateTime $updatedAt + */ +class AddOn extends Modification +{ + /** + * + * @param array $attributes + * @return AddOn + */ public static function factory($attributes) { $instance = new self(); $instance->_initialize($attributes); return $instance; } + + + /** + * static methods redirecting to gateway + * + * @return AddOn[] + */ + public static function all() + { + return Configuration::gateway()->addOn()->all(); + } } +class_alias('Braintree\AddOn', 'Braintree_AddOn'); diff --git a/lib/Braintree/AddOnGateway.php b/lib/Braintree/AddOnGateway.php new file mode 100644 index 0000000..9ecf942 --- /dev/null +++ b/lib/Braintree/AddOnGateway.php @@ -0,0 +1,53 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * + * @return AddOn[] + */ + public function all() + { + $path = $this->_config->merchantPath() . '/add_ons'; + $response = $this->_http->get($path); + + $addOns = ["addOn" => $response['addOns']]; + + return Util::extractAttributeAsArray( + $addOns, + 'addOn' + ); + } +} +class_alias('Braintree\AddOnGateway', 'Braintree_AddOnGateway'); diff --git a/lib/Braintree/Address.php b/lib/Braintree/Address.php index 821959d..ee12524 100644 --- a/lib/Braintree/Address.php +++ b/lib/Braintree/Address.php @@ -1,13 +1,9 @@ $attribs) - ); - } - /** - * attempts the create operation assuming all data will validate - * returns a Braintree_Address object instead of a Result - * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError - */ - public static function createNoValidate($attribs) - { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - - } - - /** - * delete an address by id - * - * @param mixed $customerOrId - * @param string $addressId - */ - public static function delete($customerOrId = null, $addressId = null) - { - self::_validateId($addressId); - $customerId = self::_determineCustomerId($customerOrId); - Braintree_Http::delete( - '/customers/' . $customerId . '/addresses/' . $addressId - ); - return new Braintree_Result_Successful(); - } - - /** - * find an address by id - * - * Finds the address with the given addressId that is associated - * to the given customerOrId. - * If the address cannot be found, a NotFound exception will be thrown. - * - * - * @access public - * @param mixed $customerOrId - * @param string $addressId - * @return object Braintree_Address - * @throws Braintree_Exception_NotFound - */ - public static function find($customerOrId, $addressId) - { - - $customerId = self::_determineCustomerId($customerOrId); - self::_validateId($addressId); - - try { - $response = Braintree_Http::get( - '/customers/' . $customerId . '/addresses/' . $addressId - ); - return self::factory($response['address']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'address for customer ' . $customerId . - ' with id ' . $addressId . ' not found.' - ); - } - - } - - /** - * returns false if comparing object is not a Braintree_Address, - * or is a Braintree_Address with a different id + * returns false if comparing object is not a Address, + * or is a Address with a different id * * @param object $other address to compare against * @return boolean */ public function isEqual($other) { - return !($other instanceof Braintree_Address) ? + return !($other instanceof self) ? false : ($this->id === $other->id && $this->customerId === $other->customerId); } - /** - * updates the address record - * - * if calling this method in static context, - * customerOrId is the 2nd attribute, addressId 3rd. - * customerOrId & addressId are not sent in object context. - * - * - * @access public - * @param array $attributes - * @param mixed $customerOrId (only used in static call) - * @param string $addressId (only used in static call) - * @return object Braintree_Result_Successful or Braintree_Result_Error - */ - public static function update($customerOrId, $addressId, $attributes) - { - self::_validateId($addressId); - $customerId = self::_determineCustomerId($customerOrId); - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - - $response = Braintree_Http::put( - '/customers/' . $customerId . '/addresses/' . $addressId, - array('address' => $attributes) - ); - - return self::_verifyGatewayResponse($response); - - } - - /** - * update an address record, assuming validations will pass - * - * if calling this method in static context, - * customerOrId is the 2nd attribute, addressId 3rd. - * customerOrId & addressId are not sent in object context. - * - * @access public - * @param array $transactionAttribs - * @param string $customerId - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationsFailed - * @see Braintree_Address::update() - */ - public static function updateNoValidate($customerOrId, $addressId, $attributes) - { - $result = self::update($customerOrId, $addressId, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - /** - * creates a full array signature of a valid create request - * @return array gateway create request format - */ - public static function createSignature() - { - return array( - 'company', 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', - 'countryName', 'customerId', 'extendedAddress', 'firstName', - 'lastName', 'locality', 'postalCode', 'region', 'streetAddress' - ); - } - - /** - * creates a full array signature of a valid update request - * @return array gateway update request format - */ - public static function updateSignature() - { - // TODO: remove customerId from update signature - return self::createSignature(); - - } - /** * create a printable representation of the object as: * ClassName[property=value, property=value] * @ignore - * @return var + * @return string */ public function __toString() { return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + Util::attributesToString($this->_attributes) . ']'; } /** @@ -227,7 +60,7 @@ public function __toString() * @ignore * @access protected * @param array $addressAttribs array of address data - * @return none + * @return void */ protected function _initialize($addressAttribs) { @@ -236,117 +69,82 @@ protected function _initialize($addressAttribs) } /** - * verifies that a valid address id is being used + * factory method: returns an instance of Address + * to the requesting method, with populated properties * @ignore - * @param string $id address id - * @throws InvalidArgumentException + * @return Address */ - private static function _validateId($id = null) + public static function factory($attributes) { - if (empty($id) || trim($id) == "") { - throw new InvalidArgumentException( - 'expected address id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid address id.' - ); - } + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + // static methods redirecting to gateway + /** - * verifies that a valid customer id is being used - * @ignore - * @param string $id customer id - * @throws InvalidArgumentException + * + * @param array $attribs + * @return Address */ - private static function _validateCustomerId($id = null) + public static function create($attribs) { - if (empty($id) || trim($id) == "") { - throw new InvalidArgumentException( - 'expected customer id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid customer id.' - ); - } - + return Configuration::gateway()->address()->create($attribs); } /** - * determines if a string id or Customer object was passed - * @ignore - * @param mixed $customerOrId - * @return string customerId + * + * @param array $attribs + * @return Address */ - private static function _determineCustomerId($customerOrId) + public static function createNoValidate($attribs) { - $customerId = ($customerOrId instanceof Braintree_Customer) ? $customerOrId->id : $customerOrId; - self::_validateCustomerId($customerId); - return $customerId; - + return Configuration::gateway()->address()->createNoValidate($attribs); } - /* private class methods */ /** - * sends the create request to the gateway - * @ignore - * @param string $url - * @param array $params - * @return mixed + * + * @param Customer|int $customerOrId + * @param int $addressId + * @throws InvalidArgumentException + * @return Result\Successful */ - private static function _doCreate($url, $params) + public static function delete($customerOrId = null, $addressId = null) { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); - + return Configuration::gateway()->address()->delete($customerOrId, $addressId); } /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_Address object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected + * @param Customer|int $customerOrId + * @param int $addressId + * @throws Exception\NotFound + * @return Address */ - private static function _verifyGatewayResponse($response) + public static function find($customerOrId, $addressId) { - if (isset($response['address'])) { - // return a populated instance of Braintree_Address - return new Braintree_Result_Successful( - self::factory($response['address']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected address or apiErrorResponse" - ); - } - + return Configuration::gateway()->address()->find($customerOrId, $addressId); } /** - * factory method: returns an instance of Braintree_Address - * to the requesting method, with populated properties - * @ignore - * @return object instance of Braintree_Address + * + * @param Customer|int $customerOrId + * @param int $addressId + * @param array $attributes + * @throws Exception\Unexpected + * @return Result\Successful|Result\Error */ - public static function factory($attributes) + public static function update($customerOrId, $addressId, $attributes) { - $instance = new self(); - $instance->_initialize($attributes); - return $instance; + return Configuration::gateway()->address()->update($customerOrId, $addressId, $attributes); + } + public static function updateNoValidate($customerOrId, $addressId, $attributes) + { + return Configuration::gateway()->address()->updateNoValidate($customerOrId, $addressId, $attributes); } } +class_alias('Braintree\Address', 'Braintree_Address'); diff --git a/lib/Braintree/AddressGateway.php b/lib/Braintree/AddressGateway.php new file mode 100644 index 0000000..a0256c0 --- /dev/null +++ b/lib/Braintree/AddressGateway.php @@ -0,0 +1,314 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + + /* public class methods */ + /** + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function create($attribs) + { + Util::verifyKeys(self::createSignature(), $attribs); + $customerId = isset($attribs['customerId']) ? + $attribs['customerId'] : + null; + + $this->_validateCustomerId($customerId); + unset($attribs['customerId']); + try { + return $this->_doCreate( + '/customers/' . $customerId . '/addresses', + ['address' => $attribs] + ); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'Customer ' . $customerId . ' not found.' + ); + } + } + + /** + * attempts the create operation assuming all data will validate + * returns a Address object instead of a Result + * + * @access public + * @param array $attribs + * @return self + * @throws Exception\ValidationError + */ + public function createNoValidate($attribs) + { + $result = $this->create($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + + } + + /** + * delete an address by id + * + * @param mixed $customerOrId + * @param string $addressId + */ + public function delete($customerOrId = null, $addressId = null) + { + $this->_validateId($addressId); + $customerId = $this->_determineCustomerId($customerOrId); + $path = $this->_config->merchantPath() . '/customers/' . $customerId . '/addresses/' . $addressId; + $this->_http->delete($path); + return new Result\Successful(); + } + + /** + * find an address by id + * + * Finds the address with the given addressId that is associated + * to the given customerOrId. + * If the address cannot be found, a NotFound exception will be thrown. + * + * + * @access public + * @param mixed $customerOrId + * @param string $addressId + * @return Address + * @throws Exception\NotFound + */ + public function find($customerOrId, $addressId) + { + + $customerId = $this->_determineCustomerId($customerOrId); + $this->_validateId($addressId); + + try { + $path = $this->_config->merchantPath() . '/customers/' . $customerId . '/addresses/' . $addressId; + $response = $this->_http->get($path); + return Address::factory($response['address']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'address for customer ' . $customerId . + ' with id ' . $addressId . ' not found.' + ); + } + + } + + /** + * updates the address record + * + * if calling this method in context, + * customerOrId is the 2nd attribute, addressId 3rd. + * customerOrId & addressId are not sent in object context. + * + * + * @access public + * @param array $attributes + * @param mixed $customerOrId (only used in call) + * @param string $addressId (only used in call) + * @return Result\Successful|Result\Error + */ + public function update($customerOrId, $addressId, $attributes) + { + $this->_validateId($addressId); + $customerId = $this->_determineCustomerId($customerOrId); + Util::verifyKeys(self::updateSignature(), $attributes); + + $path = $this->_config->merchantPath() . '/customers/' . $customerId . '/addresses/' . $addressId; + $response = $this->_http->put($path, ['address' => $attributes]); + + return $this->_verifyGatewayResponse($response); + + } + + /** + * update an address record, assuming validations will pass + * + * if calling this method in context, + * customerOrId is the 2nd attribute, addressId 3rd. + * customerOrId & addressId are not sent in object context. + * + * @access public + * @param array $transactionAttribs + * @param string $customerId + * @return Transaction + * @throws Exception\ValidationsFailed + * @see Address::update() + */ + public function updateNoValidate($customerOrId, $addressId, $attributes) + { + $result = $this->update($customerOrId, $addressId, $attributes); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + /** + * creates a full array signature of a valid create request + * @return array gateway create request format + */ + public static function createSignature() + { + return [ + 'company', 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'countryName', 'customerId', 'extendedAddress', 'firstName', + 'lastName', 'locality', 'postalCode', 'region', 'streetAddress' + ]; + } + + /** + * creates a full array signature of a valid update request + * @return array gateway update request format + */ + public static function updateSignature() + { + // TODO: remove customerId from update signature + return self::createSignature(); + + } + + /** + * verifies that a valid address id is being used + * @ignore + * @param string $id address id + * @throws InvalidArgumentException + */ + private function _validateId($id = null) + { + if (empty($id) || trim($id) == "") { + throw new InvalidArgumentException( + 'expected address id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { + throw new InvalidArgumentException( + $id . ' is an invalid address id.' + ); + } + } + + /** + * verifies that a valid customer id is being used + * @ignore + * @param string $id customer id + * @throws InvalidArgumentException + */ + private function _validateCustomerId($id = null) + { + if (empty($id) || trim($id) == "") { + throw new InvalidArgumentException( + 'expected customer id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { + throw new InvalidArgumentException( + $id . ' is an invalid customer id.' + ); + } + + } + + /** + * determines if a string id or Customer object was passed + * @ignore + * @param mixed $customerOrId + * @return string customerId + */ + private function _determineCustomerId($customerOrId) + { + $customerId = ($customerOrId instanceof Customer) ? $customerOrId->id : $customerOrId; + $this->_validateCustomerId($customerId); + return $customerId; + + } + + /* private class methods */ + /** + * sends the create request to the gateway + * @ignore + * @param string $subPath + * @param array $params + * @return Result\Successful|Result\Error + */ + private function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new Address object and encapsulates + * it inside a Result\Successful object, or + * encapsulates an Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['address'])) { + // return a populated instance of Address + return new Result\Successful( + Address::factory($response['address']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected address or apiErrorResponse" + ); + } + + } +} +class_alias('Braintree\AddressGateway', 'Braintree_AddressGateway'); diff --git a/lib/Braintree/AmexExpressCheckoutCard.php b/lib/Braintree/AmexExpressCheckoutCard.php new file mode 100644 index 0000000..90d48f2 --- /dev/null +++ b/lib/Braintree/AmexExpressCheckoutCard.php @@ -0,0 +1,80 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * + * @property-read string $bin + * @property-read string $cardMemberExpiryDate + * @property-read string $cardMemberNumber + * @property-read string $cardType + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $expirationMonth + * @property-read string $expirationYear + * @property-read string $imageUrl + * @property-read string $token + * @property-read string $sourceDescription + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read \DateTime $updatedAt + */ +class AmexExpressCheckoutCard extends Base +{ + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * factory method: returns an instance of AmexExpressCheckoutCard + * to the requesting method, with populated properties + * + * @ignore + * @return AmexExpressCheckoutCard + */ + public static function factory($attributes) + { + + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $amexExpressCheckoutCardAttribs array of Amex Express Checkout card properties + * @return void + */ + protected function _initialize($amexExpressCheckoutCardAttribs) + { + // set the attributes + $this->_attributes = $amexExpressCheckoutCardAttribs; + + $subscriptionArray = []; + if (isset($amexExpressCheckoutCardAttribs['subscriptions'])) { + foreach ($amexExpressCheckoutCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } +} +class_alias('Braintree\AmexExpressCheckoutCard', 'Braintree_AmexExpressCheckoutCard'); diff --git a/lib/Braintree/AndroidPayCard.php b/lib/Braintree/AndroidPayCard.php new file mode 100644 index 0000000..4d73c4d --- /dev/null +++ b/lib/Braintree/AndroidPayCard.php @@ -0,0 +1,90 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * + * @property-read string $bin + * @property-read string $cardType + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $expirationMonth + * @property-read string $expirationYear + * @property-read string $googleTransactionId + * @property-read string $imageUrl + * @property-read string $last4 + * @property-read string $sourceCardLast4 + * @property-read string $sourceCardType + * @property-read string $sourceDescription + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read \DateTime $updatedAt + * @property-read string $virtualCardLast4 + * @property-read string $virtualCardType + */ +class AndroidPayCard extends Base +{ + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * factory method: returns an instance of AndroidPayCard + * to the requesting method, with populated properties + * + * @ignore + * @return AndroidPayCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => $attributes['virtualCardLast4'], + 'cardType' => $attributes['virtualCardType'], + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $androidPayCardAttribs array of Android Pay card properties + * @return void + */ + protected function _initialize($androidPayCardAttribs) + { + // set the attributes + $this->_attributes = $androidPayCardAttribs; + + $subscriptionArray = []; + if (isset($androidPayCardAttribs['subscriptions'])) { + foreach ($androidPayCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } +} +class_alias('Braintree\AndroidPayCard', 'Braintree_AndroidPayCard'); diff --git a/lib/Braintree/ApplePayCard.php b/lib/Braintree/ApplePayCard.php new file mode 100644 index 0000000..51f10fa --- /dev/null +++ b/lib/Braintree/ApplePayCard.php @@ -0,0 +1,103 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * + * @property-read string $bin + * @property-read string $cardType + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $expirationDate + * @property-read string $expirationMonth + * @property-read string $expirationYear + * @property-read boolean $expired + * @property-read string $imageUrl + * @property-read string $last4 + * @property-read string $token + * @property-read string $paymentInstrumentName + * @property-read string $sourceDescription + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read \DateTime $updatedAt + */ +class ApplePayCard extends Base +{ + // Card Type + const AMEX = 'Apple Pay - American Express'; + const MASTER_CARD = 'Apple Pay - MasterCard'; + const VISA = 'Apple Pay - Visa'; + + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * checks whether the card is expired based on the current date + * + * @return boolean + */ + public function isExpired() + { + return $this->expired; + } + + /** + * factory method: returns an instance of ApplePayCard + * to the requesting method, with populated properties + * + * @ignore + * @return ApplePayCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $applePayCardAttribs array of Apple Pay card properties + * @return void + */ + protected function _initialize($applePayCardAttribs) + { + // set the attributes + $this->_attributes = $applePayCardAttribs; + + $subscriptionArray = []; + if (isset($applePayCardAttribs['subscriptions'])) { + foreach ($applePayCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); + } +} +class_alias('Braintree\ApplePayCard', 'Braintree_ApplePayCard'); diff --git a/lib/Braintree/ApplePayGateway.php b/lib/Braintree/ApplePayGateway.php new file mode 100644 index 0000000..c8291be --- /dev/null +++ b/lib/Braintree/ApplePayGateway.php @@ -0,0 +1,65 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function registerDomain($domain) + { + $path = $this->_config->merchantPath() . '/processing/apple_pay/validate_domains'; + $response = $this->_http->post($path, ['url' => $domain]); + if (array_key_exists('response', $response) && $response['response']['success']) + { + return new Result\Successful; + } + else if (array_key_exists('apiErrorResponse', $response)) + { + return new Result\Error($response['apiErrorResponse']); + } + } + + public function unregisterDomain($domain) + { + $path = $this->_config->merchantPath() . '/processing/apple_pay/unregister_domain'; + $this->_http->delete($path, ['url' => $domain]); + return new Result\Successful; + } + + public function registeredDomains() + { + $path = $this->_config->merchantPath() . '/processing/apple_pay/registered_domains'; + $response = $this->_http->get($path); + if (array_key_exists('response', $response) && array_key_exists('domains', $response['response'])) + { + $options = ApplePayOptions::factory($response['response']); + return new Result\Successful($options, 'applePayOptions'); + } + else if (array_key_exists('apiErrorResponse', $response)) + { + return new Result\Error($response['apiErrorResponse']); + } + else + { + throw new Exception\Unexpected('expected response or apiErrorResponse'); + } + } +} +class_alias('Braintree\ApplePayGateway', 'Braintree_ApplePayGateway'); diff --git a/lib/Braintree/ApplePayOptions.php b/lib/Braintree/ApplePayOptions.php new file mode 100644 index 0000000..40b2cef --- /dev/null +++ b/lib/Braintree/ApplePayOptions.php @@ -0,0 +1,28 @@ +_initialize($attributes); + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} +class_alias('Braintree\ApplePayOptions', 'Braintree_ApplePayOptions'); diff --git a/lib/Braintree/AuthorizationAdjustment.php b/lib/Braintree/AuthorizationAdjustment.php new file mode 100644 index 0000000..a2eeea5 --- /dev/null +++ b/lib/Braintree/AuthorizationAdjustment.php @@ -0,0 +1,35 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($authorizationAdjustmentAttribs) + { + $this->_attributes = $authorizationAdjustmentAttribs; + } + + public function __toString() + { + return __CLASS__ . '[' . Util::attributesToString($this->_attributes) . ']'; + } +} +class_alias('Braintree\AuthorizationAdjustment', 'Braintree_Authorization_Adjustment'); diff --git a/lib/Braintree/Base.php b/lib/Braintree/Base.php new file mode 100644 index 0000000..8e027c7 --- /dev/null +++ b/lib/Braintree/Base.php @@ -0,0 +1,88 @@ +_attributes)) { + return $this->_attributes[$name]; + } + else { + trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE); + return null; + } + } + + /** + * Checks for the existance of a property stored in the private $_attributes property + * + * @ignore + * @param string $name + * @return boolean + */ + public function __isset($name) + { + return array_key_exists($name, $this->_attributes); + } + + /** + * Mutator for instance properties stored in the private $_attributes property + * + * @ignore + * @param string $key + * @param mixed $value + */ + public function _set($key, $value) + { + $this->_attributes[$key] = $value; + } + + /** + * Implementation of JsonSerializable + * + * @ignore + * @return array + */ + public function jsonSerialize() + { + return $this->_attributes; + } +} diff --git a/lib/Braintree/BinData.php b/lib/Braintree/BinData.php new file mode 100644 index 0000000..5a668de --- /dev/null +++ b/lib/Braintree/BinData.php @@ -0,0 +1,41 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the bin data + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + +} +class_alias('Braintree\BinData', 'Braintree_BinData'); diff --git a/lib/Braintree/ClientToken.php b/lib/Braintree/ClientToken.php new file mode 100644 index 0000000..f3d4884 --- /dev/null +++ b/lib/Braintree/ClientToken.php @@ -0,0 +1,49 @@ +clientToken()->generate($params); + } + + /** + * + * @param type $params + * @throws InvalidArgumentException + */ + public static function conditionallyVerifyKeys($params) + { + return Configuration::gateway()->clientToken()->conditionallyVerifyKeys($params); + } + + /** + * + * @return string client token retrieved from server + */ + public static function generateWithCustomerIdSignature() + { + return Configuration::gateway()->clientToken()->generateWithCustomerIdSignature(); + } + + /** + * + * @return string client token retrieved from server + */ + public static function generateWithoutCustomerIdSignature() + { + return Configuration::gateway()->clientToken()->generateWithoutCustomerIdSignature(); + } +} +class_alias('Braintree\ClientToken', 'Braintree_ClientToken'); diff --git a/lib/Braintree/ClientTokenGateway.php b/lib/Braintree/ClientTokenGateway.php new file mode 100644 index 0000000..b2fcbf5 --- /dev/null +++ b/lib/Braintree/ClientTokenGateway.php @@ -0,0 +1,129 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function generate($params=[]) + { + if (!array_key_exists("version", $params)) { + $params["version"] = ClientToken::DEFAULT_VERSION; + } + + $this->conditionallyVerifyKeys($params); + $generateParams = ["client_token" => $params]; + + return $this->_doGenerate('/client_token', $generateParams); + } + + /** + * sends the generate request to the gateway + * + * @ignore + * @param var $url + * @param array $params + * @return string + */ + public function _doGenerate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * + * @param array $params + * @throws InvalidArgumentException + */ + public function conditionallyVerifyKeys($params) + { + if (array_key_exists("customerId", $params)) { + Util::verifyKeys($this->generateWithCustomerIdSignature(), $params); + } else { + Util::verifyKeys($this->generateWithoutCustomerIdSignature(), $params); + } + } + + /** + * + * @return mixed[] + */ + public function generateWithCustomerIdSignature() + { + return [ + "version", "customerId", "proxyMerchantId", + ["options" => ["makeDefault", "verifyCard", "failOnDuplicatePaymentMethod"]], + "merchantAccountId"]; + } + + /** + * + * @return string[] + */ + public function generateWithoutCustomerIdSignature() + { + return ["version", "proxyMerchantId", "merchantAccountId"]; + } + + /** + * generic method for validating incoming gateway responses + * + * If the request is successful, returns a client token string. + * Otherwise, throws an InvalidArgumentException with the error + * response from the Gateway or an HTTP status code exception. + * + * @ignore + * @param array $response gateway response values + * @return string client token + * @throws InvalidArgumentException | HTTP status code exception + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['clientToken'])) { + return $response['clientToken']['value']; + } elseif (isset($response['apiErrorResponse'])) { + throw new InvalidArgumentException( + $response['apiErrorResponse']['message'] + ); + } else { + throw new Exception\Unexpected( + "Expected clientToken or apiErrorResponse" + ); + } + } + +} +class_alias('Braintree\ClientTokenGateway', 'Braintree_ClientTokenGateway'); diff --git a/lib/Braintree/CoinbaseAccount.php b/lib/Braintree/CoinbaseAccount.php new file mode 100644 index 0000000..a93fc1c --- /dev/null +++ b/lib/Braintree/CoinbaseAccount.php @@ -0,0 +1,110 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $customerId + * @property-read string $token + * @property-read string $userId + * @property-read string $userName + * @property-read string $userEmail + */ +class CoinbaseAccount extends Base +{ + /** + * factory method: returns an instance of CoinbaseAccount + * to the requesting method, with populated properties + * + * @ignore + * @return CoinbaseAccount + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $coinbaseAccountAttribs array of coinbaseAccount data + * @return void + */ + protected function _initialize($coinbaseAccountAttribs) + { + // set the attributes + $this->_attributes = $coinbaseAccountAttribs; + + $subscriptionArray = []; + if (isset($coinbaseAccountAttribs['subscriptions'])) { + foreach ($coinbaseAccountAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + + + // static methods redirecting to gateway + + public static function find($token) + { + return Configuration::gateway()->coinbaseAccount()->find($token); + } + + public static function update($token, $attributes) + { + return Configuration::gateway()->coinbaseAccount()->update($token, $attributes); + } + + public static function delete($token) + { + return Configuration::gateway()->coinbaseAccount()->delete($token); + } + + public static function sale($token, $transactionAttribs) + { + return Configuration::gateway()->coinbaseAccount()->sale($token, $transactionAttribs); + } +} +class_alias('Braintree\CoinbaseAccount', 'Braintree_CoinbaseAccount'); diff --git a/lib/Braintree/Collection.php b/lib/Braintree/Collection.php index 58293c3..1cbec1d 100644 --- a/lib/Braintree/Collection.php +++ b/lib/Braintree/Collection.php @@ -1,30 +1,31 @@ '', - 'merchantId' => '', - 'publicKey' => '', - 'privateKey' => '', - ); - /** - * - * @access protected - * @static - * @var array valid environments, used for validation - */ - private static $_validEnvironments = array( - 'development', - 'sandbox', - 'production', - 'qa', - ); + public function __construct($attribs = []) + { + foreach ($attribs as $kind => $value) { + if ($kind == 'environment') { + CredentialsParser::assertValidEnvironment($value); + $this->_environment = $value; + } + if ($kind == 'merchantId') { + $this->_merchantId = $value; + } + if ($kind == 'publicKey') { + $this->_publicKey = $value; + } + if ($kind == 'privateKey') { + $this->_privateKey = $value; + } + if ($kind == 'timeout') { + $this->_timeout = $value; + } + if ($kind == 'acceptGzipEncoding') { + $this->_acceptGzipEncoding = $value; + } + } + + if (isset($attribs['clientId']) || isset($attribs['accessToken'])) { + if (isset($attribs['environment']) || isset($attribs['merchantId']) || isset($attribs['publicKey']) || isset($attribs['privateKey'])) { + throw new Exception\Configuration('Cannot mix OAuth credentials (clientId, clientSecret, accessToken) with key credentials (publicKey, privateKey, environment, merchantId).'); + } + $parsedCredentials = new CredentialsParser($attribs); + + $this->_environment = $parsedCredentials->getEnvironment(); + $this->_merchantId = $parsedCredentials->getMerchantId(); + $this->_clientId = $parsedCredentials->getClientId(); + $this->_clientSecret = $parsedCredentials->getClientSecret(); + $this->_accessToken = $parsedCredentials->getAccessToken(); + } + } /** * resets configuration to default * @access public - * @static */ public static function reset() { - self::$_cache = array ( - 'environment' => '', - 'merchantId' => '', - 'publicKey' => '', - 'privateKey' => '', - ); + self::$global = new Configuration(); + } + + public static function gateway() + { + return new Gateway(self::$global); + } + + public static function environment($value=null) + { + if (empty($value)) { + return self::$global->getEnvironment(); + } + CredentialsParser::assertValidEnvironment($value); + self::$global->setEnvironment($value); + } + + public static function merchantId($value=null) + { + if (empty($value)) { + return self::$global->getMerchantId(); + } + self::$global->setMerchantId($value); + } + + public static function publicKey($value=null) + { + if (empty($value)) { + return self::$global->getPublicKey(); + } + self::$global->setPublicKey($value); + } + + public static function privateKey($value=null) + { + if (empty($value)) { + return self::$global->getPrivateKey(); + } + self::$global->setPrivateKey($value); } /** - * performs sanity checks when config settings are being set + * Sets or gets the read timeout to use for making requests. * - * @ignore - * @access protected - * @param string $key name of config setting - * @param string $value value to set - * @throws InvalidArgumentException - * @throws Braintree_Exception_Configuration - * @static - * @return boolean + * @param integer $value If provided, sets the read timeout + * @return integer The read timeout used for connecting to Braintree */ - private static function validate($key=null, $value=null) + public static function timeout($value=null) { - if (empty($key) && empty($value)) { - throw new InvalidArgumentException('nothing to validate'); + if (empty($value)) { + return self::$global->getTimeout(); } + self::$global->setTimeout($value); + } - if ($key === 'environment' && - !in_array($value, self::$_validEnvironments) ) { - throw new Braintree_Exception_Configuration('"' . - $value . '" is not a valid environment.'); + /** + * Sets or gets the SSL version to use for making requests. See + * https://php.net/manual/en/function.curl-setopt.php for possible + * CURLOPT_SSLVERSION values. + * + * @param integer $value If provided, sets the SSL version + * @return integer The SSL version used for connecting to Braintree + */ + public static function sslVersion($value=null) + { + if (empty($value)) { + return self::$global->getSslVersion(); } + self::$global->setSslVersion($value); + } - if (!isset(self::$_cache[$key])) { - throw new Braintree_Exception_Configuration($key . - ' is not a valid configuration setting.'); + /** + * Sets or gets the proxy host to use for connecting to Braintree + * + * @param string $value If provided, sets the proxy host + * @return string The proxy host used for connecting to Braintree + */ + public static function proxyHost($value = null) + { + if (empty($value)) { + return self::$global->getProxyHost(); } + self::$global->setProxyHost($value); + } + /** + * Sets or gets the port of the proxy to use for connecting to Braintree + * + * @param string $value If provided, sets the port of the proxy + * @return string The port of the proxy used for connecting to Braintree + */ + public static function proxyPort($value = null) + { if (empty($value)) { - throw new InvalidArgumentException($key . ' cannot be empty.'); + return self::$global->getProxyPort(); } + self::$global->setProxyPort($value); + } - return true; + /** + * Sets or gets the proxy type to use for connecting to Braintree. This value + * can be any of the CURLOPT_PROXYTYPE options in PHP cURL. + * + * @param string $value If provided, sets the proxy type + * @return string The proxy type used for connecting to Braintree + */ + public static function proxyType($value = null) + { + if (empty($value)) { + return self::$global->getProxyType(); + } + self::$global->setProxyType($value); } - private static function set($key, $value) + /** + * Specifies whether or not a proxy is properly configured + * + * @return bool true if a proxy is configured properly, false if not + */ + public static function isUsingProxy() { - // this method will raise an exception on invalid data - self::validate($key, $value); - // set the value in the cache - self::$_cache[$key] = $value; + $proxyHost = self::$global->getProxyHost(); + $proxyPort = self::$global->getProxyPort(); + return !empty($proxyHost) && !empty($proxyPort); + } + public static function proxyUser($value = null) + { + if (empty($value)) { + return self::$global->getProxyUser(); + } + self::$global->setProxyUser($value); } - private static function get($key) + public static function proxyPassword($value = null) { - // throw an exception if the value hasn't been set - if (isset(self::$_cache[$key]) && - (empty(self::$_cache[$key]))) { - throw new Braintree_Exception_Configuration( - $key.' needs to be set' - ); + if (empty($value)) { + return self::$global->getProxyPassword(); } + self::$global->setProxyPassword($value); + } + + /** + * Specified whether or not a username and password have been provided for + * use with an authenticated proxy + * + * @return bool true if both proxyUser and proxyPassword are present + */ + public static function isAuthenticatedProxy() + { + $proxyUser = self::$global->getProxyUser(); + $proxyPwd = self::$global->getProxyPassword(); + return !empty($proxyUser) && !empty($proxyPwd); + } - if (array_key_exists($key, self::$_cache)) { - return self::$_cache[$key]; + /** + * Specify if the HTTP client is able to decode gzipped responses. + * + * @param bool $value If true, will send an Accept-Encoding header with a gzip value. If false, will not send an Accept-Encoding header with a gzip value. + * @return bool true if an Accept-Encoding header with a gzip value will be sent, false if not + */ + public static function acceptGzipEncoding($value = null) + { + if (is_null($value)) { + return self::$global->getAcceptGzipEncoding(); } + self::$global->setAcceptGzipEncoding($value); + } - // return null by default to prevent __set from overloading - return null; + public static function assertGlobalHasAccessTokenOrKeys() + { + self::$global->assertHasAccessTokenOrKeys(); } + public function assertHasAccessTokenOrKeys() + { + if (empty($this->_accessToken)) { + if (empty($this->_merchantId)) { + throw new Exception\Configuration('Braintree\\Configuration::merchantId needs to be set (or accessToken needs to be passed to Braintree\\Gateway).'); + } else if (empty($this->_environment)) { + throw new Exception\Configuration('Braintree\\Configuration::environment needs to be set.'); + } else if (empty($this->_publicKey)) { + throw new Exception\Configuration('Braintree\\Configuration::publicKey needs to be set.'); + } else if (empty($this->_privateKey)) { + throw new Exception\Configuration('Braintree\\Configuration::privateKey needs to be set.'); + } + } + } - private static function setOrGet($name, $value = null) + public function assertHasClientCredentials() { - if (!empty($value) && is_array($value)) { - $value = $value[0]; + $this->assertHasClientId(); + $this->assertHasClientSecret(); + } + + public function assertHasClientId() + { + if (empty($this->_clientId)) { + throw new Exception\Configuration('clientId needs to be passed to Braintree\\Gateway.'); } - if (!empty($value)) { - self::set($name, $value); - } else { - return self::get($name); + } + + public function assertHasClientSecret() + { + if (empty($this->_clientSecret)) { + throw new Exception\Configuration('clientSecret needs to be passed to Braintree\\Gateway.'); } - return true; } - /**#@+ - * sets or returns the property after validation - * @access public - * @static - * @param string $value pass a string to set, empty to get - * @return mixed returns true on set + + public function getEnvironment() + { + return $this->_environment; + } + + /** + * Do not use this method directly. Pass in the environment to the constructor. */ - public static function environment($value = null) + public function setEnvironment($value) + { + $this->_environment = $value; + } + + public function getMerchantId() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_merchantId; } - public static function merchantId($value = null) + /** + * Do not use this method directly. Pass in the merchantId to the constructor. + */ + public function setMerchantId($value) { - return self::setOrGet(__FUNCTION__, $value); + $this->_merchantId = $value; } - public static function publicKey($value = null) + public function getPublicKey() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_publicKey; } - public static function privateKey($value = null) + public function getClientId() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_clientId; } - /**#@-*/ /** - * returns the full merchant URL based on config values - * - * @access public - * @static - * @param none - * @return string merchant URL + * Do not use this method directly. Pass in the publicKey to the constructor. + */ + public function setPublicKey($value) + { + $this->_publicKey = $value; + } + + public function getPrivateKey() + { + return $this->_privateKey; + } + + public function getClientSecret() + { + return $this->_clientSecret; + } + + /** + * Do not use this method directly. Pass in the privateKey to the constructor. */ - public static function merchantUrl() + public function setPrivateKey($value) + { + $this->_privateKey = $value; + } + + private function setProxyHost($value) + { + $this->_proxyHost = $value; + } + + public function getProxyHost() + { + return $this->_proxyHost; + } + + private function setProxyPort($value) + { + $this->_proxyPort = $value; + } + + public function getProxyPort() + { + return $this->_proxyPort; + } + + private function setProxyType($value) + { + $this->_proxyType = $value; + } + + public function getProxyType() + { + return $this->_proxyType; + } + + private function setProxyUser($value) { - return self::baseUrl() . - self::merchantPath(); + $this->_proxyUser = $value; } + public function getProxyUser() + { + return $this->_proxyUser; + } + + private function setProxyPassword($value) + { + $this->_proxyPassword = $value; + } + + public function getProxyPassword() + { + return $this->_proxyPassword; + } + + private function setTimeout($value) + { + $this->_timeout = $value; + } + + public function getTimeout() + { + return $this->_timeout; + } + + private function setSslVersion($value) + { + $this->_sslVersion = $value; + } + + private function getSslVersion() + { + return $this->_sslVersion; + } + + public function getAcceptGzipEncoding() + { + return $this->_acceptGzipEncoding; + } + + private function setAcceptGzipEncoding($value) + { + $this->_acceptGzipEncoding = $value; + } + + public function getAccessToken() + { + return $this->_accessToken; + } + + public function isAccessToken() + { + return !empty($this->_accessToken); + } + + public function isClientCredentials() + { + return !empty($this->_clientId); + } /** * returns the base braintree gateway URL based on config values * * @access public - * @static * @param none * @return string braintree gateway URL */ - public static function baseUrl() + public function baseUrl() { - return self::protocol() . '://' . - self::serverName() . ':' . - self::portNumber(); + return sprintf('%s://%s:%d', $this->protocol(), $this->serverName(), $this->portNumber()); } + /** + * returns the base URL for Braintree's GraphQL endpoint based on config values + * + * @access public + * @param none + * @return string Braintree GraphQL URL + */ + public function graphQLBaseUrl() + { + return sprintf('%s://%s:%d/graphql', $this->protocol(), $this->graphQLServerName(), $this->graphQLPortNumber()); + } + /** * sets the merchant path based on merchant ID * * @access protected - * @static * @param none * @return string merchant path uri */ - public static function merchantPath() + public function merchantPath() { - return '/merchants/'.self::merchantId(); + return '/merchants/' . $this->_merchantId; } /** * sets the physical path for the location of the CA certs * * @access public - * @static * @param none * @return string filepath */ - public static function caFile($sslPath = NULL) + public function caFile($sslPath = NULL) { $sslPath = $sslPath ? $sslPath : DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'ssl' . DIRECTORY_SEPARATOR; - - $caPath = realpath( - dirname(__FILE__) . - $sslPath . 'api_braintreegateway_com.ca.crt' - ); + $caPath = __DIR__ . $sslPath . 'api_braintreegateway_com.ca.crt'; if (!file_exists($caPath)) { - throw new Braintree_Exception_SSLCaFileNotFound(); + throw new Exception\SSLCaFileNotFound(); } return $caPath; @@ -240,52 +506,65 @@ public static function caFile($sslPath = NULL) * returns the port number depending on environment * * @access public - * @static * @param none * @return int portnumber */ - public static function portNumber() + public function portNumber() { - if (self::sslOn()) { + if ($this->sslOn()) { return 443; } return getenv("GATEWAY_PORT") ? getenv("GATEWAY_PORT") : 3000; } + /** + * returns the graphql port number depending on environment + * + * @access public + * @param none + * @return int graphql portnumber + */ + public function graphQLPortNumber() + { + if ($this->sslOn()) { + return 443; + } + return getenv("GRAPHQL_PORT") ?: 8080; + } + /** * returns http protocol depending on environment * * @access public - * @static * @param none * @return string http || https */ - public static function protocol() + public function protocol() { - return self::sslOn() ? 'https' : 'http'; + return $this->sslOn() ? 'https' : 'http'; } /** * returns gateway server name depending on environment * * @access public - * @static * @param none * @return string server domain name */ - public static function serverName() + public function serverName() { - switch(self::environment()) { + switch($this->_environment) { case 'production': $serverName = 'api.braintreegateway.com'; break; case 'qa': - $serverName = 'qa.braintreegateway.com'; + $serverName = 'gateway.qa.braintreepayments.com'; break; case 'sandbox': $serverName = 'api.sandbox.braintreegateway.com'; break; case 'development': + case 'integration': default: $serverName = 'localhost'; break; @@ -294,18 +573,69 @@ public static function serverName() return $serverName; } + /** + * returns Braintree GraphQL server name depending on environment + * + * @access public + * @param none + * @return string graphql domain name + */ + public function graphQLServerName() + { + switch($this->_environment) { + case 'production': + $graphQLServerName = 'payments.braintree-api.com'; + break; + case 'qa': + $graphQLServerName = 'payments-qa.dev.braintree-api.com'; + break; + case 'sandbox': + $graphQLServerName = 'payments.sandbox.braintree-api.com'; + break; + case 'development': + case 'integration': + default: + $graphQLServerName = 'graphql.bt.local'; + break; + } + + return $graphQLServerName; + } + + public function authUrl() + { + switch($this->_environment) { + case 'production': + $serverName = 'https://auth.venmo.com'; + break; + case 'qa': + $serverName = 'https://auth.qa.venmo.com'; + break; + case 'sandbox': + $serverName = 'https://auth.sandbox.venmo.com'; + break; + case 'development': + case 'integration': + default: + $serverName = 'http://auth.venmo.dev:9292'; + break; + } + + return $serverName; + } + /** * returns boolean indicating SSL is on or off for this session, * depending on environment * * @access public - * @static * @param none * @return boolean */ - public static function sslOn() + public function sslOn() { - switch(self::environment()) { + switch($this->_environment) { + case 'integration': case 'development': $ssl = false; break; @@ -326,9 +656,10 @@ public static function sslOn() * @param string $message * */ - public static function logMessage($message) + public function logMessage($message) { error_log('[Braintree] ' . $message); } - } +Configuration::reset(); +class_alias('Braintree\Configuration', 'Braintree_Configuration'); diff --git a/lib/Braintree/ConnectedMerchantPayPalStatusChanged.php b/lib/Braintree/ConnectedMerchantPayPalStatusChanged.php new file mode 100644 index 0000000..ca4e7ef --- /dev/null +++ b/lib/Braintree/ConnectedMerchantPayPalStatusChanged.php @@ -0,0 +1,37 @@ +_initialize($attributes); + $instance->_attributes['merchantId'] = $instance->_attributes['merchantPublicId']; + + return $instance; + } + + /** + * @ignore + */ + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} +class_alias('Braintree\ConnectedMerchantPayPalStatusChanged', 'Braintree_ConnectedMerchantPayPalStatusChanged'); diff --git a/lib/Braintree/ConnectedMerchantStatusTransitioned.php b/lib/Braintree/ConnectedMerchantStatusTransitioned.php new file mode 100644 index 0000000..4614437 --- /dev/null +++ b/lib/Braintree/ConnectedMerchantStatusTransitioned.php @@ -0,0 +1,37 @@ +_initialize($attributes); + $instance->_attributes['merchantId'] = $instance->_attributes['merchantPublicId']; + + return $instance; + } + + /** + * @ignore + */ + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} +class_alias('Braintree\ConnectedMerchantStatusTransitioned', 'Braintree_ConnectedMerchantStatusTransitioned'); diff --git a/lib/Braintree/CredentialsParser.php b/lib/Braintree/CredentialsParser.php new file mode 100644 index 0000000..034d973 --- /dev/null +++ b/lib/Braintree/CredentialsParser.php @@ -0,0 +1,147 @@ + $value) { + if ($kind == 'clientId') { + $this->_clientId = $value; + } + if ($kind == 'clientSecret') { + $this->_clientSecret = $value; + } + if ($kind == 'accessToken') { + $this->_accessToken = $value; + } + } + $this->parse(); + } + + /** + * + * @access protected + * @static + * @var array valid environments, used for validation + */ + private static $_validEnvironments = [ + 'development', + 'integration', + 'sandbox', + 'production', + 'qa', + ]; + + + public function parse() + { + $environments = []; + if (!empty($this->_clientId)) { + $environments[] = ['clientId', $this->_parseClientCredential('clientId', $this->_clientId, 'client_id')]; + } + if (!empty($this->_clientSecret)) { + $environments[] = ['clientSecret', $this->_parseClientCredential('clientSecret', $this->_clientSecret, 'client_secret')]; + } + if (!empty($this->_accessToken)) { + $environments[] = ['accessToken', $this->_parseAccessToken()]; + } + + $checkEnv = $environments[0]; + foreach ($environments as $env) { + if ($env[1] !== $checkEnv[1]) { + throw new Exception\Configuration( + 'Mismatched credential environments: ' . $checkEnv[0] . ' environment is ' . $checkEnv[1] . + ' and ' . $env[0] . ' environment is ' . $env[1]); + } + } + + self::assertValidEnvironment($checkEnv[1]); + $this->_environment = $checkEnv[1]; + } + + public static function assertValidEnvironment($environment) { + if (!in_array($environment, self::$_validEnvironments)) { + throw new Exception\Configuration('"' . + $environment . '" is not a valid environment.'); + } + } + + private function _parseClientCredential($credentialType, $value, $expectedValuePrefix) + { + $explodedCredential = explode('$', $value); + if (sizeof($explodedCredential) != 3) { + throw new Exception\Configuration('Incorrect ' . $credentialType . ' format. Expected: type$environment$token'); + } + + $gotValuePrefix = $explodedCredential[0]; + $environment = $explodedCredential[1]; + $token = $explodedCredential[2]; + + if ($gotValuePrefix != $expectedValuePrefix) { + throw new Exception\Configuration('Value passed for ' . $credentialType . ' is not a ' . $credentialType); + } + + return $environment; + } + + private function _parseAccessToken() + { + $accessTokenExploded = explode('$', $this->_accessToken); + if (sizeof($accessTokenExploded) != 4) { + throw new Exception\Configuration('Incorrect accessToken syntax. Expected: type$environment$merchant_id$token'); + } + + $gotValuePrefix = $accessTokenExploded[0]; + $environment = $accessTokenExploded[1]; + $merchantId = $accessTokenExploded[2]; + $token = $accessTokenExploded[3]; + + if ($gotValuePrefix != 'access_token') { + throw new Exception\Configuration('Value passed for accessToken is not an accessToken'); + } + + $this->_merchantId = $merchantId; + return $environment; + } + + public function getClientId() + { + return $this->_clientId; + } + + public function getClientSecret() + { + return $this->_clientSecret; + } + + public function getAccessToken() + { + return $this->_accessToken; + } + + public function getEnvironment() + { + return $this->_environment; + } + + public function getMerchantId() + { + return $this->_merchantId; + } +} +class_alias('Braintree\CredentialsParser', 'Braintree_CredentialsParser'); diff --git a/lib/Braintree/CreditCard.php b/lib/Braintree/CreditCard.php index 74ecff3..a889545 100644 --- a/lib/Braintree/CreditCard.php +++ b/lib/Braintree/CreditCard.php @@ -1,40 +1,48 @@ == More information == * - * For more detailed information on CreditCards, see {@link http://www.braintreepayments.com/gateway/credit-card-api http://www.braintreepaymentsolutions.com/gateway/credit-card-api}
- * For more detailed information on CreditCard verifications, see {@link http://www.braintreepayments.com/gateway/credit-card-verification-api http://www.braintreepaymentsolutions.com/gateway/credit-card-verification-api} + * For more detailed information on CreditCards, see {@link https://developers.braintreepayments.com/reference/response/credit-card/php https://developers.braintreepayments.com/reference/response/credit-card/php}
+ * For more detailed information on CreditCard verifications, see {@link https://developers.braintreepayments.com/reference/response/credit-card-verification/php https://developers.braintreepayments.com/reference/response/credit-card-verification/php} * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions * - * @property-read string $billingAddress + * @property-read \Braintree\Address $billingAddress * @property-read string $bin * @property-read string $cardType * @property-read string $cardholderName - * @property-read string $createdAt + * @property-read string $commercial + * @property-read \DateTime $createdAt * @property-read string $customerId + * @property-read string $customerLocation + * @property-read string $debit + * @property-read boolean $default + * @property-read string $durbinRegulated * @property-read string $expirationDate * @property-read string $expirationMonth * @property-read string $expirationYear + * @property-read boolean $expired + * @property-read boolean $healthcare * @property-read string $imageUrl + * @property-read string $issuingBank * @property-read string $last4 * @property-read string $maskedNumber + * @property-read string $payroll + * @property-read string $prepaid + * @property-read string $productId + * @property-read \Braintree\Subscription[] $subscriptions * @property-read string $token - * @property-read string $updatedAt + * @property-read string $uniqueNumberIdentifier + * @property-read \DateTime $updatedAt + * @property-read \Braintree\CreditCardVerification|null $verification */ -class Braintree_CreditCard extends Braintree +class CreditCard extends Base { // Card Type const AMEX = 'American Express'; @@ -42,18 +50,20 @@ class Braintree_CreditCard extends Braintree const CHINA_UNION_PAY = 'China UnionPay'; const DINERS_CLUB_INTERNATIONAL = 'Diners Club'; const DISCOVER = 'Discover'; + const ELO = 'Elo'; const JCB = 'JCB'; const LASER = 'Laser'; const MAESTRO = 'Maestro'; + const UK_MAESTRO = 'UK Maestro'; const MASTER_CARD = 'MasterCard'; const SOLO = 'Solo'; const SWITCH_TYPE = 'Switch'; const VISA = 'Visa'; const UNKNOWN = 'Unknown'; - // Credit card origination location - const INTERNATIONAL = "international"; - const US = "us"; + // Credit card origination location + const INTERNATIONAL = "international"; + const US = "us"; const PREPAID_YES = 'Yes'; const PREPAID_NO = 'No'; @@ -81,523 +91,242 @@ class Braintree_CreditCard extends Braintree const COUNTRY_OF_ISSUANCE_UNKNOWN = "Unknown"; const ISSUING_BANK_UNKNOWN = "Unknown"; + const PRODUCT_ID_UNKNOWN = "Unknown"; - public static function create($attribs) - { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/payment_methods', array('credit_card' => $attribs)); - } - + /* instance methods */ /** - * attempts the create operation assuming all data will validate - * returns a Braintree_CreditCard object instead of a Result + * returns false if default is null or false * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError + * @return boolean */ - public static function createNoValidate($attribs) + public function isDefault() { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); + return $this->default; } + /** - * create a customer from a TransparentRedirect operation + * checks whether the card is expired based on the current date * - * @access public - * @param array $attribs - * @return object + * @return boolean */ - public static function createFromTransparentRedirect($queryString) + public function isExpired() { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doCreate( - '/payment_methods/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return $this->expired; } /** + * checks whether the card is associated with venmo sdk * - * @access public - * @param none - * @return string + * @return boolean */ - public static function createCreditCardUrl() + public function isVenmoSdk() { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/payment_methods/all/create_via_transparent_redirect_request'; + return $this->venmoSdk; } /** - * returns a ResourceCollection of expired credit cards - * @return object ResourceCollection + * sets instance properties from an array of values + * + * @access protected + * @param array $creditCardAttribs array of creditcard data + * @return void */ - public static function expired() + protected function _initialize($creditCardAttribs) { - $response = Braintree_Http::post("/payment_methods/all/expired_ids"); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetchExpired', - 'methodArgs' => array() - ); - - return new Braintree_ResourceCollection($response, $pager); - } + // set the attributes + $this->_attributes = $creditCardAttribs; - public static function fetchExpired($ids) - { - $response = Braintree_Http::post("/payment_methods/all/expired", array('search' => array('ids' => $ids))); + // map each address into its own object + $billingAddress = isset($creditCardAttribs['billingAddress']) ? + Address::factory($creditCardAttribs['billingAddress']) : + null; - return Braintree_Util::extractattributeasarray( - $response['paymentMethods'], - 'creditCard' - ); - } - /** - * returns a ResourceCollection of credit cards expiring between start/end - * - * @return object ResourceCollection - */ - public static function expiringBetween($startDate, $endDate) - { - $queryPath = '/payment_methods/all/expiring_ids?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate); - $response = Braintree_Http::post($queryPath); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetchExpiring', - 'methodArgs' => array($startDate, $endDate) - ); - - return new Braintree_ResourceCollection($response, $pager); - } + $subscriptionArray = []; + if (isset($creditCardAttribs['subscriptions'])) { + foreach ($creditCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } - public static function fetchExpiring($startDate, $endDate, $ids) - { - $queryPath = '/payment_methods/all/expiring?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate); - $response = Braintree_Http::post($queryPath, array('search' => array('ids' => $ids))); + $this->_set('subscriptions', $subscriptionArray); + $this->_set('billingAddress', $billingAddress); + $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); + $this->_set('maskedNumber', $this->bin . '******' . $this->last4); - return Braintree_Util::extractAttributeAsArray( - $response['paymentMethods'], - 'creditCard' - ); - } + if(isset($creditCardAttribs['verifications']) && count($creditCardAttribs['verifications']) > 0) { + $verifications = $creditCardAttribs['verifications']; + usort($verifications, [$this, '_compareCreatedAtOnVerifications']); - /** - * find a creditcard by token - * - * @access public - * @param string $token credit card unique id - * @return object Braintree_CreditCard - * @throws Braintree_Exception_NotFound - */ - public static function find($token) - { - self::_validateId($token); - try { - $response = Braintree_Http::get('/payment_methods/'.$token); - return self::factory($response['creditCard']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'credit card with token ' . $token . ' not found' - ); + $this->_set('verification', CreditCardVerification::factory($verifications[0])); } - } - /** - * create a credit on the card for the passed transaction - * - * @access public - * @param array $attribs - * @return object Braintree_Result_Successful or Braintree_Result_Error - */ - public static function credit($token, $transactionAttribs) + private function _compareCreatedAtOnVerifications($verificationAttrib1, $verificationAttrib2) { - self::_validateId($token); - return Braintree_Transaction::credit( - array_merge( - $transactionAttribs, - array('paymentMethodToken' => $token) - ) - ); + return ($verificationAttrib2['createdAt'] < $verificationAttrib1['createdAt']) ? -1 : 1; } /** - * create a credit on this card, assuming validations will pass + * returns false if comparing object is not a CreditCard, + * or is a CreditCard with a different id * - * returns a Braintree_Transaction object on success - * - * @access public - * @param array $attribs - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationError + * @param object $otherCreditCard customer to compare against + * @return boolean */ - public static function creditNoValidate($token, $transactionAttribs) + public function isEqual($otherCreditCard) { - $result = self::credit($token, $transactionAttribs); - return self::returnObjectOrThrowException('Transaction', $result); + return !($otherCreditCard instanceof self) ? false : $this->token === $otherCreditCard->token; } /** - * create a new sale for the current card - * - * @param string $token - * @param array $transactionAttribs - * @return object Braintree_Result_Successful or Braintree_Result_Error - * @see Braintree_Transaction::sale() + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string */ - public static function sale($token, $transactionAttribs) + public function __toString() { - self::_validateId($token); - return Braintree_Transaction::sale( - array_merge( - $transactionAttribs, - array('paymentMethodToken' => $token) - ) - ); + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; } /** - * create a new sale using this card, assuming validations will pass - * - * returns a Braintree_Transaction object on success + * factory method: returns an instance of CreditCard + * to the requesting method, with populated properties * - * @access public - * @param array $transactionAttribs - * @param string $token - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationsFailed - * @see Braintree_Transaction::sale() + * @ignore + * @return CreditCard */ - public static function saleNoValidate($token, $transactionAttribs) + public static function factory($attributes) { - $result = self::sale($token, $transactionAttribs); - return self::returnObjectOrThrowException('Transaction', $result); - } + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; - /** - * updates the creditcard record - * - * if calling this method in static context, $token - * is the 2nd attribute. $token is not sent in object context. - * - * @access public - * @param array $attributes - * @param string $token (optional) - * @return object Braintree_Result_Successful or Braintree_Result_Error - */ - public static function update($token, $attributes) - { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - self::_validateId($token); - return self::_doUpdate('put', '/payment_methods/' . $token, array('creditCard' => $attributes)); + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; } - /** - * update a creditcard record, assuming validations will pass - * - * if calling this method in static context, $token - * is the 2nd attribute. $token is not sent in object context. - * returns a Braintree_CreditCard object on success - * - * @access public - * @param array $attributes - * @param string $token - * @return object Braintree_CreditCard - * @throws Braintree_Exception_ValidationsFailed - */ - public static function updateNoValidate($token, $attributes) + + // static methods redirecting to gateway + + public static function create($attribs) { - $result = self::update($token, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); + return Configuration::gateway()->creditCard()->create($attribs); } - /** - * - * @access public - * @param none - * @return string - */ - public static function updateCreditCardUrl() + + public static function createNoValidate($attribs) { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/payment_methods/all/update_via_transparent_redirect_request'; + return Configuration::gateway()->creditCard()->createNoValidate($attribs); } - /** - * update a customer from a TransparentRedirect operation - * - * @access public - * @param array $attribs - * @return object - */ - public static function updateFromTransparentRedirect($queryString) + public static function createFromTransparentRedirect($queryString) { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doUpdate( - 'post', - '/payment_methods/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return Configuration::gateway()->creditCard()->createFromTransparentRedirect($queryString); } - /* instance methods */ - /** - * returns false if default is null or false - * - * @return boolean - */ - public function isDefault() + public static function createCreditCardUrl() { - return $this->default; + return Configuration::gateway()->creditCard()->createCreditCardUrl(); } - /** - * checks whether the card is expired based on the current date - * - * @return boolean - */ - public function isExpired() + public static function expired() { - return $this->expired; + return Configuration::gateway()->creditCard()->expired(); } - /** - * checks whether the card is associated with venmo sdk - * - * @return boolean - */ - public function isVenmoSdk() + public static function fetchExpired($ids) { - return $this->venmoSdk; + return Configuration::gateway()->creditCard()->fetchExpired($ids); } - public static function delete($token) + public static function expiringBetween($startDate, $endDate) { - self::_validateId($token); - Braintree_Http::delete('/payment_methods/' . $token); - return new Braintree_Result_Successful(); + return Configuration::gateway()->creditCard()->expiringBetween($startDate, $endDate); } - /** - * sets instance properties from an array of values - * - * @access protected - * @param array $creditCardAttribs array of creditcard data - * @return none - */ - protected function _initialize($creditCardAttribs) + public static function fetchExpiring($startDate, $endDate, $ids) { - // set the attributes - $this->_attributes = $creditCardAttribs; - - // map each address into its own object - $billingAddress = isset($creditCardAttribs['billingAddress']) ? - Braintree_Address::factory($creditCardAttribs['billingAddress']) : - null; - - $subscriptionArray = array(); - if (isset($creditCardAttribs['subscriptions'])) { - foreach ($creditCardAttribs['subscriptions'] AS $subscription) { - $subscriptionArray[] = Braintree_Subscription::factory($subscription); - } - } - - $this->_set('subscriptions', $subscriptionArray); - $this->_set('billingAddress', $billingAddress); - $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); - $this->_set('maskedNumber', $this->bin . '******' . $this->last4); + return Configuration::gateway()->creditCard()->fetchExpiring($startDate, $endDate, $ids); } - /** - * returns false if comparing object is not a Braintree_CreditCard, - * or is a Braintree_CreditCard with a different id - * - * @param object $otherCreditCard customer to compare against - * @return boolean - */ - public function isEqual($otherCreditCard) + public static function find($token) { - return !($otherCreditCard instanceof Braintree_CreditCard) ? false : $this->token === $otherCreditCard->token; + return Configuration::gateway()->creditCard()->find($token); } - private static function baseOptions() + public static function fromNonce($nonce) { - return array('makeDefault', 'verificationMerchantAccountId', 'verifyCard', 'venmoSdkSession'); + return Configuration::gateway()->creditCard()->fromNonce($nonce); } - private static function baseSignature($options) + public static function credit($token, $transactionAttribs) { - return array( - 'billingAddressId', 'cardholderName', 'cvv', 'number', 'deviceSessionId', - 'expirationDate', 'expirationMonth', 'expirationYear', 'token', 'venmoSdkPaymentMethodCode', - 'deviceData', 'fraudMerchantId', - array('options' => $options), - array( - 'billingAddress' => array( - 'firstName', - 'lastName', - 'company', - 'countryCodeAlpha2', - 'countryCodeAlpha3', - 'countryCodeNumeric', - 'countryName', - 'extendedAddress', - 'locality', - 'region', - 'postalCode', - 'streetAddress' - ), - ), - ); + return Configuration::gateway()->creditCard()->credit($token, $transactionAttribs); } - public static function createSignature() + public static function creditNoValidate($token, $transactionAttribs) { - $options = self::baseOptions(); - $options[] = "failOnDuplicatePaymentMethod"; - $signature = self::baseSignature($options); - $signature[] = 'customerId'; - return $signature; + return Configuration::gateway()->creditCard()->creditNoValidate($token, $transactionAttribs); } - public static function updateSignature() + public static function sale($token, $transactionAttribs) { - $signature = self::baseSignature(self::baseOptions()); - - $updateExistingBillingSignature = array( - array( - 'options' => array( - 'updateExisting' - ) - ) - ); - - foreach($signature AS $key => $value) { - if(is_array($value) and array_key_exists('billingAddress', $value)) { - $signature[$key]['billingAddress'] = array_merge_recursive($value['billingAddress'], $updateExistingBillingSignature); - } - } - - return $signature; + return Configuration::gateway()->creditCard()->sale($token, $transactionAttribs); } - /** - * sends the create request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - public static function _doCreate($url, $params) + public static function saleNoValidate($token, $transactionAttribs) { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->creditCard()->saleNoValidate($token, $transactionAttribs); } - /** - * create a printable representation of the object as: - * ClassName[property=value, property=value] - * @return string - */ - public function __toString() + public static function update($token, $attributes) { - return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + return Configuration::gateway()->creditCard()->update($token, $attributes); } - /** - * verifies that a valid credit card token is being used - * @ignore - * @param string $token - * @throws InvalidArgumentException - */ - private static function _validateId($token = null) + public static function updateNoValidate($token, $attributes) { - if (empty($token)) { - throw new InvalidArgumentException( - 'expected credit card id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $token)) { - throw new InvalidArgumentException( - $token . ' is an invalid credit card id.' - ); - } + return Configuration::gateway()->creditCard()->updateNoValidate($token, $attributes); } - /** - * sends the update request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - private static function _doUpdate($httpVerb, $url, $params) + public static function updateCreditCardUrl() { - $response = Braintree_Http::$httpVerb($url, $params); - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->creditCard()->updateCreditCardUrl(); } - /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_CreditCard object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. - * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected - */ - private static function _verifyGatewayResponse($response) + public static function updateFromTransparentRedirect($queryString) { - if (isset($response['creditCard'])) { - // return a populated instance of Braintree_Address - return new Braintree_Result_Successful( - self::factory($response['creditCard']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected address or apiErrorResponse" - ); - } + return Configuration::gateway()->creditCard()->updateFromTransparentRedirect($queryString); } - /** - * factory method: returns an instance of Braintree_CreditCard - * to the requesting method, with populated properties - * - * @ignore - * @return object instance of Braintree_CreditCard - */ - public static function factory($attributes) + public static function delete($token) { - $defaultAttributes = array( - 'bin' => '', - 'expirationMonth' => '', - 'expirationYear' => '', - 'last4' => '', - ); - - $instance = new self(); - $instance->_initialize(array_merge($defaultAttributes, $attributes)); - return $instance; + return Configuration::gateway()->creditCard()->delete($token); + } + + /** @return array */ + public static function allCardTypes() + { + return [ + CreditCard::AMEX, + CreditCard::CARTE_BLANCHE, + CreditCard::CHINA_UNION_PAY, + CreditCard::DINERS_CLUB_INTERNATIONAL, + CreditCard::DISCOVER, + CreditCard::ELO, + CreditCard::JCB, + CreditCard::LASER, + CreditCard::MAESTRO, + CreditCard::MASTER_CARD, + CreditCard::SOLO, + CreditCard::SWITCH_TYPE, + CreditCard::VISA, + CreditCard::UNKNOWN + ]; } } +class_alias('Braintree\CreditCard', 'Braintree_CreditCard'); diff --git a/lib/Braintree/CreditCardGateway.php b/lib/Braintree/CreditCardGateway.php new file mode 100644 index 0000000..5f8ce04 --- /dev/null +++ b/lib/Braintree/CreditCardGateway.php @@ -0,0 +1,486 @@ +== More information == + * + * For more detailed information on CreditCards, see {@link https://developers.braintreepayments.com/reference/response/credit-card/php https://developers.braintreepayments.com/reference/response/credit-card/php}
+ * For more detailed information on CreditCard verifications, see {@link https://developers.braintreepayments.com/reference/response/credit-card-verification/php https://developers.braintreepayments.com/reference/response/credit-card-verification/php} + * + * @package Braintree + * @category Resources + */ +class CreditCardGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function create($attribs) + { + Util::verifyKeys(self::createSignature(), $attribs); + return $this->_doCreate('/payment_methods', ['credit_card' => $attribs]); + } + + /** + * attempts the create operation assuming all data will validate + * returns a CreditCard object instead of a Result + * + * @access public + * @param array $attribs + * @return CreditCard + * @throws Exception\ValidationError + */ + public function createNoValidate($attribs) + { + $result = $this->create($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + /** + * create a customer from a TransparentRedirect operation + * + * @deprecated since version 2.3.0 + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function createFromTransparentRedirect($queryString) + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE); + $params = TransparentRedirect::parseAndValidateQueryString( + $queryString + ); + return $this->_doCreate( + '/payment_methods/all/confirm_transparent_redirect_request', + ['id' => $params['id']] + ); + } + + /** + * + * @deprecated since version 2.3.0 + * @access public + * @param none + * @return string + */ + public function createCreditCardUrl() + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE); + return $this->_config->baseUrl() . $this->_config->merchantPath(). + '/payment_methods/all/create_via_transparent_redirect_request'; + } + + /** + * returns a ResourceCollection of expired credit cards + * @return ResourceCollection + */ + public function expired() + { + $path = $this->_config->merchantPath() . '/payment_methods/all/expired_ids'; + $response = $this->_http->post($path); + $pager = [ + 'object' => $this, + 'method' => 'fetchExpired', + 'methodArgs' => [] + ]; + + return new ResourceCollection($response, $pager); + } + + public function fetchExpired($ids) + { + $path = $this->_config->merchantPath() . "/payment_methods/all/expired"; + $response = $this->_http->post($path, ['search' => ['ids' => $ids]]); + + return Util::extractattributeasarray( + $response['paymentMethods'], + 'creditCard' + ); + } + /** + * returns a ResourceCollection of credit cards expiring between start/end + * + * @return ResourceCollection + */ + public function expiringBetween($startDate, $endDate) + { + $queryPath = $this->_config->merchantPath() . '/payment_methods/all/expiring_ids?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate); + $response = $this->_http->post($queryPath); + $pager = [ + 'object' => $this, + 'method' => 'fetchExpiring', + 'methodArgs' => [$startDate, $endDate] + ]; + + return new ResourceCollection($response, $pager); + } + + public function fetchExpiring($startDate, $endDate, $ids) + { + $queryPath = $this->_config->merchantPath() . '/payment_methods/all/expiring?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate); + $response = $this->_http->post($queryPath, ['search' => ['ids' => $ids]]); + + return Util::extractAttributeAsArray( + $response['paymentMethods'], + 'creditCard' + ); + } + + /** + * find a creditcard by token + * + * @access public + * @param string $token credit card unique id + * @return CreditCard + * @throws Exception\NotFound + */ + public function find($token) + { + $this->_validateId($token); + try { + $path = $this->_config->merchantPath() . '/payment_methods/credit_card/' . $token; + $response = $this->_http->get($path); + return CreditCard::factory($response['creditCard']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'credit card with token ' . $token . ' not found' + ); + } + + } + + /** + * Convert a payment method nonce to a credit card + * + * @access public + * @param string $nonce payment method nonce + * @return CreditCard + * @throws Exception\NotFound + */ + public function fromNonce($nonce) + { + $this->_validateId($nonce, "nonce"); + try { + $path = $this->_config->merchantPath() . '/payment_methods/from_nonce/' . $nonce; + $response = $this->_http->get($path); + return CreditCard::factory($response['creditCard']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'credit card with nonce ' . $nonce . ' locked, consumed or not found' + ); + } + + } + + /** + * create a credit on the card for the passed transaction + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function credit($token, $transactionAttribs) + { + $this->_validateId($token); + return Transaction::credit( + array_merge( + $transactionAttribs, + ['paymentMethodToken' => $token] + ) + ); + } + + /** + * create a credit on this card, assuming validations will pass + * + * returns a Transaction object on success + * + * @access public + * @param array $attribs + * @return Transaction + * @throws Exception\ValidationError + */ + public function creditNoValidate($token, $transactionAttribs) + { + $result = $this->credit($token, $transactionAttribs); + return Util::returnObjectOrThrowException('Braintree\Transaction', $result); + } + + /** + * create a new sale for the current card + * + * @param string $token + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + * @see Transaction::sale() + */ + public function sale($token, $transactionAttribs) + { + $this->_validateId($token); + return Transaction::sale( + array_merge( + $transactionAttribs, + ['paymentMethodToken' => $token] + ) + ); + } + + /** + * create a new sale using this card, assuming validations will pass + * + * returns a Transaction object on success + * + * @access public + * @param array $transactionAttribs + * @param string $token + * @return Transaction + * @throws Exception\ValidationsFailed + * @see Transaction::sale() + */ + public function saleNoValidate($token, $transactionAttribs) + { + $result = $this->sale($token, $transactionAttribs); + return Util::returnObjectOrThrowException('Braintree\Transaction', $result); + } + + /** + * updates the creditcard record + * + * if calling this method in context, $token + * is the 2nd attribute. $token is not sent in object context. + * + * @access public + * @param array $attributes + * @param string $token (optional) + * @return Result\Successful|Result\Error + */ + public function update($token, $attributes) + { + Util::verifyKeys(self::updateSignature(), $attributes); + $this->_validateId($token); + return $this->_doUpdate('put', '/payment_methods/credit_card/' . $token, ['creditCard' => $attributes]); + } + + /** + * update a creditcard record, assuming validations will pass + * + * if calling this method in context, $token + * is the 2nd attribute. $token is not sent in object context. + * returns a CreditCard object on success + * + * @access public + * @param array $attributes + * @param string $token + * @return CreditCard + * @throws Exception\ValidationsFailed + */ + public function updateNoValidate($token, $attributes) + { + $result = $this->update($token, $attributes); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + /** + * + * @access public + * @param none + * @return string + */ + public function updateCreditCardUrl() + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE); + return $this->_config->baseUrl() . $this->_config->merchantPath() . + '/payment_methods/all/update_via_transparent_redirect_request'; + } + + /** + * update a customer from a TransparentRedirect operation + * + * @deprecated since version 2.3.0 + * @access public + * @param array $attribs + * @return object + */ + public function updateFromTransparentRedirect($queryString) + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE); + $params = TransparentRedirect::parseAndValidateQueryString( + $queryString + ); + return $this->_doUpdate( + 'post', + '/payment_methods/all/confirm_transparent_redirect_request', + ['id' => $params['id']] + ); + } + + public function delete($token) + { + $this->_validateId($token); + $path = $this->_config->merchantPath() . '/payment_methods/credit_card/' . $token; + $this->_http->delete($path); + return new Result\Successful(); + } + + private static function baseOptions() + { + return ['makeDefault', 'verificationMerchantAccountId', 'verifyCard', 'verificationAmount', 'venmoSdkSession']; + } + + private static function baseSignature($options) + { + return [ + 'billingAddressId', 'cardholderName', 'cvv', 'number', 'deviceSessionId', + 'expirationDate', 'expirationMonth', 'expirationYear', 'token', 'venmoSdkPaymentMethodCode', + 'deviceData', 'fraudMerchantId', 'paymentMethodNonce', + ['options' => $options], + [ + 'billingAddress' => self::billingAddressSignature() + ], + ]; + } + + public static function billingAddressSignature() + { + return [ + 'firstName', + 'lastName', + 'company', + 'countryCodeAlpha2', + 'countryCodeAlpha3', + 'countryCodeNumeric', + 'countryName', + 'extendedAddress', + 'locality', + 'region', + 'postalCode', + 'streetAddress' + ]; + } + + public static function createSignature() + { + $options = self::baseOptions(); + $options[] = "failOnDuplicatePaymentMethod"; + $signature = self::baseSignature($options); + $signature[] = 'customerId'; + return $signature; + } + + public static function updateSignature() + { + $options = self::baseOptions(); + $options[] = "failOnDuplicatePaymentMethod"; + $signature = self::baseSignature($options); + + $updateExistingBillingSignature = [ + [ + 'options' => [ + 'updateExisting' + ] + ] + ]; + + foreach($signature AS $key => $value) { + if(is_array($value) and array_key_exists('billingAddress', $value)) { + $signature[$key]['billingAddress'] = array_merge_recursive($value['billingAddress'], $updateExistingBillingSignature); + } + } + + return $signature; + } + + /** + * sends the create request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * verifies that a valid credit card identifier is being used + * @ignore + * @param string $identifier + * @param Optional $string $identifierType type of identifier supplied, default "token" + * @throws InvalidArgumentException + */ + private function _validateId($identifier = null, $identifierType = "token") + { + if (empty($identifier)) { + throw new InvalidArgumentException( + 'expected credit card id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) { + throw new InvalidArgumentException( + $identifier . ' is an invalid credit card ' . $identifierType . '.' + ); + } + } + + /** + * sends the update request to the gateway + * + * @ignore + * @param string $url + * @param array $params + * @return mixed + */ + private function _doUpdate($httpVerb, $subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->$httpVerb($fullPath, $params); + return $this->_verifyGatewayResponse($response); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new CreditCard object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['creditCard'])) { + // return a populated instance of Address + return new Result\Successful( + CreditCard::factory($response['creditCard']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected address or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\CreditCardGateway', 'Braintree_CreditCardGateway'); diff --git a/lib/Braintree/CreditCardVerification.php b/lib/Braintree/CreditCardVerification.php index c20409d..d33bb47 100644 --- a/lib/Braintree/CreditCardVerification.php +++ b/lib/Braintree/CreditCardVerification.php @@ -1,5 +1,31 @@ name] = $term->toparam(); - } - $criteria["ids"] = Braintree_CreditCardVerificationSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/verifications/advanced_search', array('search' => $criteria)); + Util::verifyKeys(self::createSignature(), $attributes); + return Configuration::gateway()->creditCardVerification()->create($attributes); + } - return Braintree_Util::extractattributeasarray( - $response['creditCardVerifications'], - 'verification' - ); + public static function fetch($query, $ids) + { + return Configuration::gateway()->creditCardVerification()->fetch($query, $ids); } public static function search($query) { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - - $response = Braintree_Http::post('/verifications/advanced_search_ids', array('search' => $criteria)); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); + return Configuration::gateway()->creditCardVerification()->search($query); + } - return new Braintree_ResourceCollection($response, $pager); + public static function createSignature() + { + return [ + ['options' => ['amount', 'merchantAccountId']], + ['creditCard' => + [ + 'cardholderName', 'cvv', 'number', + 'expirationDate', 'expirationMonth', 'expirationYear', + ['billingAddress' => CreditCardGateway::billingAddressSignature()] + ] + ]]; } } +class_alias('Braintree\CreditCardVerification', 'Braintree_CreditCardVerification'); diff --git a/lib/Braintree/CreditCardVerificationGateway.php b/lib/Braintree/CreditCardVerificationGateway.php new file mode 100644 index 0000000..869bd61 --- /dev/null +++ b/lib/Braintree/CreditCardVerificationGateway.php @@ -0,0 +1,74 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function create($attributes) + { + $response = $this->_http->post($this->_config->merchantPath() . "/verifications", ['verification' => $attributes]); + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + + if(isset($response['verification'])){ + return new Result\Successful( + CreditCardVerification::factory($response['verification']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected transaction or apiErrorResponse" + ); + } + } + + public function fetch($query, $ids) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $criteria["ids"] = CreditCardVerificationSearch::ids()->in($ids)->toparam(); + $path = $this->_config->merchantPath() . '/verifications/advanced_search'; + $response = $this->_http->post($path, ['search' => $criteria]); + + return Util::extractattributeasarray( + $response['creditCardVerifications'], + 'verification' + ); + } + + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + $path = $this->_config->merchantPath() . '/verifications/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } +} +class_alias('Braintree\CreditCardVerificationGateway', 'Braintree_CreditCardVerificationGateway'); diff --git a/lib/Braintree/CreditCardVerificationSearch.php b/lib/Braintree/CreditCardVerificationSearch.php index e799200..2dadff9 100644 --- a/lib/Braintree/CreditCardVerificationSearch.php +++ b/lib/Braintree/CreditCardVerificationSearch.php @@ -1,33 +1,56 @@ == More information == * - * For more detailed information on Customers, see {@link http://www.braintreepayments.com/gateway/customer-api http://www.braintreepaymentsolutions.com/gateway/customer-api} + * For more detailed information on Customers, see {@link https://developers.braintreepayments.com/reference/response/customer/php https://developers.braintreepayments.com/reference/response/customer/php} * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions * - * @property-read array $addresses + * @property-read \Braintree\Address[] $addresses + * @property-read \Braintree\AndroidPayCard[] $androidPayCards + * @property-read \Braintree\AmexExpressCheckoutCard[] $amexExpressCheckoutCards + * @property-read \Braintree\ApplePayCard[] $applePayCards + * @property-read \Braintree\CoinbaseAccount[] $coinbaseAccounts * @property-read string $company - * @property-read string $createdAt - * @property-read array $creditCards + * @property-read \DateTime $createdAt + * @property-read \Braintree\CreditCard[] $creditCards * @property-read array $customFields custom fields passed with the request * @property-read string $email * @property-read string $fax * @property-read string $firstName * @property-read string $id * @property-read string $lastName + * @property-read \Braintree\MasterpassCard[] $masterpassCards + * @property-read \Braintree\PaymentMethod[] $paymentMethods + * @property-read \Braintree\PayPalAccount[] $paypalAccounts * @property-read string $phone - * @property-read string $updatedAt + * @property-read \Braintree\SamsungPayCard[] $samsungPayCards + * @property-read \DateTime $updatedAt + * @property-read \Braintree\UsBankAccount[] $usBankAccounts + * @property-read \Braintree\VenmoAccount[] $venmoAccounts + * @property-read \Braintree\VisaCheckoutCard[] $visaCheckoutCards * @property-read string $website */ -class Braintree_Customer extends Braintree +class Customer extends Base { + /** + * + * @return Customer[] + */ public static function all() { - $response = Braintree_Http::post('/customers/advanced_search_ids'); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array(array()) - ); - - return new Braintree_ResourceCollection($response, $pager); + return Configuration::gateway()->customer()->all(); } + /** + * + * @param string $query + * @param int[] $ids + * @return Customer|Customer[] + */ public static function fetch($query, $ids) { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - $criteria["ids"] = Braintree_CustomerSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/customers/advanced_search', array('search' => $criteria)); - - return Braintree_Util::extractattributeasarray( - $response['customers'], - 'customer' - ); + return Configuration::gateway()->customer()->fetch($query, $ids); } /** - * Creates a customer using the given +attributes+. If :id is not passed, - * the gateway will generate it. * - * - * $result = Braintree_Customer::create(array( - * 'first_name' => 'John', - * 'last_name' => 'Smith', - * 'company' => 'Smith Co.', - * 'email' => 'john@smith.com', - * 'website' => 'www.smithco.com', - * 'fax' => '419-555-1234', - * 'phone' => '614-555-1234' - * )); - * if($result->success) { - * echo 'Created customer ' . $result->customer->id; - * } else { - * echo 'Could not create customer, see result->errors'; - * } - * - * - * @access public * @param array $attribs - * @return object Result, either Successful or Error + * @return Result\Successful|Result\Error */ - public static function create($attribs = array()) + public static function create($attribs = []) { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/customers', array('customer' => $attribs)); + return Configuration::gateway()->customer()->create($attribs); } /** - * attempts the create operation assuming all data will validate - * returns a Braintree_Customer object instead of a Result * - * @access public * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError + * @return Customer */ - public static function createNoValidate($attribs = array()) + public static function createNoValidate($attribs = []) { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); + return Configuration::gateway()->customer()->createNoValidate($attribs); } + /** - * create a customer from a TransparentRedirect operation - * - * @access public - * @param array $attribs - * @return object + * @deprecated since version 2.3.0 + * @param string $queryString + * @return Result\Successful */ public static function createFromTransparentRedirect($queryString) { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doCreate( - '/customers/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return Configuration::gateway()->customer()->createFromTransparentRedirect($queryString); } /** - * - * @access public - * @param none + * @deprecated since version 2.3.0 * @return string */ public static function createCustomerUrl() { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/customers/all/create_via_transparent_redirect_request'; + return Configuration::gateway()->customer()->createCustomerUrl(); } - /** - * creates a full array signature of a valid create request - * @return array gateway create request format - */ - public static function createSignature() - { - - $creditCardSignature = Braintree_CreditCard::createSignature(); - unset($creditCardSignature['customerId']); - $signature = array( - 'id', 'company', 'email', 'fax', 'firstName', - 'lastName', 'phone', 'website', 'deviceData', - 'deviceSessionId', 'fraudMerchantId', - array('creditCard' => $creditCardSignature), - array('customFields' => array('_anyKey_')), - ); - return $signature; - } - - /** - * creates a full array signature of a valid update request - * @return array update request format - */ - public static function updateSignature() - { - $creditCardSignature = Braintree_CreditCard::updateSignature(); - - foreach($creditCardSignature AS $key => $value) { - if(is_array($value) and array_key_exists('options', $value)) { - array_push($creditCardSignature[$key]['options'], 'updateExistingToken'); - } - } - - $signature = array( - 'id', 'company', 'email', 'fax', 'firstName', - 'lastName', 'phone', 'website', 'deviceData', - 'deviceSessionId', 'fraudMerchantId', - array('creditCard' => $creditCardSignature), - array('customFields' => array('_anyKey_')), - ); - return $signature; - } - - - /** - * find a customer by id * - * @access public - * @param string id customer Id - * @return object Braintree_Customer - * @throws Braintree_Exception_NotFound + * @throws Exception\NotFound + * @param string $id customer id + * @return Customer */ - public static function find($id) + public static function find($id, $associationFilterId = null) { - self::_validateId($id); - try { - $response = Braintree_Http::get('/customers/'.$id); - return self::factory($response['customer']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'customer with id ' . $id . ' not found' - ); - } - + return Configuration::gateway()->customer()->find($id, $associationFilterId); } /** - * credit a customer for the passed transaction * - * @access public - * @param array $attribs - * @return object Braintree_Result_Successful or Braintree_Result_Error + * @param int $customerId + * @param array $transactionAttribs + * @return Result\Successful|Result\Error */ public static function credit($customerId, $transactionAttribs) { - self::_validateId($customerId); - return Braintree_Transaction::credit( - array_merge($transactionAttribs, - array('customerId' => $customerId) - ) - ); + return Configuration::gateway()->customer()->credit($customerId, $transactionAttribs); } /** - * credit a customer, assuming validations will pass * - * returns a Braintree_Transaction object on success - * - * @access public - * @param array $attribs - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationError + * @throws Exception\ValidationError + * @param type $customerId + * @param type $transactionAttribs + * @return Transaction */ public static function creditNoValidate($customerId, $transactionAttribs) { - $result = self::credit($customerId, $transactionAttribs); - return self::returnObjectOrThrowException('Braintree_Transaction', $result); + return Configuration::gateway()->customer()->creditNoValidate($customerId, $transactionAttribs); } /** - * delete a customer by id * - * @param string $customerId + * @throws Exception on invalid id or non-200 http response code + * @param int $customerId + * @return Result\Successful */ public static function delete($customerId) { - self::_validateId($customerId); - Braintree_Http::delete('/customers/' . $customerId); - return new Braintree_Result_Successful(); + return Configuration::gateway()->customer()->delete($customerId); } /** - * create a new sale for a customer * - * @param string $customerId + * @param int $customerId * @param array $transactionAttribs - * @return object Braintree_Result_Successful or Braintree_Result_Error - * @see Braintree_Transaction::sale() + * @return Transaction */ public static function sale($customerId, $transactionAttribs) { - self::_validateId($customerId); - return Braintree_Transaction::sale( - array_merge($transactionAttribs, - array('customerId' => $customerId) - ) - ); + return Configuration::gateway()->customer()->sale($customerId, $transactionAttribs); } /** - * create a new sale for a customer, assuming validations will pass * - * returns a Braintree_Transaction object on success - * @access public - * @param string $customerId + * @param int $customerId * @param array $transactionAttribs - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationsFailed - * @see Braintree_Transaction::sale() + * @return Transaction */ public static function saleNoValidate($customerId, $transactionAttribs) { - $result = self::sale($customerId, $transactionAttribs); - return self::returnObjectOrThrowException('Braintree_Transaction', $result); + return Configuration::gateway()->customer()->saleNoValidate($customerId, $transactionAttribs); } /** - * Returns a ResourceCollection of customers matching the search query. * - * If query is a string, the search will be a basic search. - * If query is a hash, the search will be an advanced search. - * For more detailed information and examples, see {@link http://www.braintreepayments.com/gateway/customer-api#searching http://www.braintreepaymentsolutions.com/gateway/customer-api} - * - * @param mixed $query search query - * @param array $options options such as page number - * @return object Braintree_ResourceCollection * @throws InvalidArgumentException + * @param string $query + * @return ResourceCollection */ public static function search($query) { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - - $response = Braintree_Http::post('/customers/advanced_search_ids', array('search' => $criteria)); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); - - return new Braintree_ResourceCollection($response, $pager); + return Configuration::gateway()->customer()->search($query); } /** - * updates the customer record * - * if calling this method in static context, customerId - * is the 2nd attribute. customerId is not sent in object context. - * - * @access public + * @throws Exception\Unexpected + * @param int $customerId * @param array $attributes - * @param string $customerId (optional) - * @return object Braintree_Result_Successful or Braintree_Result_Error + * @return Result\Successful|Result\Error */ public static function update($customerId, $attributes) { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - self::_validateId($customerId); - return self::_doUpdate( - 'put', - '/customers/' . $customerId, - array('customer' => $attributes) - ); + return Configuration::gateway()->customer()->update($customerId, $attributes); } /** - * update a customer record, assuming validations will pass - * - * if calling this method in static context, customerId - * is the 2nd attribute. customerId is not sent in object context. - * returns a Braintree_Customer object on success * - * @access public + * @throws Exception\Unexpected + * @param int $customerId * @param array $attributes - * @param string $customerId - * @return object Braintree_Customer - * @throws Braintree_Exception_ValidationsFailed + * @return CustomerGateway */ public static function updateNoValidate($customerId, $attributes) { - $result = self::update($customerId, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); + return Configuration::gateway()->customer()->updateNoValidate($customerId, $attributes); } + /** * - * @access public - * @param none + * @deprecated since version 2.3.0 * @return string */ public static function updateCustomerUrl() { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/customers/all/update_via_transparent_redirect_request'; + return Configuration::gateway()->customer()->updateCustomerUrl(); } /** - * update a customer from a TransparentRedirect operation * - * @access public - * @param array $attribs - * @return object + * @deprecated since version 2.3.0 + * @param string $queryString + * @return Result\Successful|Result\Error */ public static function updateFromTransparentRedirect($queryString) { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doUpdate( - 'post', - '/customers/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return Configuration::gateway()->customer()->updateFromTransparentRedirect($queryString); } /* instance methods */ @@ -395,32 +229,121 @@ public static function updateFromTransparentRedirect($queryString) * @ignore * @access protected * @param array $customerAttribs array of customer data - * @return none */ protected function _initialize($customerAttribs) { - // set the attributes $this->_attributes = $customerAttribs; - // map each address into its own object - $addressArray = array(); + $addressArray = []; if (isset($customerAttribs['addresses'])) { foreach ($customerAttribs['addresses'] AS $address) { - $addressArray[] = Braintree_Address::factory($address); + $addressArray[] = Address::factory($address); } } $this->_set('addresses', $addressArray); - // map each creditcard into its own object - $ccArray = array(); + $creditCardArray = []; if (isset($customerAttribs['creditCards'])) { foreach ($customerAttribs['creditCards'] AS $creditCard) { - $ccArray[] = Braintree_CreditCard::factory($creditCard); + $creditCardArray[] = CreditCard::factory($creditCard); } } - $this->_set('creditCards', $ccArray); + $this->_set('creditCards', $creditCardArray); + $coinbaseAccountArray = []; + if (isset($customerAttribs['coinbaseAccounts'])) { + foreach ($customerAttribs['coinbaseAccounts'] AS $coinbaseAccount) { + $coinbaseAccountArray[] = CoinbaseAccount::factory($coinbaseAccount); + } + } + $this->_set('coinbaseAccounts', $coinbaseAccountArray); + + $paypalAccountArray = []; + if (isset($customerAttribs['paypalAccounts'])) { + foreach ($customerAttribs['paypalAccounts'] AS $paypalAccount) { + $paypalAccountArray[] = PayPalAccount::factory($paypalAccount); + } + } + $this->_set('paypalAccounts', $paypalAccountArray); + + $applePayCardArray = []; + if (isset($customerAttribs['applePayCards'])) { + foreach ($customerAttribs['applePayCards'] AS $applePayCard) { + $applePayCardArray[] = ApplePayCard::factory($applePayCard); + } + } + $this->_set('applePayCards', $applePayCardArray); + + $androidPayCardArray = []; + if (isset($customerAttribs['androidPayCards'])) { + foreach ($customerAttribs['androidPayCards'] AS $androidPayCard) { + $androidPayCardArray[] = AndroidPayCard::factory($androidPayCard); + } + } + $this->_set('androidPayCards', $androidPayCardArray); + + $amexExpressCheckoutCardArray = []; + if (isset($customerAttribs['amexExpressCheckoutCards'])) { + foreach ($customerAttribs['amexExpressCheckoutCards'] AS $amexExpressCheckoutCard) { + $amexExpressCheckoutCardArray[] = AmexExpressCheckoutCard::factory($amexExpressCheckoutCard); + } + } + $this->_set('amexExpressCheckoutCards', $amexExpressCheckoutCardArray); + + $venmoAccountArray = array(); + if (isset($customerAttribs['venmoAccounts'])) { + foreach ($customerAttribs['venmoAccounts'] AS $venmoAccount) { + $venmoAccountArray[] = VenmoAccount::factory($venmoAccount); + } + } + $this->_set('venmoAccounts', $venmoAccountArray); + + $visaCheckoutCardArray = []; + if (isset($customerAttribs['visaCheckoutCards'])) { + foreach ($customerAttribs['visaCheckoutCards'] AS $visaCheckoutCard) { + $visaCheckoutCardArray[] = VisaCheckoutCard::factory($visaCheckoutCard); + } + } + $this->_set('visaCheckoutCards', $visaCheckoutCardArray); + + $masterpassCardArray = []; + if (isset($customerAttribs['masterpassCards'])) { + foreach ($customerAttribs['masterpassCards'] AS $masterpassCard) { + $masterpassCardArray[] = MasterpassCard::factory($masterpassCard); + } + } + $this->_set('masterpassCards', $masterpassCardArray); + + $samsungPayCardArray = []; + if (isset($customerAttribs['samsungPayCards'])) { + foreach ($customerAttribs['samsungPayCards'] AS $samsungPayCard) { + $samsungPayCardArray[] = SamsungPayCard::factory($samsungPayCard); + } + } + $this->_set('samsungPayCards', $samsungPayCardArray); + + $usBankAccountArray = array(); + if (isset($customerAttribs['usBankAccounts'])) { + foreach ($customerAttribs['usBankAccounts'] AS $usBankAccount) { + $usBankAccountArray[] = UsBankAccount::factory($usBankAccount); + } + } + $this->_set('usBankAccounts', $usBankAccountArray); + + $this->_set('paymentMethods', array_merge( + $this->creditCards, + $this->paypalAccounts, + $this->applePayCards, + $this->coinbaseAccounts, + $this->androidPayCards, + $this->amexExpressCheckoutCards, + $this->venmoAccounts, + $this->visaCheckoutCards, + $this->masterpassCards, + $this->samsungPayCards, + $this->usBankAccounts + )); } /** @@ -430,19 +353,47 @@ protected function _initialize($customerAttribs) public function __toString() { return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + Util::attributesToString($this->_attributes) .']'; } /** - * returns false if comparing object is not a Braintree_Customer, - * or is a Braintree_Customer with a different id + * returns false if comparing object is not a Customer, + * or is a Customer with a different id * * @param object $otherCust customer to compare against * @return boolean */ public function isEqual($otherCust) { - return !($otherCust instanceof Braintree_Customer) ? false : $this->id === $otherCust->id; + return !($otherCust instanceof Customer) ? false : $this->id === $otherCust->id; + } + + /** + * returns an array containt all of the customer's payment methods + * + * @deprecated since version 3.1.0 - use the paymentMethods property directly + * + * @return array + */ + public function paymentMethods() + { + return $this->paymentMethods; + } + + /** + * returns the customer's default payment method + * + * @return CreditCard|PayPalAccount + */ + public function defaultPaymentMethod() + { + $defaultPaymentMethods = array_filter($this->paymentMethods, 'Braintree\Customer::_defaultPaymentMethodFilter'); + return current($defaultPaymentMethods); + } + + public static function _defaultPaymentMethodFilter($paymentMethod) + { + return $paymentMethod->isDefault(); } /* private class properties */ @@ -451,7 +402,7 @@ public function isEqual($otherCust) * @access protected * @var array registry of customer data */ - protected $_attributes = array( + protected $_attributes = [ 'addresses' => '', 'company' => '', 'creditCards' => '', @@ -464,101 +415,21 @@ public function isEqual($otherCust) 'createdAt' => '', 'updatedAt' => '', 'website' => '', - ); - - /** - * sends the create request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - public static function _doCreate($url, $params) - { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); - } + ]; /** - * verifies that a valid customer id is being used - * @ignore - * @param string customer id - * @throws InvalidArgumentException - */ - private static function _validateId($id = null) { - if (empty($id)) { - throw new InvalidArgumentException( - 'expected customer id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid customer id.' - ); - } - } - - - /* private class methods */ - - /** - * sends the update request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - private static function _doUpdate($httpVerb, $url, $params) - { - $response = Braintree_Http::$httpVerb($url, $params); - - return self::_verifyGatewayResponse($response); - } - - /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_Customer object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. - * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected - */ - private static function _verifyGatewayResponse($response) - { - if (isset($response['customer'])) { - // return a populated instance of Braintree_Customer - return new Braintree_Result_Successful( - self::factory($response['customer']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected customer or apiErrorResponse" - ); - } - } - - /** - * factory method: returns an instance of Braintree_Customer + * factory method: returns an instance of Customer * to the requesting method, with populated properties * * @ignore - * @return object instance of Braintree_Customer + * @param array $attributes + * @return Customer */ public static function factory($attributes) { - $instance = new self(); + $instance = new Customer(); $instance->_initialize($attributes); return $instance; } - } +class_alias('Braintree\Customer', 'Braintree_Customer'); diff --git a/lib/Braintree/CustomerGateway.php b/lib/Braintree/CustomerGateway.php new file mode 100644 index 0000000..f010082 --- /dev/null +++ b/lib/Braintree/CustomerGateway.php @@ -0,0 +1,668 @@ +== More information == + * + * For more detailed information on Customers, see {@link https://developers.braintreepayments.com/reference/response/customer/php https://developers.braintreepayments.com/reference/response/customer/php} + * + * @package Braintree + * @category Resources + */ +class CustomerGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function all() + { + $path = $this->_config->merchantPath() . '/customers/advanced_search_ids'; + $response = $this->_http->post($path); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [[]] + ]; + + return new ResourceCollection($response, $pager); + } + + public function fetch($query, $ids) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $criteria["ids"] = CustomerSearch::ids()->in($ids)->toparam(); + $path = $this->_config->merchantPath() . '/customers/advanced_search'; + $response = $this->_http->post($path, ['search' => $criteria]); + + return Util::extractattributeasarray( + $response['customers'], + 'customer' + ); + } + + /** + * Creates a customer using the given +attributes+. If :id is not passed, + * the gateway will generate it. + * + * + * $result = Customer::create(array( + * 'first_name' => 'John', + * 'last_name' => 'Smith', + * 'company' => 'Smith Co.', + * 'email' => 'john@smith.com', + * 'website' => 'www.smithco.com', + * 'fax' => '419-555-1234', + * 'phone' => '614-555-1234' + * )); + * if($result->success) { + * echo 'Created customer ' . $result->customer->id; + * } else { + * echo 'Could not create customer, see result->errors'; + * } + * + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function create($attribs = []) + { + Util::verifyKeys(self::createSignature(), $attribs); + return $this->_doCreate('/customers', ['customer' => $attribs]); + } + + /** + * attempts the create operation assuming all data will validate + * returns a Customer object instead of a Result + * + * @access public + * @param array $attribs + * @return Customer + * @throws Exception\ValidationError + */ + public function createNoValidate($attribs = []) + { + $result = $this->create($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + /** + * create a customer from a TransparentRedirect operation + * + * @deprecated since version 2.3.0 + * @access public + * @param array $attribs + * @return Customer + */ + public function createFromTransparentRedirect($queryString) + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE); + $params = TransparentRedirect::parseAndValidateQueryString( + $queryString + ); + return $this->_doCreate( + '/customers/all/confirm_transparent_redirect_request', + ['id' => $params['id']] + ); + } + + /** + * + * @deprecated since version 2.3.0 + * @access public + * @param none + * @return string + */ + public function createCustomerUrl() + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE); + return $this->_config->baseUrl() . $this->_config->merchantPath() . + '/customers/all/create_via_transparent_redirect_request'; + } + + + /** + * creates a full array signature of a valid create request + * @return array gateway create request format + */ + public static function createSignature() + { + $creditCardSignature = CreditCardGateway::createSignature(); + unset($creditCardSignature[array_search('customerId', $creditCardSignature)]); + + $signature = [ + 'id', 'company', 'email', 'fax', 'firstName', + 'lastName', 'phone', 'website', 'deviceData', + 'deviceSessionId', 'fraudMerchantId', 'paymentMethodNonce', + ['riskData' => + ['customerBrowser', 'customerIp', 'customer_browser', 'customer_ip'] + ], + ['creditCard' => $creditCardSignature], + ['customFields' => ['_anyKey_']], + ['options' => [ + ['paypal' => [ + 'payee_email', + 'payeeEmail', + 'order_id', + 'orderId', + 'custom_field', + 'customField', + 'description', + 'amount', + ['shipping' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'postalCode', 'region', + 'streetAddress'], + ], + ]] + ]], + ]; + return $signature; + } + + /** + * creates a full array signature of a valid update request + * @return array update request format + */ + public static function updateSignature() + { + $creditCardSignature = CreditCardGateway::updateSignature(); + + foreach($creditCardSignature AS $key => $value) { + if(is_array($value) and array_key_exists('options', $value)) { + array_push($creditCardSignature[$key]['options'], 'updateExistingToken'); + } + } + + $signature = [ + 'id', 'company', 'email', 'fax', 'firstName', + 'lastName', 'phone', 'website', 'deviceData', + 'deviceSessionId', 'fraudMerchantId', 'paymentMethodNonce', 'defaultPaymentMethodToken', + ['creditCard' => $creditCardSignature], + ['customFields' => ['_anyKey_']], + ['options' => [ + ['paypal' => [ + 'payee_email', + 'payeeEmail', + 'order_id', + 'orderId', + 'custom_field', + 'customField', + 'description', + 'amount', + ['shipping' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'postalCode', 'region', + 'streetAddress'], + ], + ]], + ]], + ]; + return $signature; + } + + + /** + * find a customer by id + * + * @access public + * @param string id customer Id + * @param string associationFilterId association filter Id + * @return Customer|boolean The customer object or false if the request fails. + * @throws Exception\NotFound + */ + public function find($id, $associationFilterId = null) + { + $this->_validateId($id); + try { + $queryParams = ''; + if ($associationFilterId) { + $queryParams = '?association_filter_id=' . $associationFilterId; + } + $path = $this->_config->merchantPath() . '/customers/' . $id . $queryParams; + $response = $this->_http->get($path); + return Customer::factory($response['customer']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'customer with id ' . $id . ' not found' + ); + } + } + + /** + * credit a customer for the passed transaction + * + * @access public + * @param int $customerId + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + */ + public function credit($customerId, $transactionAttribs) + { + $this->_validateId($customerId); + return Transaction::credit( + array_merge($transactionAttribs, + ['customerId' => $customerId] + ) + ); + } + + /** + * credit a customer, assuming validations will pass + * + * returns a Transaction object on success + * + * @access public + * @param int $customerId + * @param array $transactionAttribs + * @return Transaction + * @throws Exception\ValidationError + */ + public function creditNoValidate($customerId, $transactionAttribs) + { + $result = $this->credit($customerId, $transactionAttribs); + return Util::returnObjectOrThrowException('Braintree\Transaction', $result); + } + + /** + * delete a customer by id + * + * @param string $customerId + */ + public function delete($customerId) + { + $this->_validateId($customerId); + $path = $this->_config->merchantPath() . '/customers/' . $customerId; + $this->_http->delete($path); + return new Result\Successful(); + } + + /** + * create a new sale for a customer + * + * @param string $customerId + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + * @see Transaction::sale() + */ + public function sale($customerId, $transactionAttribs) + { + $this->_validateId($customerId); + return Transaction::sale( + array_merge($transactionAttribs, + ['customerId' => $customerId] + ) + ); + } + + /** + * create a new sale for a customer, assuming validations will pass + * + * returns a Transaction object on success + * @access public + * @param string $customerId + * @param array $transactionAttribs + * @return Transaction + * @throws Exception\ValidationsFailed + * @see Transaction::sale() + */ + public function saleNoValidate($customerId, $transactionAttribs) + { + $result = $this->sale($customerId, $transactionAttribs); + return Util::returnObjectOrThrowException('Braintree\Transaction', $result); + } + + /** + * Returns a ResourceCollection of customers matching the search query. + * + * If query is a string, the search will be a basic search. + * If query is a hash, the search will be an advanced search. + * For more detailed information and examples, see {@link https://developers.braintreepayments.com/reference/request/customer/search/php https://developers.braintreepayments.com/reference/request/customer/search/php} + * + * @param mixed $query search query + * @return ResourceCollection + * @throws InvalidArgumentException + */ + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $result = $term->toparam(); + if(is_null($result) || empty($result)) { + throw new InvalidArgumentException('Operator must be provided'); + } + + $criteria[$term->name] = $term->toparam(); + } + + $path = $this->_config->merchantPath() . '/customers/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } + + /** + * updates the customer record + * + * if calling this method in static context, customerId + * is the 2nd attribute. customerId is not sent in object context. + * + * @access public + * @param string $customerId (optional) + * @param array $attributes + * @return Result\Successful|Result\Error + */ + public function update($customerId, $attributes) + { + Util::verifyKeys(self::updateSignature(), $attributes); + $this->_validateId($customerId); + return $this->_doUpdate( + 'put', + '/customers/' . $customerId, + ['customer' => $attributes] + ); + } + + /** + * update a customer record, assuming validations will pass + * + * if calling this method in static context, customerId + * is the 2nd attribute. customerId is not sent in object context. + * returns a Customer object on success + * + * @access public + * @param string $customerId + * @param array $attributes + * @return Customer + * @throws Exception\ValidationsFailed + */ + public function updateNoValidate($customerId, $attributes) + { + $result = $this->update($customerId, $attributes); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + /** + * + * @deprecated since version 2.3.0 + * @access public + * @return string + */ + public function updateCustomerUrl() + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE); + return $this->_config->baseUrl() . $this->_config->merchantPath() . + '/customers/all/update_via_transparent_redirect_request'; + } + + /** + * update a customer from a TransparentRedirect operation + * + * @deprecated since version 2.3.0 + * @access public + * @param string $queryString + * @return object + */ + public function updateFromTransparentRedirect($queryString) + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE); + $params = TransparentRedirect::parseAndValidateQueryString( + $queryString + ); + return $this->_doUpdate( + 'post', + '/customers/all/confirm_transparent_redirect_request', + ['id' => $params['id']] + ); + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @ignore + * @access protected + * @param array $customerAttribs array of customer data + * @return void + */ + protected function _initialize($customerAttribs) + { + // set the attributes + $this->_attributes = $customerAttribs; + + // map each address into its own object + $addressArray = []; + if (isset($customerAttribs['addresses'])) { + + foreach ($customerAttribs['addresses'] AS $address) { + $addressArray[] = Address::factory($address); + } + } + $this->_set('addresses', $addressArray); + + // map each creditCard into its own object + $creditCardArray = []; + if (isset($customerAttribs['creditCards'])) { + foreach ($customerAttribs['creditCards'] AS $creditCard) { + $creditCardArray[] = CreditCard::factory($creditCard); + } + } + $this->_set('creditCards', $creditCardArray); + + // map each coinbaseAccount into its own object + $coinbaseAccountArray = []; + if (isset($customerAttribs['coinbaseAccounts'])) { + foreach ($customerAttribs['coinbaseAccounts'] AS $coinbaseAccount) { + $coinbaseAccountArray[] = CoinbaseAccount::factory($coinbaseAccount); + } + } + $this->_set('coinbaseAccounts', $coinbaseAccountArray); + + // map each paypalAccount into its own object + $paypalAccountArray = []; + if (isset($customerAttribs['paypalAccounts'])) { + foreach ($customerAttribs['paypalAccounts'] AS $paypalAccount) { + $paypalAccountArray[] = PayPalAccount::factory($paypalAccount); + } + } + $this->_set('paypalAccounts', $paypalAccountArray); + + // map each applePayCard into its own object + $applePayCardArray = []; + if (isset($customerAttribs['applePayCards'])) { + foreach ($customerAttribs['applePayCards'] AS $applePayCard) { + $applePayCardArray[] = ApplePayCard::factory($applePayCard); + } + } + $this->_set('applePayCards', $applePayCardArray); + + // map each androidPayCard into its own object + $androidPayCardArray = []; + if (isset($customerAttribs['androidPayCards'])) { + foreach ($customerAttribs['androidPayCards'] AS $androidPayCard) { + $androidPayCardArray[] = AndroidPayCard::factory($androidPayCard); + } + } + $this->_set('androidPayCards', $androidPayCardArray); + + $this->_set('paymentMethods', array_merge($this->creditCards, $this->paypalAccounts, $this->applePayCards, $this->coinbaseAccounts, $this->androidPayCards)); + } + + /** + * returns a string representation of the customer + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + + /** + * returns false if comparing object is not a Customer, + * or is a Customer with a different id + * + * @param object $otherCust customer to compare against + * @return boolean + */ + public function isEqual($otherCust) + { + return !($otherCust instanceof Customer) ? false : $this->id === $otherCust->id; + } + + /** + * returns an array containt all of the customer's payment methods + * + * @return array + */ + public function paymentMethods() + { + return $this->paymentMethods; + } + + /** + * returns the customer's default payment method + * + * @return CreditCard|PayPalAccount|ApplePayCard|AndroidPayCard + */ + public function defaultPaymentMethod() + { + $defaultPaymentMethods = array_filter($this->paymentMethods, 'Braintree\\Customer::_defaultPaymentMethodFilter'); + return current($defaultPaymentMethods); + } + + public static function _defaultPaymentMethodFilter($paymentMethod) + { + return $paymentMethod->isDefault(); + } + + /* private class properties */ + + /** + * @access protected + * @var array registry of customer data + */ + protected $_attributes = [ + 'addresses' => '', + 'company' => '', + 'creditCards' => '', + 'email' => '', + 'fax' => '', + 'firstName' => '', + 'id' => '', + 'lastName' => '', + 'phone' => '', + 'createdAt' => '', + 'updatedAt' => '', + 'website' => '', + ]; + + /** + * sends the create request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * verifies that a valid customer id is being used + * @ignore + * @param string customer id + * @throws InvalidArgumentException + */ + private function _validateId($id = null) { + if (is_null($id)) { + throw new InvalidArgumentException( + 'expected customer id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { + throw new InvalidArgumentException( + $id . ' is an invalid customer id.' + ); + } + } + + + /* private class methods */ + + /** + * sends the update request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + private function _doUpdate($httpVerb, $subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->$httpVerb($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new Customer object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['customer'])) { + // return a populated instance of Customer + return new Result\Successful( + Customer::factory($response['customer']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected customer or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\CustomerGateway', 'Braintree_CustomerGateway'); diff --git a/lib/Braintree/CustomerSearch.php b/lib/Braintree/CustomerSearch.php index 5de6cee..3bc734e 100644 --- a/lib/Braintree/CustomerSearch.php +++ b/lib/Braintree/CustomerSearch.php @@ -1,30 +1,34 @@ _set('merchantAccount', - Braintree_MerchantAccount::factory($disbursementAttribs['merchantAccount']) + MerchantAccount::factory($disbursementAttribs['merchantAccount']) ); } } public function transactions() { - $collection = Braintree_Transaction::search(array( - Braintree_TransactionSearch::ids()->in($this->transactionIds) - )); + $collection = Transaction::search([ + TransactionSearch::ids()->in($this->transactionIds), + ]); return $collection; } @@ -33,17 +39,28 @@ public static function factory($attributes) public function __toString() { - $display = array( + $display = [ 'id', 'merchantAccountDetails', 'exceptionMessage', 'amount', 'disbursementDate', 'followUpAction', 'retry', 'success', - 'transactionIds' - ); + 'transactionIds', 'disbursementType' + ]; - $displayAttributes = array(); + $displayAttributes = []; foreach ($display AS $attrib) { $displayAttributes[$attrib] = $this->$attrib; } return __CLASS__ . '[' . - Braintree_Util::attributesToString($displayAttributes) .']'; + Util::attributesToString($displayAttributes) .']'; + } + + public function isDebit() + { + return $this->disbursementType == Disbursement::TYPE_DEBIT; + } + + public function isCredit() + { + return $this->disbursementType == Disbursement::TYPE_CREDIT; } } +class_alias('Braintree\Disbursement', 'Braintree_Disbursement'); diff --git a/lib/Braintree/DisbursementDetails.php b/lib/Braintree/DisbursementDetails.php index 471a163..6c5e182 100644 --- a/lib/Braintree/DisbursementDetails.php +++ b/lib/Braintree/DisbursementDetails.php @@ -1,31 +1,24 @@ disbursementDate); } } +class_alias('Braintree\DisbursementDetails', 'Braintree_DisbursementDetails'); diff --git a/lib/Braintree/Discount.php b/lib/Braintree/Discount.php index 0c8e12e..efa3d72 100644 --- a/lib/Braintree/Discount.php +++ b/lib/Braintree/Discount.php @@ -1,22 +1,35 @@ $response['discounts']); - - return Braintree_Util::extractAttributeAsArray( - $discounts, - 'discount' - ); - } +namespace Braintree; +/** + * @property-read string $amount + * @property-read \DateTime $createdAt + * @property-read int|null $currentBillingCycle + * @property-read string $description + * @property-read string $id + * @property-read string|null $kind + * @property-read string $merchantId + * @property-read string $name + * @property-read boolean $neverExpires + * @property-read int|null $numberOfBillingCycles + * @property-read int|null $quantity + * @property-read \DateTime $updatedAt + */ +class Discount extends Modification +{ public static function factory($attributes) { $instance = new self(); $instance->_initialize($attributes); return $instance; } + + + // static methods redirecting to gateway + + public static function all() + { + return Configuration::gateway()->discount()->all(); + } } +class_alias('Braintree\Discount', 'Braintree_Discount'); diff --git a/lib/Braintree/DiscountGateway.php b/lib/Braintree/DiscountGateway.php new file mode 100644 index 0000000..d26672a --- /dev/null +++ b/lib/Braintree/DiscountGateway.php @@ -0,0 +1,31 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function all() + { + $path = $this->_config->merchantPath() . '/discounts'; + $response = $this->_http->get($path); + + $discounts = ["discount" => $response['discounts']]; + + return Util::extractAttributeAsArray( + $discounts, + 'discount' + ); + } +} +class_alias('Braintree\DiscountGateway', 'Braintree_DiscountGateway'); diff --git a/lib/Braintree/Dispute.php b/lib/Braintree/Dispute.php new file mode 100644 index 0000000..b2baa9e --- /dev/null +++ b/lib/Braintree/Dispute.php @@ -0,0 +1,191 @@ +_attributes = $disputeAttribs; + + if (isset($disputeAttribs['transaction'])) { + $transactionDetails = new Dispute\TransactionDetails($disputeAttribs['transaction']); + $this->_set('transactionDetails', $transactionDetails); + $this->_set('transaction', $transactionDetails); + } + + if (isset($disputeAttribs['evidence'])) { + $evidenceArray = array_map(function($evidence) { + return new Dispute\EvidenceDetails($evidence); + }, $disputeAttribs['evidence']); + $this->_set('evidence', $evidenceArray); + } + + if (isset($disputeAttribs['statusHistory'])) { + $statusHistoryArray = array_map(function($statusHistory) { + return new Dispute\StatusHistoryDetails($statusHistory); + }, $disputeAttribs['statusHistory']); + $this->_set('statusHistory', $statusHistoryArray); + } + + if (isset($disputeAttribs['transaction'])) { + $this->_set('transaction', + new Dispute\TransactionDetails($disputeAttribs['transaction']) + ); + } + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + public function __toString() + { + $display = [ + 'amount', 'reason', 'status', + 'replyByDate', 'receivedDate', 'currencyIsoCode' + ]; + + $displayAttributes = []; + foreach ($display AS $attrib) { + $displayAttributes[$attrib] = $this->$attrib; + } + return __CLASS__ . '[' . + Util::attributesToString($displayAttributes) .']'; + } + + /** + * Accepts a dispute, given a dispute ID + * + * @param string $id + */ + public static function accept($id) + { + return Configuration::gateway()->dispute()->accept($id); + } + + /** + * Adds file evidence to a dispute, given a dispute ID and a document ID + * + * @param string $disputeId + * @param string $documentIdOrRequest + */ + public static function addFileEvidence($disputeId, $documentIdOrRequest) + { + return Configuration::gateway()->dispute()->addFileEvidence($disputeId, $documentIdOrRequest); + } + + /** + * Adds text evidence to a dispute, given a dispute ID and content + * + * @param string $id + * @param string $contentOrRequest + */ + public static function addTextEvidence($id, $contentOrRequest) + { + return Configuration::gateway()->dispute()->addTextEvidence($id, $contentOrRequest); + } + + /** + * Finalize a dispute, given a dispute ID + * + * @param string $id + */ + public static function finalize($id) + { + return Configuration::gateway()->dispute()->finalize($id); + } + + /** + * Find a dispute, given a dispute ID + * + * @param string $id + */ + public static function find($id) + { + return Configuration::gateway()->dispute()->find($id); + } + + /** + * Remove evidence from a dispute, given a dispute ID and evidence ID + * + * @param string $disputeId + * @param string $evidenceId + */ + public static function removeEvidence($disputeId, $evidenceId) + { + return Configuration::gateway()->dispute()->removeEvidence($disputeId, $evidenceId); + } + + /** + * Search for Disputes, given a DisputeSearch query + * + * @param DisputeSearch $query + */ + public static function search($query) + { + return Configuration::gateway()->dispute()->search($query); + } +} +class_alias('Braintree\Dispute', 'Braintree_Dispute'); diff --git a/lib/Braintree/Dispute/EvidenceDetails.php b/lib/Braintree/Dispute/EvidenceDetails.php new file mode 100644 index 0000000..19d3c0a --- /dev/null +++ b/lib/Braintree/Dispute/EvidenceDetails.php @@ -0,0 +1,31 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /* public class methods */ + + /** + * Accepts a dispute, given a dispute ID + * + * @param string $id + */ + public function accept($id) + { + try { + if (trim($id) == "") { + throw new Exception\NotFound(); + } + + $path = $this->_config->merchantPath() . '/disputes/' . $id . '/accept'; + $response = $this->_http->put($path); + + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } + + return new Result\Successful(); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('dispute with id "' . $id . '" not found'); + } + } + + /** + * Adds file evidence to a dispute, given a dispute ID and a document ID + * + * @param string $disputeId + * @param string $documentIdOrRequest + */ + public function addFileEvidence($disputeId, $documentIdOrRequest) + { + $request = is_array($documentIdOrRequest) ? $documentIdOrRequest : ['documentId' => $documentIdOrRequest]; + + if (trim($disputeId) == "") { + throw new Exception\NotFound('dispute with id "' . $disputeId . '" not found'); + } + + if (trim($request['documentId']) == "") { + throw new Exception\NotFound('document with id "' . $request['documentId'] . '" not found'); + } + + try { + if (array_key_exists('category', $request)) { + if (trim($request['category']) == "") { + throw new InvalidArgumentException('category cannot be blank'); + } + } + + $request['document_upload_id'] = $request['documentId']; + unset($request['documentId']); + + $path = $this->_config->merchantPath() . '/disputes/' . $disputeId . '/evidence'; + $response = $this->_http->post($path, ['evidence' => $request]); + + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } + + if (isset($response['evidence'])) { + $evidence = new Dispute\EvidenceDetails($response['evidence']); + return new Result\Successful($evidence); + } + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('dispute with id "' . $disputeId . '" not found'); + } + } + + /** + * Adds text evidence to a dispute, given a dispute ID and content + * + * @param string $id + * @param string $content + */ + public function addTextEvidence($id, $contentOrRequest) + { + $request = is_array($contentOrRequest) ? $contentOrRequest : ['content' => $contentOrRequest]; + if (trim($request['content']) == "") { + throw new InvalidArgumentException('content cannot be blank'); + } + + try { + $evidence = [ + 'comments' => $request['content'], + ]; + + if (trim($id) == "") { + throw new Exception\NotFound(); + } + + if (array_key_exists('tag', $request)) { + $evidence['category'] = $request['tag']; + } + + if (array_key_exists('category', $request)) { + if (trim($request['category']) == "") { + throw new InvalidArgumentException('category cannot be blank'); + } + $evidence['category'] = $request['category']; + } + + if (array_key_exists('sequenceNumber', $request)) { + if (trim($request['sequenceNumber']) == "") { + throw new InvalidArgumentException('sequenceNumber cannot be blank'); + } else if ((string)(int)($request['sequenceNumber']) != $request['sequenceNumber']) { + throw new InvalidArgumentException('sequenceNumber must be an integer'); + } + $evidence['sequenceNumber'] = (int)$request['sequenceNumber']; + } + + $path = $this->_config->merchantPath() . '/disputes/' . $id . '/evidence'; + $response = $this->_http->post($path, [ + 'evidence' => $evidence + ]); + + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } + + if (isset($response['evidence'])) { + $evidence = new Dispute\EvidenceDetails($response['evidence']); + return new Result\Successful($evidence); + } + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('dispute with id "' . $id . '" not found'); + } + } + + /** + * Finalize a dispute, given a dispute ID + * + * @param string $id + */ + public function finalize($id) + { + try { + if (trim($id) == "") { + throw new Exception\NotFound(); + } + + $path = $this->_config->merchantPath() . '/disputes/' . $id . '/finalize'; + $response = $this->_http->put($path); + + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } + + return new Result\Successful(); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('dispute with id "' . $id . '" not found'); + } + } + + /** + * Find a dispute, given a dispute ID + * + * @param string $id + */ + public function find($id) + { + if (trim($id) == "") { + throw new Exception\NotFound('dispute with id "' . $id . '" not found'); + } + + try { + $path = $this->_config->merchantPath() . '/disputes/' . $id; + $response = $this->_http->get($path); + return Dispute::factory($response['dispute']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('dispute with id "' . $id . '" not found'); + } + } + + /** + * Remove evidence from a dispute, given a dispute ID and evidence ID + * + * @param string $disputeId + * @param string $evidenceId + */ + public function removeEvidence($disputeId, $evidenceId) + { + try { + if (trim($disputeId) == "" || trim($evidenceId) == "") { + throw new Exception\NotFound(); + } + + $path = $this->_config->merchantPath() . '/disputes/' . $disputeId . '/evidence/' . $evidenceId; + $response = $this->_http->delete($path); + + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } + + return new Result\Successful(); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('evidence with id "' . $evidenceId . '" for dispute with id "' . $disputeId . '" not found'); + } + } + + /** + * Search for Disputes, given a DisputeSearch query + * + * @param DisputeSearch $query + */ + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $pager = [ + 'object' => $this, + 'method' => 'fetchDisputes', + 'query' => $criteria + ]; + return new PaginatedCollection($pager); + } + + public function fetchDisputes($query, $page) + { + $response = $this->_http->post($this->_config->merchantPath() . '/disputes/advanced_search?page=' . $page, [ + 'search' => $query + ]); + $body = $response['disputes']; + $disputes = Util::extractattributeasarray($body, 'dispute'); + $totalItems = $body['totalItems'][0]; + $pageSize = $body['pageSize'][0]; + return new PaginatedResult($totalItems, $pageSize, $disputes); + } +} +class_alias('Braintree\DisputeGateway', 'Braintree_DisputeGateway'); diff --git a/lib/Braintree/DisputeSearch.php b/lib/Braintree/DisputeSearch.php new file mode 100644 index 0000000..b97c463 --- /dev/null +++ b/lib/Braintree/DisputeSearch.php @@ -0,0 +1,90 @@ + Braintree\DocumentUpload::EVIDENCE_DOCUMENT, + * "file" => $pngFile + * ]); + * + * For more information on DocumentUploads, see https://developers.braintreepayments.com/reference/request/document_upload/create + * + * @property-read string $contentType + * @property-read \DateTime $expiresAt + * @property-read string $id + * @property-read string $kind + * @property-read string $name + * @property-read int $size + */ +class DocumentUpload extends Base +{ + /* DocumentUpload Kind */ + const EVIDENCE_DOCUMENT = "evidence_document"; + + protected function _initialize($documentUploadAttribs) + { + $this->_attributes = $documentUploadAttribs; + } + + /** + * Creates a DocumentUpload object + * @param kind The kind of document + * @param file The open file to upload + * @throws InvalidArgumentException if the params are not expected + */ + public static function create($params) + { + return Configuration::gateway()->documentUpload()->create($params); + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } +} +class_alias('Braintree\DocumentUpload', 'Braintree_DocumentUpload'); diff --git a/lib/Braintree/DocumentUploadGateway.php b/lib/Braintree/DocumentUploadGateway.php new file mode 100644 index 0000000..2839968 --- /dev/null +++ b/lib/Braintree/DocumentUploadGateway.php @@ -0,0 +1,81 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /* public class methods */ + + /** + * Accepts a dispute, given a dispute ID + * + * @param string $id + */ + public function create($params) + { + Util::verifyKeys(self::createSignature(), $params); + + $file = $params['file']; + + if (!is_resource($file)) { + throw new InvalidArgumentException('file must be a stream resource'); + } + + $payload = [ + 'document_upload[kind]' => $params['kind'] + ]; + $path = $this->_config->merchantPath() . '/document_uploads/'; + $response = $this->_http->postMultipart($path, $payload, $file); + + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } + + if (isset($response['documentUpload'])) { + $documentUpload = DocumentUpload::factory($response['documentUpload']); + return new Result\Successful($documentUpload); + } + } + + public static function createSignature() + { + return [ + 'file', 'kind' + ]; + } +} +class_alias('Braintree\DocumentUploadGateway', 'Braintree_DocumentUploadGateway'); diff --git a/lib/Braintree/EndsWithNode.php b/lib/Braintree/EndsWithNode.php new file mode 100644 index 0000000..edccec2 --- /dev/null +++ b/lib/Braintree/EndsWithNode.php @@ -0,0 +1,23 @@ +name = $name; + $this->searchTerms = []; + } + + public function endsWith($value) + { + $this->searchTerms["ends_with"] = strval($value); + return $this; + } + + public function toParam() + { + return $this->searchTerms; + } +} +class_alias('Braintree\EndsWithNode', 'Braintree_EndsWithNode'); diff --git a/lib/Braintree/EqualityNode.php b/lib/Braintree/EqualityNode.php index 68c10b5..d91e1eb 100644 --- a/lib/Braintree/EqualityNode.php +++ b/lib/Braintree/EqualityNode.php @@ -1,6 +1,7 @@ _errors = - new Braintree_Error_ValidationErrorCollection($errorData); + new ValidationErrorCollection($errorData); } + /** + * Return count of items in collection + * Implements countable + * + * @return integer + */ + public function count() + { + return $this->deepSize(); + } /** * Returns all of the validation errors at all levels of nesting in a single, flat array. @@ -77,10 +81,10 @@ public function onHtmlField($field) $pieces = preg_split("/[\[\]]+/", $field, 0, PREG_SPLIT_NO_EMPTY); $errors = $this; foreach(array_slice($pieces, 0, -1) as $key) { - $errors = $errors->forKey(Braintree_Util::delimiterToCamelCase($key)); - if (!isset($errors)) { return array(); } + $errors = $errors->forKey(Util::delimiterToCamelCase($key)); + if (!isset($errors)) { return []; } } - $finalKey = Braintree_Util::delimiterToCamelCase(end($pieces)); + $finalKey = Util::delimiterToCamelCase(end($pieces)); return $errors->onAttribute($finalKey); } @@ -88,7 +92,7 @@ public function onHtmlField($field) * Returns the errors at the given nesting level (see forKey) in a single, flat array: * * - * $result = Braintree_Customer::create(...); + * $result = Customer::create(...); * $customerErrors = $result->errors->forKey('customer')->shallowAll(); * */ @@ -116,3 +120,4 @@ public function __toString() return sprintf('%s', $this->_errors); } } +class_alias('Braintree\Error\ErrorCollection', 'Braintree_Error_ErrorCollection'); diff --git a/lib/Braintree/Error/Validation.php b/lib/Braintree/Error/Validation.php index 8253ae6..79a4cc1 100644 --- a/lib/Braintree/Error/Validation.php +++ b/lib/Braintree/Error/Validation.php @@ -1,11 +1,7 @@ == More information == * - * For more detailed information on Validation errors, see {@link http://www.braintreepayments.com/gateway/validation-errors http://www.braintreepaymentsolutions.com/gateway/validation-errors} + * For more detailed information on Validation errors, see {@link https://developers.braintreepayments.com/reference/general/validation-errors/overview/php https://developers.braintreepayments.com/reference/general/validation-errors/overview/php} * * @package Braintree * @subpackage Error - * @copyright 2010 Braintree Payment Solutions * * @property-read string $attribute * @property-read string $code * @property-read string $message */ -class Braintree_Error_Validation +class Validation { - private $_attribute; - private $_code; - private $_message; + private $_attribute; + private $_code; + private $_message; /** * @ignore @@ -42,13 +37,13 @@ public function __construct($attributes) * @ignore * @access protected * @param array $attributes array of properties to set - single level - * @return none + * @return void */ private function _initializeFromArray($attributes) { foreach($attributes AS $name => $value) { $varName = "_$name"; - $this->$varName = Braintree_Util::delimiterToCamelCase($value, '_'); + $this->$varName = Util::delimiterToCamelCase($value, '_'); } } @@ -62,3 +57,4 @@ public function __get($name) return isset($this->$varName) ? $this->$varName : null; } } +class_alias('Braintree\Error\Validation', 'Braintree_Error_Validation'); diff --git a/lib/Braintree/Error/ValidationErrorCollection.php b/lib/Braintree/Error/ValidationErrorCollection.php index 5125c90..ff3683e 100644 --- a/lib/Braintree/Error/ValidationErrorCollection.php +++ b/lib/Braintree/Error/ValidationErrorCollection.php @@ -1,30 +1,25 @@ == More information == * - * For more detailed information on Validation errors, see {@link http://www.braintreepayments.com/gateway/validation-errors http://www.braintreepaymentsolutions.com/gateway/validation-errors} + * For more detailed information on Validation errors, see {@link https://developers.braintreepayments.com/reference/general/validation-errors/overview/php https://developers.braintreepayments.com/reference/general/validation-errors/overview/php} * * @package Braintree * @subpackage Error - * @copyright 2010 Braintree Payment Solutions * * @property-read array $errors * @property-read array $nested */ -class Braintree_Error_ValidationErrorCollection extends Braintree_Collection +class ValidationErrorCollection extends Collection { - private $_errors = array(); - private $_nested = array(); + private $_errors = []; + private $_nested = []; /** * @ignore @@ -35,17 +30,17 @@ public function __construct($data) // map errors to new collections recursively if ($key == 'errors') { foreach ($errorData AS $error) { - $this->_errors[] = new Braintree_Error_Validation($error); + $this->_errors[] = new Validation($error); } } else { - $this->_nested[$key] = new Braintree_Error_ValidationErrorCollection($errorData); + $this->_nested[$key] = new ValidationErrorCollection($errorData); } } public function deepAll() { - $validationErrors = array_merge(array(), $this->_errors); + $validationErrors = array_merge([], $this->_errors); foreach($this->_nested as $nestedErrors) { $validationErrors = array_merge($validationErrors, $nestedErrors->deepAll()); @@ -75,7 +70,7 @@ public function forKey($key) public function onAttribute($attribute) { - $matches = array(); + $matches = []; foreach ($this->_errors AS $key => $error) { if($error->attribute == $attribute) { $matches[] = $error; @@ -105,7 +100,7 @@ public function __get($name) */ public function __toString() { - $output = array(); + $output = []; // TODO: implement scope if (!empty($this->_errors)) { @@ -133,3 +128,4 @@ private function _inspect($errors, $scope = null) return $eOutput; } } +class_alias('Braintree\Error\ValidationErrorCollection', 'Braintree_Error_ValidationErrorCollection'); diff --git a/lib/Braintree/EuropeBankAccount.php b/lib/Braintree/EuropeBankAccount.php new file mode 100644 index 0000000..b661e70 --- /dev/null +++ b/lib/Braintree/EuropeBankAccount.php @@ -0,0 +1,67 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * + * @property-read string $account-holder-name + * @property-read string $bic + * @property-read string $customerId + * @property-read string $default + * @property-read string $image-url + * @property-read string $mandate-reference-number + * @property-read string $masked-iban + * @property-read string $token + */ +class EuropeBankAccount extends Base +{ + + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * factory method: returns an instance of EuropeBankAccount + * to the requesting method, with populated properties + * + * @ignore + * @return EuropeBankAccount + */ + public static function factory($attributes) + { + $defaultAttributes = [ + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $europeBankAccountAttribs array of EuropeBankAccount properties + * @return void + */ + protected function _initialize($europeBankAccountAttribs) + { + $this->_attributes = $europeBankAccountAttribs; + } +} +class_alias('Braintree\EuropeBankAccount', 'Braintree_EuropeBankAccount'); diff --git a/lib/Braintree/Exception.php b/lib/Braintree/Exception.php index 40ec119..58d0805 100644 --- a/lib/Braintree/Exception.php +++ b/lib/Braintree/Exception.php @@ -1,20 +1,13 @@ _initialize($attributes); + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the facilitated details + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + +} +class_alias('Braintree\FacilitatedDetails', 'Braintree_FacilitatedDetails'); diff --git a/lib/Braintree/FacilitatorDetails.php b/lib/Braintree/FacilitatorDetails.php new file mode 100644 index 0000000..d84ce21 --- /dev/null +++ b/lib/Braintree/FacilitatorDetails.php @@ -0,0 +1,35 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the facilitator details + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + +} +class_alias('Braintree\FacilitatorDetails', 'Braintree_FacilitatorDetails'); diff --git a/lib/Braintree/Gateway.php b/lib/Braintree/Gateway.php new file mode 100644 index 0000000..701a599 --- /dev/null +++ b/lib/Braintree/Gateway.php @@ -0,0 +1,278 @@ +config = $config; + } + + /** + * + * @return AddOnGateway + */ + public function addOn() + { + return new AddOnGateway($this); + } + + /** + * + * @return AddressGateway + */ + public function address() + { + return new AddressGateway($this); + } + + /** + * + * @return ApplePayGateway + */ + public function applePay() + { + return new ApplePayGateway($this); + } + + /** + * + * @return ClientTokenGateway + */ + public function clientToken() + { + return new ClientTokenGateway($this); + } + + /** + * + * @return CreditCardGateway + */ + public function creditCard() + { + return new CreditCardGateway($this); + } + + /** + * + * @return CreditCardVerificationGateway + */ + public function creditCardVerification() + { + return new CreditCardVerificationGateway($this); + } + + /** + * + * @return CustomerGateway + */ + public function customer() + { + return new CustomerGateway($this); + } + + /** + * + * @return DiscountGateway + */ + public function discount() + { + return new DiscountGateway($this); + } + + /** + * + * @return DisputeGateway + */ + public function dispute() + { + return new DisputeGateway($this); + } + + /** + * + * @return DocumentUploadGateway + */ + public function documentUpload() + { + return new DocumentUploadGateway($this); + } + + /** + * + * @return MerchantGateway + */ + public function merchant() + { + return new MerchantGateway($this); + } + + /** + * + * @return MerchantAccountGateway + */ + public function merchantAccount() + { + return new MerchantAccountGateway($this); + } + + /** + * + * @return OAuthGateway + */ + public function oauth() + { + return new OAuthGateway($this); + } + + /** + * + * @return PaymentMethodGateway + */ + public function paymentMethod() + { + return new PaymentMethodGateway($this); + } + + /** + * + * @return PaymentMethodNonceGateway + */ + public function paymentMethodNonce() + { + return new PaymentMethodNonceGateway($this); + } + + /** + * + * @return PayPalAccountGateway + */ + public function payPalAccount() + { + return new PayPalAccountGateway($this); + } + + /** + * + * @return PlanGateway + */ + public function plan() + { + return new PlanGateway($this); + } + + /** + * + * @return SettlementBatchSummaryGateway + */ + public function settlementBatchSummary() + { + return new SettlementBatchSummaryGateway($this); + } + + /** + * + * @return SubscriptionGateway + */ + public function subscription() + { + return new SubscriptionGateway($this); + } + + /** + * + * @return TestingGateway + */ + public function testing() + { + return new TestingGateway($this); + } + + /** + * + * @return TransactionGateway + */ + public function transaction() + { + return new TransactionGateway($this); + } + + /** + * + * @return TransactionLineItemGateway + */ + public function transactionLineItem() + { + return new TransactionLineItemGateway($this); + } + + /** + * + * @return TransparentRedirectGateway + */ + public function transparentRedirect() + { + return new TransparentRedirectGateway($this); + } + + /** + * + * @return UsBankAccountGateway + */ + public function usBankAccount() + { + return new UsBankAccountGateway($this); + } + + /** + * + * @return UsBankAccountVerificationGateway + */ + public function usBankAccountVerification() + { + return new UsBankAccountVerificationGateway($this); + } + + /** + * + * @return IdealPaymentGateway + */ + public function idealPayment() + { + return new IdealPaymentGateway($this); + } + + /** + * + * @return WebhookNotificationGateway + */ + public function webhookNotification() + { + return new WebhookNotificationGateway($this); + } + + /** + * + * @return WebhookTestingGateway + */ + public function webhookTesting() + { + return new WebhookTestingGateway($this); + } +} +class_alias('Braintree\Gateway', 'Braintree_Gateway'); diff --git a/lib/Braintree/GrantedPaymentInstrumentUpdate.php b/lib/Braintree/GrantedPaymentInstrumentUpdate.php new file mode 100644 index 0000000..551b0c8 --- /dev/null +++ b/lib/Braintree/GrantedPaymentInstrumentUpdate.php @@ -0,0 +1,73 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $grantOwnerMerchantId + * @property-read string $grantRecipientMerchantId + * @property-read string $paymentMethodNonce + * @property-read string $token + * @property-read string $updatedFields + */ +class GrantedPaymentInstrumentUpdate extends Base +{ + /** + * factory method: returns an instance of GrantedPaymentInstrumentUpdate + * to the requesting method, with populated properties + * + * @ignore + * @return GrantedPaymentInstrumentUpdate + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $GrantedPaymentInstrumentAttribs array of grantedPaymentInstrumentUpdate data + * @return void + */ + protected function _initialize($grantedPaymentInstrumentUpdateAttribs) + { + // set the attributes + $this->_attributes = $grantedPaymentInstrumentUpdateAttribs; + + $paymentMethodNonce = isset($grantedPaymentInstrumentUpdateAttribs['paymentMethodNonce']) ? + GrantedPaymentInstrumentUpdate::factory($grantedPaymentInstrumentUpdateAttribs['paymentMethodNonce']) : + null; + $this->_set('paymentMethodNonce', $paymentMethodNonce); + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} +class_alias('Braintree\GrantedPaymentInstrumentUpdate', 'Braintree_GrantedPaymentInstrumentUpdate'); diff --git a/lib/Braintree/GraphQL.php b/lib/Braintree/GraphQL.php new file mode 100644 index 0000000..488d6fe --- /dev/null +++ b/lib/Braintree/GraphQL.php @@ -0,0 +1,44 @@ + $definition]; + if ($variables) { + $graphQLRequest["variables"] = $variables; + } + + $response = $this->_doUrlRequest('POST', $this->_config->graphQLBaseUrl(), json_encode($graphQLRequest), null, $this->graphQLHeaders()); + + $result = json_decode($response["body"], true); + Util::throwGraphQLResponseException($result); + + return $result; + } +} + +class_alias('Braintree\GraphQL', 'Braintree_GraphQL'); diff --git a/lib/Braintree/Http.php b/lib/Braintree/Http.php index fb05985..616be51 100644 --- a/lib/Braintree/Http.php +++ b/lib/Braintree/Http.php @@ -1,104 +1,273 @@ _config = $config; + } + + public function delete($path, $params = null) { - $response = self::_doRequest('DELETE', $path); - if($response['status'] === 200) { + $response = $this->_doRequest('DELETE', $path, $this->_buildXml($params)); + $responseCode = $response['status']; + if ($responseCode === 200 || $responseCode === 204) { return true; + } else if ($responseCode === 422) { + return Xml::buildArrayFromXml($response['body']); + } else { + Util::throwStatusCodeException($response['status']); + } + } + + public function get($path) + { + $response = $this->_doRequest('GET', $path); + if ($response['status'] === 200) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($response['status']); + Util::throwStatusCodeException($response['status']); } } - public static function get($path) + public function post($path, $params = null) { - $response = self::_doRequest('GET', $path); - if($response['status'] === 200) { - return Braintree_Xml::buildArrayFromXml($response['body']); + $response = $this->_doRequest('POST', $path, $this->_buildXml($params)); + $responseCode = $response['status']; + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($response['status']); + Util::throwStatusCodeException($responseCode); } } - public static function post($path, $params = null) + public function postMultipart($path, $params, $file) { - $response = self::_doRequest('POST', $path, self::_buildXml($params)); + $headers = [ + 'User-Agent: Braintree PHP Library ' . Version::get(), + 'X-ApiVersion: ' . Configuration::API_VERSION + ]; + $response = $this->_doRequest('POST', $path, $params, $file, $headers); $responseCode = $response['status']; - if($responseCode === 200 || $responseCode === 201 || $responseCode === 422) { - return Braintree_Xml::buildArrayFromXml($response['body']); + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($responseCode); + Util::throwStatusCodeException($responseCode); } } - public static function put($path, $params = null) + public function put($path, $params = null) { - $response = self::_doRequest('PUT', $path, self::_buildXml($params)); + $response = $this->_doRequest('PUT', $path, $this->_buildXml($params)); $responseCode = $response['status']; - if($responseCode === 200 || $responseCode === 201 || $responseCode === 422) { - return Braintree_Xml::buildArrayFromXml($response['body']); + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($responseCode); + Util::throwStatusCodeException($responseCode); } } - private static function _buildXml($params) + private function _buildXml($params) { - return empty($params) ? null : Braintree_Xml::buildXmlFromArray($params); + return empty($params) ? null : Xml::buildXmlFromArray($params); } - private static function _doRequest($httpVerb, $path, $requestBody = null) + private function _getHeaders() { - return self::_doUrlRequest($httpVerb, Braintree_Configuration::merchantUrl() . $path, $requestBody); + return [ + 'Accept: application/xml', + ]; } - public static function _doUrlRequest($httpVerb, $url, $requestBody = null) + private function _getAuthorization() + { + if ($this->_useClientCredentials) { + return [ + 'user' => $this->_config->getClientId(), + 'password' => $this->_config->getClientSecret(), + ]; + } else if ($this->_config->isAccessToken()) { + return [ + 'token' => $this->_config->getAccessToken(), + ]; + } else { + return [ + 'user' => $this->_config->getPublicKey(), + 'password' => $this->_config->getPrivateKey(), + ]; + } + } + + public function useClientCredentials() + { + $this->_useClientCredentials = true; + } + + private function _doRequest($httpVerb, $path, $requestBody = null, $file = null, $headers = null) + { + return $this->_doUrlRequest($httpVerb, $this->_config->baseUrl() . $path, $requestBody, $file, $headers); + } + + public function _doUrlRequest($httpVerb, $url, $requestBody = null, $file = null, $customHeaders = null) { $curl = curl_init(); - curl_setopt($curl, CURLOPT_TIMEOUT, 60); + curl_setopt($curl, CURLOPT_TIMEOUT, $this->_config->timeout()); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpVerb); curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_ENCODING, 'gzip'); - curl_setopt($curl, CURLOPT_HTTPHEADER, array( - 'Accept: application/xml', - 'Content-Type: application/xml', - 'User-Agent: Braintree PHP Library ' . Braintree_Version::get(), - 'X-ApiVersion: ' . Braintree_Configuration::API_VERSION - )); - curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt($curl, CURLOPT_USERPWD, Braintree_Configuration::publicKey() . ':' . Braintree_Configuration::privateKey()); - // curl_setopt($curl, CURLOPT_VERBOSE, true); - if (Braintree_Configuration::sslOn()) { + + if ($this->_config->acceptGzipEncoding()) { + curl_setopt($curl, CURLOPT_ENCODING, 'gzip'); + } + if ($this->_config->sslVersion()) { + curl_setopt($curl, CURLOPT_SSLVERSION, $this->_config->sslVersion()); + } + + $headers = []; + if ($customHeaders) { + $headers = $customHeaders; + } else { + $headers = $this->_getHeaders($curl); + $headers[] = 'User-Agent: Braintree PHP Library ' . Version::get(); + $headers[] = 'X-ApiVersion: ' . Configuration::API_VERSION; + $headers[] = 'Content-Type: application/xml'; + } + + $authorization = $this->_getAuthorization(); + if (isset($authorization['user'])) { + curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($curl, CURLOPT_USERPWD, $authorization['user'] . ':' . $authorization['password']); + } else if (isset($authorization['token'])) { + $headers[] = 'Authorization: Bearer ' . $authorization['token']; + } + + if ($this->_config->sslOn()) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($curl, CURLOPT_CAINFO, Braintree_Configuration::caFile()); + curl_setopt($curl, CURLOPT_CAINFO, $this->getCaFile()); } - if(!empty($requestBody)) { + if (!empty($file)) { + $boundary = "---------------------" . md5(mt_rand() . microtime()); + $headers[] = "Content-Type: multipart/form-data; boundary={$boundary}"; + $this->prepareMultipart($curl, $requestBody, $file, $boundary); + } else if (!empty($requestBody)) { curl_setopt($curl, CURLOPT_POSTFIELDS, $requestBody); } + if ($this->_config->isUsingProxy()) { + $proxyHost = $this->_config->getProxyHost(); + $proxyPort = $this->_config->getProxyPort(); + $proxyType = $this->_config->getProxyType(); + $proxyUser = $this->_config->getProxyUser(); + $proxyPwd= $this->_config->getProxyPassword(); + curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort); + if (!empty($proxyType)) { + curl_setopt($curl, CURLOPT_PROXYTYPE, $proxyType); + } + if ($this->_config->isAuthenticatedProxy()) { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUser . ':' . $proxyPwd); + } + } + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($curl); $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $error_code = curl_errno($curl); + $error = curl_error($curl); + + if ($error_code == 28 && $httpStatus == 0) { + throw new Exception\Timeout(); + } + curl_close($curl); - if (Braintree_Configuration::sslOn()) { + if ($this->_config->sslOn()) { if ($httpStatus == 0) { - throw new Braintree_Exception_SSLCertificate(); + throw new Exception\SSLCertificate($error, $error_code); + } + } else if ($error_code) { + throw new Exception\Connection($error, $error_code); + } + + return ['status' => $httpStatus, 'body' => $response]; + } + + function prepareMultipart($ch, $requestBody, $file, $boundary) { + $disallow = ["\0", "\"", "\r", "\n"]; + $fileInfo = new finfo(FILEINFO_MIME_TYPE); + $filePath = stream_get_meta_data($file)['uri']; + $data = file_get_contents($filePath); + $mimeType = $fileInfo->buffer($data); + + // build normal parameters + foreach ($requestBody as $k => $v) { + $k = str_replace($disallow, "_", $k); + $body[] = implode("\r\n", [ + "Content-Disposition: form-data; name=\"{$k}\"", + "", + filter_var($v), + ]); + } + + // build file parameter + $splitFilePath = explode(DIRECTORY_SEPARATOR, $filePath); + $filePath = end($splitFilePath); + $filePath = str_replace($disallow, "_", $filePath); + $body[] = implode("\r\n", [ + "Content-Disposition: form-data; name=\"file\"; filename=\"{$filePath}\"", + "Content-Type: {$mimeType}", + "", + $data, + ]); + + // add boundary for each parameters + array_walk($body, function (&$part) use ($boundary) { + $part = "--{$boundary}\r\n{$part}"; + }); + + // add final boundary + $body[] = "--{$boundary}--"; + $body[] = ""; + + // set options + return curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => implode("\r\n", $body) + ]); + } + + private function getCaFile() + { + static $memo; + + if ($memo === null) { + $caFile = $this->_config->caFile(); + + if (substr($caFile, 0, 7) !== 'phar://') { + return $caFile; } + + $extractedCaFile = sys_get_temp_dir() . '/api_braintreegateway_com.ca.crt'; + + if (!file_exists($extractedCaFile) || sha1_file($extractedCaFile) != sha1_file($caFile)) { + if (!copy($caFile, $extractedCaFile)) { + throw new Exception\SSLCaFileNotFound(); + } + } + $memo = $extractedCaFile; } - return array('status' => $httpStatus, 'body' => $response); + + return $memo; } } +class_alias('Braintree\Http', 'Braintree_Http'); diff --git a/lib/Braintree/IbanBankAccount.php b/lib/Braintree/IbanBankAccount.php new file mode 100644 index 0000000..8e53563 --- /dev/null +++ b/lib/Braintree/IbanBankAccount.php @@ -0,0 +1,57 @@ +_attributes) . ']'; + } + + /** + * sets instance properties from an array of values + * + * @ignore + * @access protected + * @param array $ibanAttribs array of ibanBankAccount data + * @return void + */ + protected function _initialize($ibanAttribs) + { + // set the attributes + $this->_attributes = $ibanAttribs; + } + + /** + * factory method: returns an instance of IbanBankAccount + * to the requesting method, with populated properties + * @ignore + * @return IbanBankAccount + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } +} +class_alias('Braintree\IbanBankAccount', 'Braintree_IbanBankAccount'); diff --git a/lib/Braintree/IdealPayment.php b/lib/Braintree/IdealPayment.php new file mode 100644 index 0000000..2ada135 --- /dev/null +++ b/lib/Braintree/IdealPayment.php @@ -0,0 +1,92 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $id + * @property-read string $idealTransactionId + * @property-read string $currency + * @property-read string $amount + * @property-read string $status + * @property-read string $orderId + * @property-read string $issuer + * @property-read string $ibanBankAccount + */ +class IdealPayment extends Base +{ + /** + * factory method: returns an instance of IdealPayment + * to the requesting method, with populated properties + * + * @ignore + * @return IdealPayment + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $idealPaymentAttribs array of idealPayment data + * @return void + */ + protected function _initialize($idealPaymentAttribs) + { + // set the attributes + $this->_attributes = $idealPaymentAttribs; + + $ibanBankAccount = isset($idealPaymentAttribs['ibanBankAccount']) ? + IbanBankAccount::factory($idealPaymentAttribs['ibanBankAccount']) : + null; + $this->_set('ibanBankAccount', $ibanBankAccount); + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } + + + // static methods redirecting to gateway + + public static function find($idealPaymentId) + { + return Configuration::gateway()->idealPayment()->find($idealPaymentId); + } + + public static function sale($idealPaymentId, $transactionAttribs) + { + $transactionAttribs['options'] = [ + 'submitForSettlement' => true + ]; + return Configuration::gateway()->idealPayment()->sale($idealPaymentId, $transactionAttribs); + } +} +class_alias('Braintree\IdealPayment', 'Braintree_IdealPayment'); diff --git a/lib/Braintree/IdealPaymentGateway.php b/lib/Braintree/IdealPaymentGateway.php new file mode 100644 index 0000000..258bc46 --- /dev/null +++ b/lib/Braintree/IdealPaymentGateway.php @@ -0,0 +1,104 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class IdealPaymentGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * find an IdealPayment by id + * + * @access public + * @param string $idealPaymentId + * @return IdealPayment + * @throws Exception\NotFound + */ + public function find($idealPaymentId) + { + try { + $path = $this->_config->merchantPath() . '/ideal_payments/' . $idealPaymentId; + $response = $this->_http->get($path); + return IdealPayment::factory($response['idealPayment']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'iDEAL Payment with id ' . $idealPaymentId . ' not found' + ); + } + } + + /** + * create a new sale for the current IdealPayment + * + * @param string $idealPaymentId + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + * @see Transaction::sale() + */ + public function sale($idealPaymentId, $transactionAttribs) + { + return Transaction::sale( + array_merge( + $transactionAttribs, + ['paymentMethodNonce' => $idealPaymentId] + ) + ); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new IdealPayment object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['idealPayment'])) { + // return a populated instance of IdealPayment + return new Result\Successful( + IdealPayment::factory($response['idealPayment']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + 'Expected Ideal Payment or apiErrorResponse' + ); + } + } +} +class_alias('Braintree\IdealPaymentGateway', 'Braintree_IdealPaymentGateway'); diff --git a/lib/Braintree/Instance.php b/lib/Braintree/Instance.php index c048254..ba0d272 100644 --- a/lib/Braintree/Instance.php +++ b/lib/Braintree/Instance.php @@ -1,25 +1,18 @@ _attributes); - return get_class($this) .'['.$objOutput.']'; + $objOutput = Util::implodeAssociativeArray($this->_attributes); + return get_class($this) .'[' . $objOutput . ']'; } /** * initializes instance properties from the keys/values of an array * @ignore * @access protected * @param $aAttribs array of properties to set - single level - * @return none + * @return void */ private function _initializeFromArray($attributes) { @@ -79,3 +71,4 @@ private function _initializeFromArray($attributes) } } +class_alias('Braintree\Instance', 'Braintree_Instance'); diff --git a/lib/Braintree/IsNode.php b/lib/Braintree/IsNode.php index 321fbb3..6db01b0 100644 --- a/lib/Braintree/IsNode.php +++ b/lib/Braintree/IsNode.php @@ -1,21 +1,24 @@ name = $name; - $this->searchTerms = array(); + $this->searchTerms = []; } - function is($value) + public function is($value) { $this->searchTerms['is'] = strval($value); + return $this; } - function toParam() + public function toParam() { return $this->searchTerms; } } +class_alias('Braintree\IsNode', 'Braintree_IsNode'); diff --git a/lib/Braintree/KeyValueNode.php b/lib/Braintree/KeyValueNode.php index ec0cb94..1fd0139 100644 --- a/lib/Braintree/KeyValueNode.php +++ b/lib/Braintree/KeyValueNode.php @@ -1,22 +1,23 @@ name = $name; $this->searchTerm = True; - } - function is($value) + public function is($value) { $this->searchTerm = $value; return $this; } - function toParam() + public function toParam() { return $this->searchTerm; } } +class_alias('Braintree\KeyValueNode', 'Braintree_KeyValueNode'); diff --git a/lib/Braintree/LocalPaymentCompleted.php b/lib/Braintree/LocalPaymentCompleted.php new file mode 100644 index 0000000..07b7360 --- /dev/null +++ b/lib/Braintree/LocalPaymentCompleted.php @@ -0,0 +1,68 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $grantOwnerMerchantId + * @property-read string $grantRecipientMerchantId + * @property-read string $paymentMethodNonce + * @property-read string $token + * @property-read string $updatedFields + */ +class LocalPaymentCompleted extends Base +{ + /** + * factory method: returns an instance of GrantedPaymentInstrumentUpdate + * to the requesting method, with populated properties + * + * @ignore + * @return LocalPaymentCompleted + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $LocalPaymentCompletedAttribs array of localPaymentCompleted data + * @return void + */ + protected function _initialize($localPaymentCompletedAttribs) + { + // set the attributes + $this->_attributes = $localPaymentCompletedAttribs; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} +class_alias('Braintree\LocalPaymentCompleted', 'Braintree_LocalPaymentCompleted'); diff --git a/lib/Braintree/MasterpassCard.php b/lib/Braintree/MasterpassCard.php new file mode 100644 index 0000000..a7fdef2 --- /dev/null +++ b/lib/Braintree/MasterpassCard.php @@ -0,0 +1,141 @@ +== More information == + * + * For more detailed information on CreditCard verifications, see {@link https://developers.braintreepayments.com/reference/response/credit-card-verification/php https://developers.braintreepayments.com/reference/response/credit-card-verification/php} + * + * @package Braintree + * @category Resources + * + * @property-read \Braintree\Address $billingAddress + * @property-read string $bin + * @property-read string $cardType + * @property-read string $cardholderName + * @property-read string $commercial + * @property-read string $countryOfIssuance + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read string $customerLocation + * @property-read string $debit + * @property-read boolean $default + * @property-read string $durbinRegulated + * @property-read string $expirationDate + * @property-read string $expirationMonth + * @property-read string $expirationYear + * @property-read boolean $expired + * @property-read string $healthcare + * @property-read string $imageUrl + * @property-read string $issuingBank + * @property-read string $last4 + * @property-read string $maskedNumber + * @property-read string $payroll + * @property-read string $prepaid + * @property-read string $productId + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read string $uniqueNumberIdentifier + * @property-read \DateTime $updatedAt + */ +class MasterpassCard extends Base +{ + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * checks whether the card is expired based on the current date + * + * @return boolean + */ + public function isExpired() + { + return $this->expired; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $creditCardAttribs array of creditcard data + * @return void + */ + protected function _initialize($creditCardAttribs) + { + // set the attributes + $this->_attributes = $creditCardAttribs; + + // map each address into its own object + $billingAddress = isset($creditCardAttribs['billingAddress']) ? + Address::factory($creditCardAttribs['billingAddress']) : + null; + + $subscriptionArray = []; + if (isset($creditCardAttribs['subscriptions'])) { + foreach ($creditCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + $this->_set('billingAddress', $billingAddress); + $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); + $this->_set('maskedNumber', $this->bin . '******' . $this->last4); + } + + /** + * returns false if comparing object is not a CreditCard, + * or is a CreditCard with a different id + * + * @param object $otherCreditCard customer to compare against + * @return boolean + */ + public function isEqual($otherMasterpassCard) + { + return !($otherMasterpassCard instanceof self) ? false : $this->token === $otherMasterpassCard->token; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + + /** + * factory method: returns an instance of CreditCard + * to the requesting method, with populated properties + * + * @ignore + * @return MasterpassCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } +} +class_alias('Braintree\MasterpassCard', 'Braintree_MasterpassCard'); diff --git a/lib/Braintree/Merchant.php b/lib/Braintree/Merchant.php new file mode 100644 index 0000000..09d8bf4 --- /dev/null +++ b/lib/Braintree/Merchant.php @@ -0,0 +1,36 @@ +_attributes = $attribs; + + $merchantAccountArray = []; + if (isset($attribs['merchantAccounts'])) { + foreach ($attribs['merchantAccounts'] AS $merchantAccount) { + $merchantAccountArray[] = MerchantAccount::factory($merchantAccount); + } + } + $this->_set('merchantAccounts', $merchantAccountArray); + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * returns a string representation of the merchant + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } +} +class_alias('Braintree\Merchant', 'Braintree_Merchant'); diff --git a/lib/Braintree/MerchantAccount.php b/lib/Braintree/MerchantAccount.php index 7c9aaf9..0797e23 100644 --- a/lib/Braintree/MerchantAccount.php +++ b/lib/Braintree/MerchantAccount.php @@ -1,6 +1,17 @@ $attribs)); - } - - public static function find($merchant_account_id) - { - try { - $response = Braintree_Http::get('/merchant_accounts/' . $merchant_account_id); - return self::factory($response['merchantAccount']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound('merchant account with id ' . $merchant_account_id . ' not found'); - } - } - - public static function update($merchant_account_id, $attributes) - { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - return self::_doUpdate('/merchant_accounts/' . $merchant_account_id . '/update_via_api', array('merchant_account' => $attributes)); - } - - public static function detectSignature($attribs) - { - if (isset($attribs['applicantDetails'])) { - trigger_error("DEPRECATED: Passing applicantDetails to create is deprecated. Please use individual, business, and funding", E_USER_NOTICE); - return self::createDeprecatedSignature(); - } else { - return self::createSignature(); - } - } - - public static function updateSignature() - { - $signature = self::createSignature(); - unset($signature['tosAccepted']); - return $signature; - } - - public static function createSignature() - { - $addressSignature = array('streetAddress', 'postalCode', 'locality', 'region'); - $individualSignature = array( - 'firstName', - 'lastName', - 'email', - 'phone', - 'dateOfBirth', - 'ssn', - array('address' => $addressSignature) - ); - - $businessSignature = array( - 'dbaName', - 'legalName', - 'taxId', - array('address' => $addressSignature) - ); - - $fundingSignature = array( - 'routingNumber', - 'accountNumber', - 'destination', - 'email', - 'mobilePhone' - ); - - return array( - 'id', - 'tosAccepted', - 'masterMerchantAccountId', - array('individual' => $individualSignature), - array('funding' => $fundingSignature), - array('business' => $businessSignature) - ); - } - - public static function createDeprecatedSignature() - { - $applicantDetailsAddressSignature = array('streetAddress', 'postalCode', 'locality', 'region'); - $applicantDetailsSignature = array( - 'companyName', - 'firstName', - 'lastName', - 'email', - 'phone', - 'dateOfBirth', - 'ssn', - 'taxId', - 'routingNumber', - 'accountNumber', - array('address' => $applicantDetailsAddressSignature) - ); - - return array( - array('applicantDetails' => $applicantDetailsSignature), - 'id', - 'tosAccepted', - 'masterMerchantAccountId' - ); - } - - public static function _doCreate($url, $params) - { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); - } - - private static function _doUpdate($url, $params) - { - $response = Braintree_Http::put($url, $params); - - return self::_verifyGatewayResponse($response); - } - - private static function _verifyGatewayResponse($response) - { - if (isset($response['merchantAccount'])) { - // return a populated instance of Braintree_merchantAccount - return new Braintree_Result_Successful( - self::factory($response['merchantAccount']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected merchant account or apiErrorResponse" - ); - } - } - public static function factory($attributes) { $instance = new self(); @@ -155,22 +34,41 @@ protected function _initialize($merchantAccountAttribs) if (isset($merchantAccountAttribs['individual'])) { $individual = $merchantAccountAttribs['individual']; - $this->_set('individualDetails', Braintree_MerchantAccount_IndividualDetails::Factory($individual)); + $this->_set('individualDetails', MerchantAccount\IndividualDetails::Factory($individual)); } if (isset($merchantAccountAttribs['business'])) { $business = $merchantAccountAttribs['business']; - $this->_set('businessDetails', Braintree_MerchantAccount_BusinessDetails::Factory($business)); + $this->_set('businessDetails', MerchantAccount\BusinessDetails::Factory($business)); } if (isset($merchantAccountAttribs['funding'])) { $funding = $merchantAccountAttribs['funding']; - $this->_set('fundingDetails', new Braintree_MerchantAccount_FundingDetails($funding)); + $this->_set('fundingDetails', new MerchantAccount\FundingDetails($funding)); } if (isset($merchantAccountAttribs['masterMerchantAccount'])) { $masterMerchantAccount = $merchantAccountAttribs['masterMerchantAccount']; - $this->_set('masterMerchantAccount', Braintree_MerchantAccount::Factory($masterMerchantAccount)); + $this->_set('masterMerchantAccount', self::Factory($masterMerchantAccount)); } } + + + // static methods redirecting to gateway + + public static function create($attribs) + { + return Configuration::gateway()->merchantAccount()->create($attribs); + } + + public static function find($merchant_account_id) + { + return Configuration::gateway()->merchantAccount()->find($merchant_account_id); + } + + public static function update($merchant_account_id, $attributes) + { + return Configuration::gateway()->merchantAccount()->update($merchant_account_id, $attributes); + } } +class_alias('Braintree\MerchantAccount', 'Braintree_MerchantAccount'); diff --git a/lib/Braintree/MerchantAccount/AddressDetails.php b/lib/Braintree/MerchantAccount/AddressDetails.php index 15349f6..c4caf9a 100644 --- a/lib/Braintree/MerchantAccount/AddressDetails.php +++ b/lib/Braintree/MerchantAccount/AddressDetails.php @@ -1,5 +1,10 @@ _attributes = $businessAttribs; if (isset($businessAttribs['address'])) { - $this->_set('addressDetails', new Braintree_MerchantAccount_AddressDetails($businessAttribs['address'])); + $this->_set('addressDetails', new AddressDetails($businessAttribs['address'])); } } @@ -17,3 +20,4 @@ public static function factory($attributes) return $instance; } } +class_alias('Braintree\MerchantAccount\BusinessDetails', 'Braintree_MerchantAccount_BusinessDetails'); diff --git a/lib/Braintree/MerchantAccount/FundingDetails.php b/lib/Braintree/MerchantAccount/FundingDetails.php index f33e595..6fac0b5 100644 --- a/lib/Braintree/MerchantAccount/FundingDetails.php +++ b/lib/Braintree/MerchantAccount/FundingDetails.php @@ -1,6 +1,10 @@ _attributes = $individualAttribs; if (isset($individualAttribs['address'])) { - $this->_set('addressDetails', new Braintree_MerchantAccount_AddressDetails($individualAttribs['address'])); + $this->_set('addressDetails', new AddressDetails($individualAttribs['address'])); } } @@ -17,3 +20,4 @@ public static function factory($attributes) return $instance; } } +class_alias('Braintree\MerchantAccount\IndividualDetails', 'Braintree_MerchantAccount_IndividualDetails'); diff --git a/lib/Braintree/MerchantAccountGateway.php b/lib/Braintree/MerchantAccountGateway.php new file mode 100644 index 0000000..a9704c8 --- /dev/null +++ b/lib/Braintree/MerchantAccountGateway.php @@ -0,0 +1,182 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function create($attribs) + { + Util::verifyKeys(self::detectSignature($attribs), $attribs); + return $this->_doCreate('/merchant_accounts/create_via_api', ['merchant_account' => $attribs]); + } + + public function find($merchant_account_id) + { + try { + $path = $this->_config->merchantPath() . '/merchant_accounts/' . $merchant_account_id; + $response = $this->_http->get($path); + return MerchantAccount::factory($response['merchantAccount']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('merchant account with id ' . $merchant_account_id . ' not found'); + } + } + + public function update($merchant_account_id, $attributes) + { + Util::verifyKeys(self::updateSignature(), $attributes); + return $this->_doUpdate('/merchant_accounts/' . $merchant_account_id . '/update_via_api', ['merchant_account' => $attributes]); + } + + public static function detectSignature($attribs) + { + if (isset($attribs['applicantDetails'])) { + trigger_error("DEPRECATED: Passing applicantDetails to create is deprecated. Please use individual, business, and funding", E_USER_NOTICE); + return self::createDeprecatedSignature(); + } else { + return self::createSignature(); + } + } + + public static function updateSignature() + { + $signature = self::createSignature(); + unset($signature['tosAccepted']); + return $signature; + } + + public function createForCurrency($attribs) + { + $response = $this->_http->post($this->_config->merchantPath() . '/merchant_accounts/create_for_currency', ['merchant_account' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function all() + { + $pager = [ + 'object' => $this, + 'method' => 'fetchMerchantAccounts', + ]; + return new PaginatedCollection($pager); + } + + public function fetchMerchantAccounts($page) + { + $response = $this->_http->get($this->_config->merchantPath() . '/merchant_accounts?page=' . $page); + $body = $response['merchantAccounts']; + $merchantAccounts = Util::extractattributeasarray($body, 'merchantAccount'); + $totalItems = $body['totalItems'][0]; + $pageSize = $body['pageSize'][0]; + return new PaginatedResult($totalItems, $pageSize, $merchantAccounts); + } + + public static function createSignature() + { + $addressSignature = ['streetAddress', 'postalCode', 'locality', 'region']; + $individualSignature = [ + 'firstName', + 'lastName', + 'email', + 'phone', + 'dateOfBirth', + 'ssn', + ['address' => $addressSignature] + ]; + + $businessSignature = [ + 'dbaName', + 'legalName', + 'taxId', + ['address' => $addressSignature] + ]; + + $fundingSignature = [ + 'routingNumber', + 'accountNumber', + 'destination', + 'email', + 'mobilePhone', + 'descriptor', + ]; + + return [ + 'id', + 'tosAccepted', + 'masterMerchantAccountId', + ['individual' => $individualSignature], + ['funding' => $fundingSignature], + ['business' => $businessSignature] + ]; + } + + public static function createDeprecatedSignature() + { + $applicantDetailsAddressSignature = ['streetAddress', 'postalCode', 'locality', 'region']; + $applicantDetailsSignature = [ + 'companyName', + 'firstName', + 'lastName', + 'email', + 'phone', + 'dateOfBirth', + 'ssn', + 'taxId', + 'routingNumber', + 'accountNumber', + ['address' => $applicantDetailsAddressSignature] + ]; + + return [ + ['applicantDetails' => $applicantDetailsSignature], + 'id', + 'tosAccepted', + 'masterMerchantAccountId' + ]; + } + + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + private function _doUpdate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->put($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + if (isset($response['response'])) { + $response = $response['response']; + } + if (isset($response['merchantAccount'])) { + // return a populated instance of merchantAccount + return new Result\Successful( + MerchantAccount::factory($response['merchantAccount']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected merchant account or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\MerchantAccountGateway', 'Braintree_MerchantAccountGateway'); diff --git a/lib/Braintree/MerchantGateway.php b/lib/Braintree/MerchantGateway.php new file mode 100644 index 0000000..d628e88 --- /dev/null +++ b/lib/Braintree/MerchantGateway.php @@ -0,0 +1,42 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasClientCredentials(); + $this->_http = new Http($gateway->config); + $this->_http->useClientCredentials(); + } + + public function create($attribs) + { + $response = $this->_http->post('/merchants/create_via_api', ['merchant' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + if (isset($response['response']['merchant'])) { + // return a populated instance of merchant + return new Result\Successful([ + Merchant::factory($response['response']['merchant']), + OAuthCredentials::factory($response['response']['credentials']), + ]); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected merchant or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\MerchantGateway', 'Braintree_MerchantGateway'); diff --git a/lib/Braintree/Modification.php b/lib/Braintree/Modification.php index 7563b57..e1d32b2 100644 --- a/lib/Braintree/Modification.php +++ b/lib/Braintree/Modification.php @@ -1,17 +1,11 @@ _attributes = $attributes; - - $addOnArray = array(); - if (isset($attributes['addOns'])) { - foreach ($attributes['addOns'] AS $addOn) { - $addOnArray[] = Braintree_addOn::factory($addOn); - } - } - $this->_attributes['addOns'] = $addOnArray; } public static function factory($attributes) @@ -20,4 +14,9 @@ public static function factory($attributes) $instance->_initialize($attributes); return $instance; } + + public function __toString() { + return get_called_class() . '[' . Util::attributesToString($this->_attributes) . ']'; + } } +class_alias('Braintree\Modification', 'Braintree_Modification'); diff --git a/lib/Braintree/MultipleValueNode.php b/lib/Braintree/MultipleValueNode.php index 3100e4d..397c610 100644 --- a/lib/Braintree/MultipleValueNode.php +++ b/lib/Braintree/MultipleValueNode.php @@ -1,15 +1,18 @@ name = $name; - $this->items = array(); + $this->items = []; $this->allowedValues = $allowedValues; } - function in($values) + public function in($values) { $bad_values = array_diff($values, $this->allowedValues); if (count($this->allowedValues) > 0 && count($bad_values) > 0) { @@ -25,13 +28,14 @@ function in($values) return $this; } - function is($value) + public function is($value) { - return $this->in(array($value)); + return $this->in([$value]); } - function toParam() + public function toParam() { return $this->items; } } +class_alias('Braintree\MultipleValueNode', 'Braintree_MultipleValueNode'); diff --git a/lib/Braintree/MultipleValueOrTextNode.php b/lib/Braintree/MultipleValueOrTextNode.php index e0faca4..c98ee95 100644 --- a/lib/Braintree/MultipleValueOrTextNode.php +++ b/lib/Braintree/MultipleValueOrTextNode.php @@ -1,45 +1,47 @@ textNode = new Braintree_TextNode($name); + $this->textNode = new TextNode($name); } - function contains($value) + public function contains($value) { $this->textNode->contains($value); return $this; } - function endsWith($value) + public function endsWith($value) { $this->textNode->endsWith($value); return $this; } - function is($value) + public function is($value) { $this->textNode->is($value); return $this; } - function isNot($value) + public function isNot($value) { $this->textNode->isNot($value); return $this; } - function startsWith($value) + public function startsWith($value) { $this->textNode->startsWith($value); return $this; } - function toParam() + public function toParam() { return array_merge(parent::toParam(), $this->textNode->toParam()); } } +class_alias('Braintree\MultipleValueOrTextNode', 'Braintree_MultipleValueOrTextNode'); diff --git a/lib/Braintree/OAuthAccessRevocation.php b/lib/Braintree/OAuthAccessRevocation.php new file mode 100644 index 0000000..62a2897 --- /dev/null +++ b/lib/Braintree/OAuthAccessRevocation.php @@ -0,0 +1,32 @@ +_initialize($attributes); + + return $instance; + } + + /** + * @ignore + */ + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} +class_alias('Braintree\OAuthAccessRevocation', 'Braintree_OAuthAccessRevocation'); diff --git a/lib/Braintree/OAuthCredentials.php b/lib/Braintree/OAuthCredentials.php new file mode 100644 index 0000000..60de5fb --- /dev/null +++ b/lib/Braintree/OAuthCredentials.php @@ -0,0 +1,34 @@ +_attributes = $attribs; + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * returns a string representation of the access token + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } +} +class_alias('Braintree\OAuthCredentials', 'Braintree_OAuthCredentials'); diff --git a/lib/Braintree/OAuthGateway.php b/lib/Braintree/OAuthGateway.php new file mode 100644 index 0000000..4e2d1b2 --- /dev/null +++ b/lib/Braintree/OAuthGateway.php @@ -0,0 +1,124 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_http = new Http($gateway->config); + $this->_http->useClientCredentials(); + + $this->_config->assertHasClientCredentials(); + } + + public function createTokenFromCode($params) + { + $params['grantType'] = "authorization_code"; + return $this->_createToken($params); + } + + public function createTokenFromRefreshToken($params) + { + $params['grantType'] = "refresh_token"; + return $this->_createToken($params); + } + + public function revokeAccessToken($accessToken) + { + $params = ['token' => $accessToken]; + $response = $this->_http->post('/oauth/revoke_access_token', $params); + return $this->_verifyGatewayResponse($response); + } + + private function _createToken($params) + { + $params = ['credentials' => $params]; + $response = $this->_http->post('/oauth/access_tokens', $params); + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + if (isset($response['credentials'])) { + $result = new Result\Successful( + OAuthCredentials::factory($response['credentials']) + ); + return $this->_mapSuccess($result); + } else if (isset($response['result'])) { + $result = new Result\Successful( + OAuthResult::factory($response['result']) + ); + return $this->_mapAccessTokenRevokeSuccess($result); + } else if (isset($response['apiErrorResponse'])) { + $result = new Result\Error($response['apiErrorResponse']); + return $this->_mapError($result); + } else { + throw new Exception\Unexpected( + "Expected credentials or apiErrorResponse" + ); + } + } + + public function _mapError($result) + { + $error = $result->errors->deepAll()[0]; + + if ($error->code == Error\Codes::OAUTH_INVALID_GRANT) { + $result->error = 'invalid_grant'; + } else if ($error->code == Error\Codes::OAUTH_INVALID_CREDENTIALS) { + $result->error = 'invalid_credentials'; + } else if ($error->code == Error\Codes::OAUTH_INVALID_SCOPE) { + $result->error = 'invalid_scope'; + } + $result->errorDescription = explode(': ', $error->message)[1]; + return $result; + } + + public function _mapAccessTokenRevokeSuccess($result) + { + $result->revocationResult = $result->success; + return $result; + } + + public function _mapSuccess($result) + { + $credentials = $result->credentials; + $result->accessToken = $credentials->accessToken; + $result->refreshToken = $credentials->refreshToken; + $result->tokenType = $credentials->tokenType; + $result->expiresAt = $credentials->expiresAt; + return $result; + } + + public function connectUrl($params = []) + { + $query = Util::camelCaseToDelimiterArray($params, '_'); + $query['client_id'] = $this->_config->getClientId(); + $queryString = preg_replace('/\%5B\d+\%5D/', '%5B%5D', http_build_query($query)); + + return $this->_config->baseUrl() . '/oauth/connect?' . $queryString; + } + + /** + * @deprecated since version 3.26.1 + */ + public function computeSignature($url) + { + $key = hash('sha256', $this->_config->getClientSecret(), true); + return hash_hmac('sha256', $url, $key); + } +} +class_alias('Braintree\OAuthGateway', 'Braintree_OAuthGateway'); diff --git a/lib/Braintree/OAuthResult.php b/lib/Braintree/OAuthResult.php new file mode 100644 index 0000000..9c3e4e6 --- /dev/null +++ b/lib/Braintree/OAuthResult.php @@ -0,0 +1,34 @@ +_attributes = $attribs; + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * returns a string representation of the result + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } +} +class_alias('Braintree\OAuthResult', 'Braintree_OAuthResult'); diff --git a/lib/Braintree/PaginatedCollection.php b/lib/Braintree/PaginatedCollection.php new file mode 100644 index 0000000..9deef04 --- /dev/null +++ b/lib/Braintree/PaginatedCollection.php @@ -0,0 +1,120 @@ + + * $result = MerchantAccount::all(); + * + * foreach($result as $merchantAccount) { + * print_r($merchantAccount->status); + * } + * + * + * @package Braintree + * @subpackage Utility + */ +class PaginatedCollection implements Iterator +{ + private $_pager; + private $_pageSize; + private $_currentPage; + private $_index; + private $_totalItems; + private $_items; + + /** + * set up the paginated collection + * + * expects an array of an object and method to call on it + * + * @param array $pager + */ + public function __construct($pager) + { + $this->_pager = $pager; + $this->_pageSize = 0; + $this->_currentPage = 0; + $this->_totalItems = 0; + $this->_index = 0; + } + + /** + * returns the current item when iterating with foreach + */ + public function current() + { + return $this->_items[($this->_index % $this->_pageSize)]; + } + + public function key() + { + return null; + } + + /** + * advances to the next item in the collection when iterating with foreach + */ + public function next() + { + ++$this->_index; + } + + /** + * rewinds the collection to the first item when iterating with foreach + */ + public function rewind() + { + $this->_index = 0; + $this->_currentPage = 0; + $this->_pageSize = 0; + $this->_totalItems = 0; + $this->_items = []; + } + + /** + * returns whether the current item is valid when iterating with foreach + */ + public function valid() + { + if ($this->_currentPage == 0 || $this->_index % $this->_pageSize == 0 && $this->_index < $this->_totalItems) + { + $this->_getNextPage(); + } + + return $this->_index < $this->_totalItems; + } + + private function _getNextPage() + { + $this->_currentPage++; + $object = $this->_pager['object']; + $method = $this->_pager['method']; + + if (isset($this->_pager['query'])) { + $query = $this->_pager['query']; + $result = call_user_func( + [$object, $method], + $query, + $this->_currentPage + ); + } else { + $result = call_user_func( + [$object, $method], + $this->_currentPage + ); + } + + $this->_totalItems= $result->getTotalItems(); + $this->_pageSize = $result->getPageSize(); + $this->_items = $result->getCurrentPage(); + } +} +class_alias('Braintree\PaginatedCollection', 'Braintree_PaginatedCollection'); diff --git a/lib/Braintree/PaginatedResult.php b/lib/Braintree/PaginatedResult.php new file mode 100644 index 0000000..9b383ab --- /dev/null +++ b/lib/Braintree/PaginatedResult.php @@ -0,0 +1,32 @@ +_totalItems = $totalItems; + $this->_pageSize = $pageSize; + $this->_currentPage = $currentPage; + } + + public function getTotalItems() + { + return $this->_totalItems; + } + + public function getPageSize() + { + return $this->_pageSize; + } + + public function getCurrentPage() + { + return $this->_currentPage; + } +} +class_alias('Braintree\PaginatedResult', 'Braintree_PaginatedResult'); diff --git a/lib/Braintree/PartialMatchNode.php b/lib/Braintree/PartialMatchNode.php index 076204a..5d8dc91 100644 --- a/lib/Braintree/PartialMatchNode.php +++ b/lib/Braintree/PartialMatchNode.php @@ -1,16 +1,18 @@ searchTerms["starts_with"] = strval($value); return $this; } - function endsWith($value) + public function endsWith($value) { $this->searchTerms["ends_with"] = strval($value); return $this; } } +class_alias('Braintree\PartialMatchNode', 'Braintree_PartialMatchNode'); diff --git a/lib/Braintree/PartnerMerchant.php b/lib/Braintree/PartnerMerchant.php index 48673e7..d5ac152 100644 --- a/lib/Braintree/PartnerMerchant.php +++ b/lib/Braintree/PartnerMerchant.php @@ -1,29 +1,23 @@ _attributes = $attributes; } } +class_alias('Braintree\PartnerMerchant', 'Braintree_PartnerMerchant'); diff --git a/lib/Braintree/PayPalAccount.php b/lib/Braintree/PayPalAccount.php new file mode 100644 index 0000000..7cfbf97 --- /dev/null +++ b/lib/Braintree/PayPalAccount.php @@ -0,0 +1,115 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $billingAgreementId + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $email + * @property-read string $imageUrl + * @property-read string $payerId + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read \DateTime $updatedAt + */ +class PayPalAccount extends Base +{ + /** + * factory method: returns an instance of PayPalAccount + * to the requesting method, with populated properties + * + * @ignore + * @return PayPalAccount + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $paypalAccountAttribs array of paypalAccount data + * @return void + */ + protected function _initialize($paypalAccountAttribs) + { + // set the attributes + $this->_attributes = $paypalAccountAttribs; + + $subscriptionArray = []; + if (isset($paypalAccountAttribs['subscriptions'])) { + foreach ($paypalAccountAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } + + + // static methods redirecting to gateway + + public static function find($token) + { + return Configuration::gateway()->payPalAccount()->find($token); + } + + public static function update($token, $attributes) + { + return Configuration::gateway()->payPalAccount()->update($token, $attributes); + } + + public static function delete($token) + { + return Configuration::gateway()->payPalAccount()->delete($token); + } + + public static function sale($token, $transactionAttribs) + { + return Configuration::gateway()->payPalAccount()->sale($token, $transactionAttribs); + } +} +class_alias('Braintree\PayPalAccount', 'Braintree_PayPalAccount'); diff --git a/lib/Braintree/PayPalAccountGateway.php b/lib/Braintree/PayPalAccountGateway.php new file mode 100644 index 0000000..9302a17 --- /dev/null +++ b/lib/Braintree/PayPalAccountGateway.php @@ -0,0 +1,178 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PayPalAccountGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + + /** + * find a paypalAccount by token + * + * @access public + * @param string $token paypal accountunique id + * @return PayPalAccount + * @throws Exception\NotFound + */ + public function find($token) + { + $this->_validateId($token); + try { + $path = $this->_config->merchantPath() . '/payment_methods/paypal_account/' . $token; + $response = $this->_http->get($path); + return PayPalAccount::factory($response['paypalAccount']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'paypal account with token ' . $token . ' not found' + ); + } + + } + + /** + * updates the paypalAccount record + * + * if calling this method in context, $token + * is the 2nd attribute. $token is not sent in object context. + * + * @access public + * @param array $attributes + * @param string $token (optional) + * @return Result\Successful or Result\Error + */ + public function update($token, $attributes) + { + Util::verifyKeys(self::updateSignature(), $attributes); + $this->_validateId($token); + return $this->_doUpdate('put', '/payment_methods/paypal_account/' . $token, ['paypalAccount' => $attributes]); + } + + public function delete($token) + { + $this->_validateId($token); + $path = $this->_config->merchantPath() . '/payment_methods/paypal_account/' . $token; + $this->_http->delete($path); + return new Result\Successful(); + } + + /** + * create a new sale for the current PayPal account + * + * @param string $token + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + * @see Transaction::sale() + */ + public function sale($token, $transactionAttribs) + { + $this->_validateId($token); + return Transaction::sale( + array_merge( + $transactionAttribs, + ['paymentMethodToken' => $token] + ) + ); + } + + public static function updateSignature() + { + return [ + 'token', + ['options' => ['makeDefault']] + ]; + } + + /** + * sends the update request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + private function _doUpdate($httpVerb, $subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->$httpVerb($fullPath, $params); + return $this->_verifyGatewayResponse($response); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new PayPalAccount object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['paypalAccount'])) { + // return a populated instance of PayPalAccount + return new Result\Successful( + PayPalAccount::factory($response['paypalAccount']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + 'Expected paypal account or apiErrorResponse' + ); + } + } + + /** + * verifies that a valid paypal account identifier is being used + * @ignore + * @param string $identifier + * @param Optional $string $identifierType type of identifier supplied, default 'token' + * @throws InvalidArgumentException + */ + private function _validateId($identifier = null, $identifierType = 'token') + { + if (empty($identifier)) { + throw new InvalidArgumentException( + 'expected paypal account id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) { + throw new InvalidArgumentException( + $identifier . ' is an invalid paypal account ' . $identifierType . '.' + ); + } + } +} +class_alias('Braintree\PayPalAccountGateway', 'Braintree_PayPalAccountGateway'); diff --git a/lib/Braintree/PaymentInstrumentType.php b/lib/Braintree/PaymentInstrumentType.php new file mode 100644 index 0000000..8218e42 --- /dev/null +++ b/lib/Braintree/PaymentInstrumentType.php @@ -0,0 +1,19 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PaymentMethod extends Base +{ + // static methods redirecting to gateway + + public static function create($attribs) + { + return Configuration::gateway()->paymentMethod()->create($attribs); + } + + public static function find($token) + { + return Configuration::gateway()->paymentMethod()->find($token); + } + + public static function update($token, $attribs) + { + return Configuration::gateway()->paymentMethod()->update($token, $attribs); + } + + public static function delete($token, $options=[]) + { + return Configuration::gateway()->paymentMethod()->delete($token, $options); + } +} +class_alias('Braintree\PaymentMethod', 'Braintree_PaymentMethod'); diff --git a/lib/Braintree/PaymentMethodGateway.php b/lib/Braintree/PaymentMethodGateway.php new file mode 100644 index 0000000..df3dd58 --- /dev/null +++ b/lib/Braintree/PaymentMethodGateway.php @@ -0,0 +1,320 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PaymentMethodGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + + public function create($attribs) + { + Util::verifyKeys(self::createSignature(), $attribs); + return $this->_doCreate('/payment_methods', ['payment_method' => $attribs]); + } + + /** + * find a PaymentMethod by token + * + * @param string $token payment method unique id + * @return CreditCard|PayPalAccount + * @throws Exception\NotFound + */ + public function find($token) + { + $this->_validateId($token); + try { + $path = $this->_config->merchantPath() . '/payment_methods/any/' . $token; + $response = $this->_http->get($path); + return PaymentMethodParser::parsePaymentMethod($response); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'payment method with token ' . $token . ' not found' + ); + } + } + + public function update($token, $attribs) + { + Util::verifyKeys(self::updateSignature(), $attribs); + return $this->_doUpdate('/payment_methods/any/' . $token, ['payment_method' => $attribs]); + } + + public function delete($token, $options=[]) + { + Util::verifyKeys(self::deleteSignature(), $options); + $this->_validateId($token); + $queryString = ""; + if (!empty($options)) { + $queryString = "?" . http_build_query(Util::camelCaseToDelimiterArray($options, '_')); + } + return $this->_doDelete('/payment_methods/any/' . $token . $queryString); + } + + public function grant($sharedPaymentMethodToken, $attribs=[]) + { + if (is_bool($attribs) === true) { + $attribs = ['allow_vaulting' => $attribs]; + } + $options = [ 'shared_payment_method_token' => $sharedPaymentMethodToken ]; + + return $this->_doGrant( + '/payment_methods/grant', + [ + 'payment_method' => array_merge($attribs, $options) + ] + ); + } + + public function revoke($sharedPaymentMethodToken) + { + return $this->_doRevoke( + '/payment_methods/revoke', + [ + 'payment_method' => [ + 'shared_payment_method_token' => $sharedPaymentMethodToken + ] + ] + ); + } + + private static function baseSignature() + { + $billingAddressSignature = AddressGateway::createSignature(); + $optionsSignature = [ + 'failOnDuplicatePaymentMethod', + 'makeDefault', + 'verificationMerchantAccountId', + 'verifyCard', + 'verificationAmount', + 'usBankAccountVerificationMethod', + ['paypal' => [ + 'payee_email', + 'payeeEmail', + 'order_id', + 'orderId', + 'custom_field', + 'customField', + 'description', + 'amount', + ['shipping' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'postalCode', 'region', + 'streetAddress'], + ], + ]], + ]; + return [ + 'billingAddressId', + 'cardholderName', + 'cvv', + 'deviceData', + 'expirationDate', + 'expirationMonth', + 'expirationYear', + 'number', + 'paymentMethodNonce', + 'token', + ['options' => $optionsSignature], + ['billingAddress' => $billingAddressSignature] + ]; + } + + public static function createSignature() + { + $signature = array_merge(self::baseSignature(), ['customerId', 'paypalRefreshToken', 'paypalVaultWithoutUpgrade']); + return $signature; + } + + public static function updateSignature() + { + $billingAddressSignature = AddressGateway::updateSignature(); + array_push($billingAddressSignature, [ + 'options' => [ + 'updateExisting' + ] + ]); + $signature = array_merge(self::baseSignature(), [ + 'deviceSessionId', + 'venmoSdkPaymentMethodCode', + 'fraudMerchantId', + ['billingAddress' => $billingAddressSignature] + ]); + return $signature; + } + + private static function deleteSignature() + { + return ['revokeAllGrants']; + } + + /** + * sends the create request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + public function _doGrant($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGrantResponse($response); + } + + public function _doRevoke($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyRevokeResponse($response); + } + + /** + * sends the update request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + public function _doUpdate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->put($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + + /** + * sends the delete request to the gateway + * + * @ignore + * @param string $subPath + * @return mixed + */ + public function _doDelete($subPath) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $this->_http->delete($fullPath); + return new Result\Successful(); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new CreditCard or PayPalAccount object + * and encapsulates it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else if (($response)) { + return new Result\Successful( + PaymentMethodParser::parsePaymentMethod($response), + 'paymentMethod' + ); + } else { + throw new Exception\Unexpected( + 'Expected payment method or apiErrorResponse' + ); + } + } + + private function _verifyGrantResponse($response) { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else if (isset($response['paymentMethodNonce'])) { + return new Result\Successful( + PaymentMethodNonce::factory($response['paymentMethodNonce']), + 'paymentMethodNonce' + ); + } else { + throw new Exception\Unexpected( + 'Expected paymentMethodNonce or apiErrorResponse' + ); + } + } + + private function _verifyRevokeResponse($response) { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else if (isset($response['success'])) { + return new Result\Successful(); + } else { + throw new Exception\Unexpected( + 'Expected success or apiErrorResponse' + ); + } + } + + /** + * verifies that a valid payment method identifier is being used + * @ignore + * @param string $identifier + * @param Optional $string $identifierType type of identifier supplied, default 'token' + * @throws InvalidArgumentException + */ + private function _validateId($identifier = null, $identifierType = 'token') + { + if (empty($identifier)) { + throw new InvalidArgumentException( + 'expected payment method id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) { + throw new InvalidArgumentException( + $identifier . ' is an invalid payment method ' . $identifierType . '.' + ); + } + } +} +class_alias('Braintree\PaymentMethodGateway', 'Braintree_PaymentMethodGateway'); diff --git a/lib/Braintree/PaymentMethodNonce.php b/lib/Braintree/PaymentMethodNonce.php new file mode 100644 index 0000000..4501fa9 --- /dev/null +++ b/lib/Braintree/PaymentMethodNonce.php @@ -0,0 +1,62 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read \Braintree\BinData $binData + * @property-read boolean $default + * @property-read string $nonce + * @property-read \Braintree\ThreeDSecureInfo $threeDSecureInfo + * @property-read string $type + */ +class PaymentMethodNonce extends Base +{ + // static methods redirecting to gateway + + public static function create($token) + { + return Configuration::gateway()->paymentMethodNonce()->create($token); + } + + public static function find($nonce) + { + return Configuration::gateway()->paymentMethodNonce()->find($nonce); + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + protected function _initialize($nonceAttributes) + { + $this->_attributes = $nonceAttributes; + $this->_set('nonce', $nonceAttributes['nonce']); + $this->_set('type', $nonceAttributes['type']); + + if(isset($nonceAttributes['threeDSecureInfo'])) { + $this->_set('threeDSecureInfo', ThreeDSecureInfo::factory($nonceAttributes['threeDSecureInfo'])); + } + + if(isset($nonceAttributes['binData'])) { + $this->_set('binData', BinData::factory($nonceAttributes['binData'])); + } + } +} +class_alias('Braintree\PaymentMethodNonce', 'Braintree_PaymentMethodNonce'); diff --git a/lib/Braintree/PaymentMethodNonceGateway.php b/lib/Braintree/PaymentMethodNonceGateway.php new file mode 100644 index 0000000..40ad8af --- /dev/null +++ b/lib/Braintree/PaymentMethodNonceGateway.php @@ -0,0 +1,64 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PaymentMethodNonceGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_http = new Http($gateway->config); + } + + + public function create($token) + { + $subPath = '/payment_methods/' . $token . '/nonces'; + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath); + + return new Result\Successful( + PaymentMethodNonce::factory($response['paymentMethodNonce']), + "paymentMethodNonce" + ); + } + + /** + * @access public + * + */ + public function find($nonce) + { + try { + $path = $this->_config->merchantPath() . '/payment_method_nonces/' . $nonce; + $response = $this->_http->get($path); + return PaymentMethodNonce::factory($response['paymentMethodNonce']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'payment method nonce with id ' . $nonce . ' not found' + ); + } + + } +} +class_alias('Braintree\PaymentMethodNonceGateway', 'Braintree_PaymentMethodNonceGateway'); diff --git a/lib/Braintree/PaymentMethodParser.php b/lib/Braintree/PaymentMethodParser.php new file mode 100644 index 0000000..fea4af7 --- /dev/null +++ b/lib/Braintree/PaymentMethodParser.php @@ -0,0 +1,58 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + */ +class PaymentMethodParser +{ + public static function parsePaymentMethod($response) + { + if (isset($response['creditCard'])) { + return CreditCard::factory($response['creditCard']); + } else if (isset($response['paypalAccount'])) { + return PayPalAccount::factory($response['paypalAccount']); + } else if (isset($response['coinbaseAccount'])) { + return CoinbaseAccount::factory($response['coinbaseAccount']); + } else if (isset($response['applePayCard'])) { + return ApplePayCard::factory($response['applePayCard']); + } else if (isset($response['androidPayCard'])) { + return AndroidPayCard::factory($response['androidPayCard']); + } else if (isset($response['amexExpressCheckoutCard'])) { + return AmexExpressCheckoutCard::factory($response['amexExpressCheckoutCard']); + } else if (isset($response['europeBankAccount'])) { + return EuropeBankAccount::factory($response['europeBankAccount']); + } else if (isset($response['usBankAccount'])) { + return UsBankAccount::factory($response['usBankAccount']); + } else if (isset($response['venmoAccount'])) { + return VenmoAccount::factory($response['venmoAccount']); + } else if (isset($response['visaCheckoutCard'])) { + return VisaCheckoutCard::factory($response['visaCheckoutCard']); + } else if (isset($response['masterpassCard'])) { + return MasterpassCard::factory($response['masterpassCard']); + } else if (isset($response['samsungPayCard'])) { + return SamsungPayCard::factory($response['samsungPayCard']); + } else if (is_array($response)) { + return UnknownPaymentMethod::factory($response); + } else { + throw new Exception\Unexpected( + 'Expected payment method' + ); + } + } +} +class_alias('Braintree\PaymentMethodParser', 'Braintree_PaymentMethodParser'); diff --git a/lib/Braintree/Plan.php b/lib/Braintree/Plan.php index b0fb3d0..a65b15a 100644 --- a/lib/Braintree/Plan.php +++ b/lib/Braintree/Plan.php @@ -1,21 +1,25 @@ $response['plans']); - } else { - $plans = array("plan" => array()); - } - - return Braintree_Util::extractAttributeAsArray( - $plans, - 'plan' - ); - } +namespace Braintree; +/** + * @property-read \Braintree\Addon[] $addOns + * @property-read string $id + * @property-read int|null $billingDayOfMonth + * @property-read int $billingFrequency + * @property-read \DateTime $createdAt + * @property-read string $currencyIsoCode + * @property-read string|null $description + * @property-read \Braintree\Discount[] $discounts + * @property-read string $name + * @property-read int|null $numberOfBillingCycles + * @property-read string $price + * @property-read int|null $trialDuration + * @property-read string|null $trialDurationUnit + * @property-read boolean $trialPeriod + * @property-read \DateTime $updatedAt + */ +class Plan extends Base +{ public static function factory($attributes) { $instance = new self(); @@ -28,28 +32,37 @@ protected function _initialize($attributes) { $this->_attributes = $attributes; - $addOnArray = array(); + $addOnArray = []; if (isset($attributes['addOns'])) { foreach ($attributes['addOns'] AS $addOn) { - $addOnArray[] = Braintree_AddOn::factory($addOn); + $addOnArray[] = AddOn::factory($addOn); } } $this->_attributes['addOns'] = $addOnArray; - $discountArray = array(); + $discountArray = []; if (isset($attributes['discounts'])) { foreach ($attributes['discounts'] AS $discount) { - $discountArray[] = Braintree_Discount::factory($discount); + $discountArray[] = Discount::factory($discount); } } $this->_attributes['discounts'] = $discountArray; - $planArray = array(); + $planArray = []; if (isset($attributes['plans'])) { foreach ($attributes['plans'] AS $plan) { - $planArray[] = Braintree_Plan::factory($plan); + $planArray[] = self::factory($plan); } } $this->_attributes['plans'] = $planArray; } + + + // static methods redirecting to gateway + + public static function all() + { + return Configuration::gateway()->plan()->all(); + } } +class_alias('Braintree\Plan', 'Braintree_Plan'); diff --git a/lib/Braintree/PlanGateway.php b/lib/Braintree/PlanGateway.php new file mode 100644 index 0000000..22beaee --- /dev/null +++ b/lib/Braintree/PlanGateway.php @@ -0,0 +1,34 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function all() + { + $path = $this->_config->merchantPath() . '/plans'; + $response = $this->_http->get($path); + if (key_exists('plans', $response)){ + $plans = ["plan" => $response['plans']]; + } else { + $plans = ["plan" => []]; + } + + return Util::extractAttributeAsArray( + $plans, + 'plan' + ); + } +} +class_alias('Braintree\PlanGateway', 'Braintree_PlanGateway'); diff --git a/lib/Braintree/ProcessorResponseTypes.php b/lib/Braintree/ProcessorResponseTypes.php new file mode 100644 index 0000000..746343f --- /dev/null +++ b/lib/Braintree/ProcessorResponseTypes.php @@ -0,0 +1,15 @@ +name = $name; - $this->searchTerms = array(); + $this->searchTerms = []; } - function greaterThanOrEqualTo($value) + public function greaterThanOrEqualTo($value) { $this->searchTerms['min'] = $value; return $this; } - function lessThanOrEqualTo($value) + public function lessThanOrEqualTo($value) { $this->searchTerms['max'] = $value; return $this; } - function is($value) + public function is($value) { $this->searchTerms['is'] = $value; return $this; } - function between($min, $max) + public function between($min, $max) { return $this->greaterThanOrEqualTo($min)->lessThanOrEqualTo($max); } - function toParam() + public function toParam() { return $this->searchTerms; } } +class_alias('Braintree\RangeNode', 'Braintree_RangeNode'); diff --git a/lib/Braintree/ResourceCollection.php b/lib/Braintree/ResourceCollection.php index dc09cf1..2a5dd4d 100644 --- a/lib/Braintree/ResourceCollection.php +++ b/lib/Braintree/ResourceCollection.php @@ -1,20 +1,17 @@ - * $result = Braintree_Customer::all(); + * $result = Customer::all(); * * foreach($result as $transaction) { * print_r($transaction->id); @@ -23,12 +20,12 @@ * * @package Braintree * @subpackage Utility - * @copyright 2010 Braintree Payment Solutions */ -class Braintree_ResourceCollection implements Iterator +class ResourceCollection implements Iterator { - private $_index; private $_batchIndex; + private $_ids; + private $_index; private $_items; private $_pageSize; private $_pager; @@ -38,8 +35,8 @@ class Braintree_ResourceCollection implements Iterator * * expects an array of attributes with literal keys * - * @param array $attributes - * @param array $pagerAttribs + * @param array $response + * @param array $pager */ public function __construct($response, $pager) { @@ -64,7 +61,7 @@ public function current() public function firstItem() { $ids = $this->_ids; - $page = $this->_getPage(array($ids[0])); + $page = $this->_getPage([$ids[0]]); return $page[0]; } @@ -82,7 +79,7 @@ public function next() } /** - * rewinds thtestIterateOverResultse collection to the first item when iterating with foreach + * rewinds the testIterateOverResults collection to the first item when iterating with foreach */ public function rewind() { @@ -115,7 +112,7 @@ private function _getNextPage() { if (empty($this->_ids)) { - $this->_items = array(); + $this->_items = []; } else { @@ -128,21 +125,32 @@ private function _getNextPage() /** * requests the next page of results for the collection * - * @return none + * @return void */ private function _getPage($ids) { - $className = $this->_pager['className']; - $classMethod = $this->_pager['classMethod']; - $methodArgs = array(); + $object = $this->_pager['object']; + $method = $this->_pager['method']; + $methodArgs = []; foreach ($this->_pager['methodArgs'] as $arg) { array_push($methodArgs, $arg); } array_push($methodArgs, $ids); return call_user_func_array( - array($className, $classMethod), + [$object, $method], $methodArgs ); } + + /** + * returns all IDs in the collection + * + * @return array + */ + public function getIds() + { + return $this->_ids; + } } +class_alias('Braintree\ResourceCollection', 'Braintree_ResourceCollection'); diff --git a/lib/Braintree/Result/CreditCardVerification.php b/lib/Braintree/Result/CreditCardVerification.php index 68bc75a..f0f91b3 100644 --- a/lib/Braintree/Result/CreditCardVerification.php +++ b/lib/Braintree/Result/CreditCardVerification.php @@ -1,11 +1,8 @@ _initializeFromArray($attributes); } + /** * initializes instance properties from the keys/values of an array * @ignore * @access protected * @param $aAttribs array of properties to set - single level - * @return none + * @return void */ private function _initializeFromArray($attributes) { + if(isset($attributes['riskData'])) + { + $attributes['riskData'] = RiskData::factory($attributes['riskData']); + } + $this->_attributes = $attributes; foreach($attributes AS $name => $value) { $varName = "_$name"; $this->$varName = $value; - // $this->$varName = Braintree_Util::delimiterToCamelCase($value, '_'); } } + /** - * * @ignore */ public function __get($name) @@ -81,6 +84,17 @@ public function __get($name) public function __toString() { return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + Util::attributesToString($this->_attributes) . ']'; + } + + public static function allStatuses() + { + return [ + CreditCardVerification::FAILED, + CreditCardVerification::GATEWAY_REJECTED, + CreditCardVerification::PROCESSOR_DECLINED, + CreditCardVerification::VERIFIED + ]; } } +class_alias('Braintree\Result\CreditCardVerification', 'Braintree_Result_CreditCardVerification'); diff --git a/lib/Braintree/Result/Error.php b/lib/Braintree/Result/Error.php index 94f6bce..c0d8ba7 100644 --- a/lib/Braintree/Result/Error.php +++ b/lib/Braintree/Result/Error.php @@ -1,11 +1,12 @@ - * $result = Braintree_Transaction::void('abc123'); + * $result = Transaction::void('abc123'); * if ($result->success) { * // Successful Result * } else { - * // Braintree_Result_Error + * // Result\Error * } * * * @package Braintree * @subpackage Result - * @copyright 2010 Braintree Payment Solutions * * @property-read array $params original passed params - * @property-read object $errors Braintree_Error_ErrorCollection - * @property-read object $creditCardVerification credit card verification data + * @property-read \Braintree\Error\ErrorCollection $errors + * @property-read \Braintree\Result\CreditCardVerification $creditCardVerification credit card verification data */ -class Braintree_Result_Error extends Braintree +class Error extends Base { - /** - * - * @var boolean always false + /** + * @var bool always false */ public $success = false; @@ -54,10 +53,10 @@ public function valueForHtmlField($field) $pieces = preg_split("/[\[\]]+/", $field, 0, PREG_SPLIT_NO_EMPTY); $params = $this->params; foreach(array_slice($pieces, 0, -1) as $key) { - $params = $params[Braintree_Util::delimiterToCamelCase($key)]; + $params = $params[Util::delimiterToCamelCase($key)]; } if ($key != 'custom_fields') { - $finalKey = Braintree_Util::delimiterToCamelCase(end($pieces)); + $finalKey = Util::delimiterToCamelCase(end($pieces)); } else { $finalKey = end($pieces); } @@ -73,45 +72,52 @@ public function valueForHtmlField($field) public function __construct($response) { $this->_attributes = $response; - $this->_set('errors', new Braintree_Error_ErrorCollection($response['errors'])); + $this->_set('errors', new ErrorCollection($response['errors'])); if(isset($response['verification'])) { - $this->_set('creditCardVerification', new Braintree_Result_CreditCardVerification($response['verification'])); + $this->_set('creditCardVerification', new CreditCardVerification($response['verification'])); } else { $this->_set('creditCardVerification', null); } if(isset($response['transaction'])) { - $this->_set('transaction', Braintree_Transaction::factory($response['transaction'])); + $this->_set('transaction', Transaction::factory($response['transaction'])); } else { $this->_set('transaction', null); } if(isset($response['subscription'])) { - $this->_set('subscription', Braintree_Subscription::factory($response['subscription'])); + $this->_set('subscription', Subscription::factory($response['subscription'])); } else { $this->_set('subscription', null); } if(isset($response['merchantAccount'])) { - $this->_set('merchantAccount', Braintree_MerchantAccount::factory($response['merchantAccount'])); + $this->_set('merchantAccount', MerchantAccount::factory($response['merchantAccount'])); } else { $this->_set('merchantAccount', null); } + + if(isset($response['verification'])) { + $this->_set('verification', new CreditCardVerification($response['verification'])); + } else { + $this->_set('verification', null); + } } /** * create a printable representation of the object as: * ClassName[property=value, property=value] * @ignore - * @return var + * @return string */ public function __toString() { - $output = Braintree_Util::attributesToString($this->_attributes); + $output = Util::attributesToString($this->_attributes); if (isset($this->_creditCardVerification)) { $output .= sprintf('%s', $this->_creditCardVerification); } - return __CLASS__ .'['.$output.']'; + return __CLASS__ .'[' . $output . ']'; } } +class_alias('Braintree\Result\Error', 'Braintree_Result_Error'); diff --git a/lib/Braintree/Result/Successful.php b/lib/Braintree/Result/Successful.php index 8b7d16c..69f087c 100644 --- a/lib/Braintree/Result/Successful.php +++ b/lib/Braintree/Result/Successful.php @@ -1,11 +1,8 @@ customer like so: * * - * $result = Braintree_Customer::create(array('first_name' => "John")); + * $result = Customer::create(array('first_name' => "John")); * if ($result->success) { - * // Braintree_Result_Successful + * // Successful * echo "Created customer {$result->customer->id}"; * } else { - * // Braintree_Result_Error + * // Error * } * * * * @package Braintree * @subpackage Result - * @copyright 2010 Braintree Payment Solutions */ -class Braintree_Result_Successful extends Braintree_Instance +class Successful extends Instance { /** * @@ -42,28 +38,33 @@ class Braintree_Result_Successful extends Braintree_Instance * * @var string stores the internal name of the object providing access to */ - private $_returnObjectName; + private $_returnObjectNames; /** * @ignore - * @param string $classToReturn name of class to instantiate + * @param array|null $objsToReturn + * @param array|null $propertyNames */ - public function __construct($objToReturn = null) + public function __construct($objsToReturn = [], $propertyNames = []) { - if(!empty($objToReturn)) { - // get a lowercase direct name for the property - $property = Braintree_Util::cleanClassName( - get_class($objToReturn) - ); + // Sanitize arguments (preserves backwards compatibility) + if (!is_array($objsToReturn)) { $objsToReturn = [$objsToReturn]; } + if (!is_array($propertyNames)) { $propertyNames = [$propertyNames]; } + + $objects = $this->_mapPropertyNamesToObjsToReturn($propertyNames, $objsToReturn); + $this->_attributes = []; + $this->_returnObjectNames = []; + + foreach ($objects as $propertyName => $objToReturn) { + // save the name for indirect access - $this->_returnObjectName = $property; + array_push($this->_returnObjectNames, $propertyName); // create the property! - $this->$property = $objToReturn; + $this->$propertyName = $objToReturn; } } - /** * * @ignore @@ -71,8 +72,21 @@ public function __construct($objToReturn = null) */ public function __toString() { - $returnObject = $this->_returnObjectName; - return __CLASS__ . '['.$this->$returnObject->__toString().']'; + $objects = []; + foreach ($this->_returnObjectNames as $returnObjectName) { + array_push($objects, $returnObjectName); + } + return __CLASS__ . '[' . implode(', ', $objects) . ']'; } + private function _mapPropertyNamesToObjsToReturn($propertyNames, $objsToReturn) { + if(count($objsToReturn) != count($propertyNames)) { + $propertyNames = []; + foreach ($objsToReturn as $obj) { + array_push($propertyNames, Util::cleanClassName(get_class($obj))); + } + } + return array_combine($propertyNames, $objsToReturn); + } } +class_alias('Braintree\Result\Successful', 'Braintree_Result_Successful'); diff --git a/lib/Braintree/Result/UsBankAccountVerification.php b/lib/Braintree/Result/UsBankAccountVerification.php new file mode 100644 index 0000000..3c94c9b --- /dev/null +++ b/lib/Braintree/Result/UsBankAccountVerification.php @@ -0,0 +1,112 @@ +_initializeFromArray($attributes); + + $usBankAccount = isset($attributes['usBankAccount']) ? + UsBankAccount::factory($attributes['usBankAccount']) : + null; + $this->usBankAccount = $usBankAccount; + } + + /** + * initializes instance properties from the keys/values of an array + * @ignore + * @access protected + * @param $aAttribs array of properties to set - single level + * @return void + */ + private function _initializeFromArray($attributes) + { + $this->_attributes = $attributes; + foreach($attributes AS $name => $value) { + $varName = "_$name"; + $this->$varName = $value; + } + } + + /** + * @ignore + */ + public function __get($name) + { + $varName = "_$name"; + return isset($this->$varName) ? $this->$varName : null; + } + + /** + * returns a string representation of the customer + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } + + public static function allStatuses() + { + return [ + UsBankAccountVerification::FAILED, + UsBankAccountVerification::GATEWAY_REJECTED, + UsBankAccountVerification::PROCESSOR_DECLINED, + UsBankAccountVerification::VERIFIED, + UsBankAccountVerification::PENDING, + ]; + } + + public static function allVerificationMethods() + { + return [ + UsBankAccountVerification::TOKENIZED_CHECK, + UsBankAccountVerification::NETWORK_CHECK, + UsBankAccountVerification::INDEPENDENT_CHECK, + UsBankAccountVerification::MICRO_TRANSFERS, + ]; + } +} +class_alias('Braintree\Result\UsBankAccountVerification', 'Braintree_Result_UsBankAccountVerification'); diff --git a/lib/Braintree/RevokedPaymentMethodMetadata.php b/lib/Braintree/RevokedPaymentMethodMetadata.php new file mode 100644 index 0000000..85a2036 --- /dev/null +++ b/lib/Braintree/RevokedPaymentMethodMetadata.php @@ -0,0 +1,53 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $customerId + * @property-read string $token + * @property-read string $revokedPaymentMethod + */ +class RevokedPaymentMethodMetadata extends Base +{ + /** + * factory method: returns an instance of RevokedPaymentMethodMetadata + * to the requesting method, with populated properties + * + * @ignore + * @return RevokedPaymentMethodMetadata + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->revokedPaymentMethod = PaymentMethodParser::parsePaymentMethod($attributes); + $instance->customerId = $instance->revokedPaymentMethod->customerId; + $instance->token = $instance->revokedPaymentMethod->token; + return $instance; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} +class_alias('Braintree\RevokedPaymentMethodMetadata', 'Braintree_RevokedPaymentMethodMetadata'); diff --git a/lib/Braintree/RiskData.php b/lib/Braintree/RiskData.php new file mode 100644 index 0000000..11552e1 --- /dev/null +++ b/lib/Braintree/RiskData.php @@ -0,0 +1,35 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the risk data + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + +} +class_alias('Braintree\RiskData', 'Braintree_RiskData'); diff --git a/lib/Braintree/SamsungPayCard.php b/lib/Braintree/SamsungPayCard.php new file mode 100644 index 0000000..f774c98 --- /dev/null +++ b/lib/Braintree/SamsungPayCard.php @@ -0,0 +1,138 @@ +default; + } + + /** + * checks whether the card is expired based on the current date + * + * @return boolean + */ + public function isExpired() + { + return $this->expired; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $creditCardAttribs array of creditcard data + * @return void + */ + protected function _initialize($creditCardAttribs) + { + // set the attributes + $this->_attributes = $creditCardAttribs; + + // map each address into its own object + $billingAddress = isset($creditCardAttribs['billingAddress']) ? + Address::factory($creditCardAttribs['billingAddress']) : + null; + + $subscriptionArray = []; + if (isset($creditCardAttribs['subscriptions'])) { + foreach ($creditCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + $this->_set('billingAddress', $billingAddress); + $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); + $this->_set('maskedNumber', $this->bin . '******' . $this->last4); + } + + /** + * returns false if comparing object is not a SamsungPayCard, + * or is a SamsungPayCard with a different id + * + * @param object $otherSamsungPayCard customer to compare against + * @return boolean + */ + public function isEqual($otherSamsungPayCard) + { + return !($otherSamsungPayCard instanceof self) ? false : $this->token === $otherSamsungPayCard->token; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + + /** + * factory method: returns an instance of SamsungPayCard + * to the requesting method, with populated properties + * + * @ignore + * @return SamsungPayCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } +} +class_alias('Braintree\SamsungPayCard', 'Braintree_SamsungPayCard'); diff --git a/lib/Braintree/SettlementBatchSummary.php b/lib/Braintree/SettlementBatchSummary.php index 98591a7..2906f20 100644 --- a/lib/Braintree/SettlementBatchSummary.php +++ b/lib/Braintree/SettlementBatchSummary.php @@ -1,57 +1,16 @@ $settlement_date); - if (isset($groupByCustomField)) - { - $criteria['group_by_custom_field'] = $groupByCustomField; - } - $params = array('settlement_batch_summary' => $criteria); - $response = Braintree_Http::post('/settlement_batch_summary', $params); - - if (isset($groupByCustomField)) - { - $response['settlementBatchSummary']['records'] = self::_underscoreCustomField( - $groupByCustomField, - $response['settlementBatchSummary']['records'] - ); - } - - return self::_verifyGatewayResponse($response); - } - - private static function _underscoreCustomField($groupByCustomField, $records) - { - $updatedRecords = array(); - - foreach ($records as $record) - { - $camelized = Braintree_Util::delimiterToCamelCase($groupByCustomField); - $record[$groupByCustomField] = $record[$camelized]; - unset($record[$camelized]); - $updatedRecords[] = $record; - } - - return $updatedRecords; - } - - private static function _verifyGatewayResponse($response) - { - if (isset($response['settlementBatchSummary'])) { - return new Braintree_Result_Successful( - self::factory($response['settlementBatchSummary']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected settlementBatchSummary or apiErrorResponse" - ); - } - } +namespace Braintree; +/** + * @property-read array $records + */ +class SettlementBatchSummary extends Base +{ + /** + * + * @param array $attributes + * @return SettlementBatchSummary + */ public static function factory($attributes) { $instance = new self(); @@ -61,6 +20,7 @@ public static function factory($attributes) /** * @ignore + * @param array $attributes */ protected function _initialize($attributes) { @@ -71,4 +31,18 @@ public function records() { return $this->_attributes['records']; } + + + /** + * static method redirecting to gateway + * + * @param string $settlement_date Date YYYY-MM-DD + * @param string $groupByCustomField + * @return Result\Successful|Result\Error + */ + public static function generate($settlement_date, $groupByCustomField = NULL) + { + return Configuration::gateway()->settlementBatchSummary()->generate($settlement_date, $groupByCustomField); + } } +class_alias('Braintree\SettlementBatchSummary', 'Braintree_SettlementBatchSummary'); diff --git a/lib/Braintree/SettlementBatchSummaryGateway.php b/lib/Braintree/SettlementBatchSummaryGateway.php new file mode 100644 index 0000000..e70db87 --- /dev/null +++ b/lib/Braintree/SettlementBatchSummaryGateway.php @@ -0,0 +1,106 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * + * @param string $settlement_date + * @param string $groupByCustomField + * @return SettlementBatchSummary|Result\Error + */ + public function generate($settlement_date, $groupByCustomField = NULL) + { + $criteria = ['settlement_date' => $settlement_date]; + if (isset($groupByCustomField)) + { + $criteria['group_by_custom_field'] = $groupByCustomField; + } + $params = ['settlement_batch_summary' => $criteria]; + $path = $this->_config->merchantPath() . '/settlement_batch_summary'; + $response = $this->_http->post($path, $params); + + if (isset($groupByCustomField)) + { + $response['settlementBatchSummary']['records'] = $this->_underscoreCustomField( + $groupByCustomField, + $response['settlementBatchSummary']['records'] + ); + } + + return $this->_verifyGatewayResponse($response); + } + + /** + * + * @param string $groupByCustomField + * @param array $records + * @return array + */ + private function _underscoreCustomField($groupByCustomField, $records) + { + $updatedRecords = []; + + foreach ($records as $record) + { + $camelized = Util::delimiterToCamelCase($groupByCustomField); + $record[$groupByCustomField] = $record[$camelized]; + unset($record[$camelized]); + $updatedRecords[] = $record; + } + + return $updatedRecords; + } + + /** + * + * @param array $response + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['settlementBatchSummary'])) { + return new Result\Successful( + SettlementBatchSummary::factory($response['settlementBatchSummary']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected settlementBatchSummary or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\SettlementBatchSummaryGateway', 'Braintree_SettlementBatchSummaryGateway'); diff --git a/lib/Braintree/SignatureService.php b/lib/Braintree/SignatureService.php new file mode 100644 index 0000000..5e12369 --- /dev/null +++ b/lib/Braintree/SignatureService.php @@ -0,0 +1,24 @@ +key = $key; + $this->digest = $digest; + } + + public function sign($payload) + { + return $this->hash($payload) . "|" . $payload; + } + + public function hash($data) + { + return call_user_func($this->digest, $this->key, $data); + } + +} +class_alias('Braintree\SignatureService', 'Braintree_SignatureService'); diff --git a/lib/Braintree/Subscription.php b/lib/Braintree/Subscription.php index 12bbb54..8530b36 100644 --- a/lib/Braintree/Subscription.php +++ b/lib/Braintree/Subscription.php @@ -1,17 +1,49 @@ == More information == * - * For more detailed information on Subscriptions, see {@link http://www.braintreepayments.com/gateway/subscription-api http://www.braintreepaymentsolutions.com/gateway/subscription-api} + * For more detailed information on Subscriptions, see {@link https://developers.braintreepayments.com/reference/response/subscription/php https://developers.braintreepayments.com/reference/response/subscription/php} * * PHP Version 5 * * @package Braintree - * @copyright 2010 Braintree Payment Solutions + * + * @property-read \Braintree\Addon[] $addOns + * @property-read string $balance + * @property-read int $billingDayOfMonth + * @property-read \DateTime $billingPeriodEndDate + * @property-read \DateTime $billingPeriodStartDate + * @property-read \DateTime $createdAt + * @property-read int $currentBillingCycle + * @property-read int|null $daysPastDue + * @property-read string|null $description + * @property-read \Braintree\Descriptor|null $descriptor + * @property-read \Braintree\Discount[] $discounts + * @property-read int $failureCount + * @property-read \DateTime $firstBillingDate + * @property-read string $id + * @property-read string $merchantAccountId + * @property-read boolean $neverExpires + * @property-read string $nextBillingPeriodAmount + * @property-read \DateTime $nextBillingDate + * @property-read int|null $numberOfBillingCycles + * @property-read \DateTime|null $paidThroughDate + * @property-read string $paymentMethodToken + * @property-read string $planId + * @property-read string $price + * @property-read string $status + * @property-read \Braintree\Subscription\StatusDetails[] $statusHistory + * @property-read \Braintree\Transaction[] $transactions + * @property-read int $trialDuration + * @property-read string $trialDurationUnit + * @property-read boolean $trialPeriod + * @property-read \DateTime $updatedAt */ -class Braintree_Subscription extends Braintree +class Subscription extends Base { const ACTIVE = 'Active'; const CANCELED = 'Canceled'; @@ -19,12 +51,10 @@ class Braintree_Subscription extends Braintree const PAST_DUE = 'Past Due'; const PENDING = 'Pending'; - public static function create($attributes) - { - Braintree_Util::verifyKeys(self::_createSignature(), $attributes); - $response = Braintree_Http::post('/subscriptions', array('subscription' => $attributes)); - return self::_verifyGatewayResponse($response); - } + // Subscription Sources + const API = 'api'; + const CONTROL_PANEL = 'control_panel'; + const RECURRING = 'recurring'; /** * @ignore @@ -37,138 +67,6 @@ public static function factory($attributes) return $instance; } - public static function find($id) - { - self::_validateId($id); - - try { - $response = Braintree_Http::get('/subscriptions/' . $id); - return self::factory($response['subscription']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound('subscription with id ' . $id . ' not found'); - } - - } - - public static function search($query) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - - - $response = Braintree_Http::post('/subscriptions/advanced_search_ids', array('search' => $criteria)); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); - - return new Braintree_ResourceCollection($response, $pager); - } - - public static function fetch($query, $ids) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - $criteria["ids"] = Braintree_SubscriptionSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/subscriptions/advanced_search', array('search' => $criteria)); - - return Braintree_Util::extractAttributeAsArray( - $response['subscriptions'], - 'subscription' - ); - } - - public static function update($subscriptionId, $attributes) - { - Braintree_Util::verifyKeys(self::_updateSignature(), $attributes); - $response = Braintree_Http::put( - '/subscriptions/' . $subscriptionId, - array('subscription' => $attributes) - ); - return self::_verifyGatewayResponse($response); - } - - public static function retryCharge($subscriptionId, $amount = null) - { - $transaction_params = array('type' => Braintree_Transaction::SALE, - 'subscriptionId' => $subscriptionId); - if (isset($amount)) { - $transaction_params['amount'] = $amount; - } - - $response = Braintree_Http::post( - '/transactions', - array('transaction' => $transaction_params)); - return self::_verifyGatewayResponse($response); - } - - public static function cancel($subscriptionId) - { - $response = Braintree_Http::put('/subscriptions/' . $subscriptionId . '/cancel'); - return self::_verifyGatewayResponse($response); - } - - private static function _createSignature() - { - return array_merge( - array( - 'billingDayOfMonth', - 'firstBillingDate', - 'id', - 'merchantAccountId', - 'neverExpires', - 'numberOfBillingCycles', - 'paymentMethodToken', - 'planId', - 'price', - 'trialDuration', - 'trialDurationUnit', - 'trialPeriod', - array('descriptor' => array('name', 'phone')), - array('options' => array('doNotInheritAddOnsOrDiscounts', 'startImmediately')), - ), - self::_addOnDiscountSignature() - ); - } - - private static function _updateSignature() - { - return array_merge( - array( - 'merchantAccountId', 'numberOfBillingCycles', 'paymentMethodToken', 'planId', - 'id', 'neverExpires', 'price', - array('descriptor' => array('name', 'phone')), - array('options' => array('prorateCharges', 'replaceAllAddOnsAndDiscounts', 'revertSubscriptionOnProrationFailure')), - ), - self::_addOnDiscountSignature() - ); - } - - private static function _addOnDiscountSignature() - { - return array( - array( - 'addOns' => array( - array('add' => array('amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('update' => array('amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('remove' => array('_anyKey_')), - ) - ), - array( - 'discounts' => array( - array('add' => array('amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('update' => array('amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('remove' => array('_anyKey_')), - ) - ) - ); - } - /** * @ignore */ @@ -176,81 +74,102 @@ protected function _initialize($attributes) { $this->_attributes = $attributes; - $addOnArray = array(); + $addOnArray = []; if (isset($attributes['addOns'])) { foreach ($attributes['addOns'] AS $addOn) { - $addOnArray[] = Braintree_AddOn::factory($addOn); + $addOnArray[] = AddOn::factory($addOn); } } $this->_attributes['addOns'] = $addOnArray; - $discountArray = array(); + $discountArray = []; if (isset($attributes['discounts'])) { foreach ($attributes['discounts'] AS $discount) { - $discountArray[] = Braintree_Discount::factory($discount); + $discountArray[] = Discount::factory($discount); } } $this->_attributes['discounts'] = $discountArray; if (isset($attributes['descriptor'])) { - $this->_set('descriptor', new Braintree_Descriptor($attributes['descriptor'])); + $this->_set('descriptor', new Descriptor($attributes['descriptor'])); } - $transactionArray = array(); + if (isset($attributes['description'])) { + $this->_set('description', $attributes['description']); + } + + $statusHistory = []; + if (isset($attributes['statusHistory'])) { + foreach ($attributes['statusHistory'] AS $history) { + $statusHistory[] = new Subscription\StatusDetails($history); + } + } + $this->_attributes['statusHistory'] = $statusHistory; + + $transactionArray = []; if (isset($attributes['transactions'])) { foreach ($attributes['transactions'] AS $transaction) { - $transactionArray[] = Braintree_Transaction::factory($transaction); + $transactionArray[] = Transaction::factory($transaction); } } $this->_attributes['transactions'] = $transactionArray; } /** - * @ignore + * returns a string representation of the customer + * @return string */ - private static function _validateId($id = null) { - if (empty($id)) { - throw new InvalidArgumentException( - 'expected subscription id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid subscription id.' - ); + public function __toString() + { + $excludedAttributes = ['statusHistory']; + + $displayAttributes = []; + foreach($this->_attributes as $key => $val) { + if (!in_array($key, $excludedAttributes)) { + $displayAttributes[$key] = $val; + } } + + return __CLASS__ . '[' . + Util::attributesToString($displayAttributes) .']'; } - /** - * @ignore - */ - private static function _verifyGatewayResponse($response) + + + // static methods redirecting to gateway + + public static function create($attributes) { - if (isset($response['subscription'])) { - return new Braintree_Result_Successful( - self::factory($response['subscription']) - ); - } else if (isset($response['transaction'])) { - // return a populated instance of Braintree_Transaction, for subscription retryCharge - return new Braintree_Result_Successful( - Braintree_Transaction::factory($response['transaction']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected subscription, transaction, or apiErrorResponse" - ); - } + return Configuration::gateway()->subscription()->create($attributes); } - /** - * returns a string representation of the customer - * @return string - */ - public function __toString() + public static function find($id) { - return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + return Configuration::gateway()->subscription()->find($id); + } + + public static function search($query) + { + return Configuration::gateway()->subscription()->search($query); } + public static function fetch($query, $ids) + { + return Configuration::gateway()->subscription()->fetch($query, $ids); + } + + public static function update($subscriptionId, $attributes) + { + return Configuration::gateway()->subscription()->update($subscriptionId, $attributes); + } + + public static function retryCharge($subscriptionId, $amount = null, $submitForSettlement = false) + { + return Configuration::gateway()->subscription()->retryCharge($subscriptionId, $amount, $submitForSettlement); + } + + public static function cancel($subscriptionId) + { + return Configuration::gateway()->subscription()->cancel($subscriptionId); + } } +class_alias('Braintree\Subscription', 'Braintree_Subscription'); diff --git a/lib/Braintree/Subscription/StatusDetails.php b/lib/Braintree/Subscription/StatusDetails.php new file mode 100644 index 0000000..9f588ce --- /dev/null +++ b/lib/Braintree/Subscription/StatusDetails.php @@ -0,0 +1,24 @@ +== More information == + * + * For more detailed information on Subscriptions, see {@link https://developers.braintreepayments.com/reference/response/subscription/php https://developers.braintreepayments.com/reference/response/subscription/php} + * + * PHP Version 5 + * + * @package Braintree + */ +class SubscriptionGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function create($attributes) + { + Util::verifyKeys(self::_createSignature(), $attributes); + $path = $this->_config->merchantPath() . '/subscriptions'; + $response = $this->_http->post($path, ['subscription' => $attributes]); + return $this->_verifyGatewayResponse($response); + } + + public function find($id) + { + $this->_validateId($id); + + try { + $path = $this->_config->merchantPath() . '/subscriptions/' . $id; + $response = $this->_http->get($path); + return Subscription::factory($response['subscription']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('subscription with id ' . $id . ' not found'); + } + + } + + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + + $path = $this->_config->merchantPath() . '/subscriptions/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } + + public function fetch($query, $ids) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $criteria["ids"] = SubscriptionSearch::ids()->in($ids)->toparam(); + $path = $this->_config->merchantPath() . '/subscriptions/advanced_search'; + $response = $this->_http->post($path, ['search' => $criteria]); + + return Util::extractAttributeAsArray( + $response['subscriptions'], + 'subscription' + ); + } + + public function update($subscriptionId, $attributes) + { + Util::verifyKeys(self::_updateSignature(), $attributes); + $path = $this->_config->merchantPath() . '/subscriptions/' . $subscriptionId; + $response = $this->_http->put($path, ['subscription' => $attributes]); + return $this->_verifyGatewayResponse($response); + } + + public function retryCharge($subscriptionId, $amount = null, $submitForSettlement = false) + { + $transaction_params = ['type' => Transaction::SALE, + 'subscriptionId' => $subscriptionId]; + if (isset($amount)) { + $transaction_params['amount'] = $amount; + } + if ($submitForSettlement) { + $transaction_params['options'] = ['submitForSettlement' => $submitForSettlement]; + } + + $path = $this->_config->merchantPath() . '/transactions'; + $response = $this->_http->post($path, ['transaction' => $transaction_params]); + return $this->_verifyGatewayResponse($response); + } + + public function cancel($subscriptionId) + { + $path = $this->_config->merchantPath() . '/subscriptions/' . $subscriptionId . '/cancel'; + $response = $this->_http->put($path); + return $this->_verifyGatewayResponse($response); + } + + private static function _createSignature() + { + return array_merge( + [ + 'billingDayOfMonth', + 'firstBillingDate', + 'createdAt', + 'updatedAt', + 'id', + 'merchantAccountId', + 'neverExpires', + 'numberOfBillingCycles', + 'paymentMethodToken', + 'paymentMethodNonce', + 'planId', + 'price', + 'trialDuration', + 'trialDurationUnit', + 'trialPeriod', + ['descriptor' => ['name', 'phone', 'url']], + ['options' => [ + 'doNotInheritAddOnsOrDiscounts', + 'startImmediately', + ['paypal' => ['description']] + ]], + ], + self::_addOnDiscountSignature() + ); + } + + private static function _updateSignature() + { + return array_merge( + [ + 'merchantAccountId', 'numberOfBillingCycles', 'paymentMethodToken', 'planId', + 'paymentMethodNonce', 'id', 'neverExpires', 'price', + ['descriptor' => ['name', 'phone', 'url']], + ['options' => [ + 'prorateCharges', + 'replaceAllAddOnsAndDiscounts', + 'revertSubscriptionOnProrationFailure', + ['paypal' => ['description']] + ]], + ], + self::_addOnDiscountSignature() + ); + } + + private static function _addOnDiscountSignature() + { + return [ + [ + 'addOns' => [ + ['add' => ['amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['update' => ['amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['remove' => ['_anyKey_']], + ] + ], + [ + 'discounts' => [ + ['add' => ['amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['update' => ['amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['remove' => ['_anyKey_']], + ] + ] + ]; + } + + /** + * @ignore + */ + private function _validateId($id = null) { + if (empty($id)) { + throw new InvalidArgumentException( + 'expected subscription id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { + throw new InvalidArgumentException( + $id . ' is an invalid subscription id.' + ); + } + } + + /** + * @ignore + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['subscription'])) { + return new Result\Successful( + Subscription::factory($response['subscription']) + ); + } else if (isset($response['transaction'])) { + // return a populated instance of Transaction, for subscription retryCharge + return new Result\Successful( + Transaction::factory($response['transaction']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected subscription, transaction, or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\SubscriptionGateway', 'Braintree_SubscriptionGateway'); diff --git a/lib/Braintree/SubscriptionSearch.php b/lib/Braintree/SubscriptionSearch.php index 01cba1a..ba2a5ef 100644 --- a/lib/Braintree/SubscriptionSearch.php +++ b/lib/Braintree/SubscriptionSearch.php @@ -1,64 +1,72 @@ '378734493671000', 'Discover' => '6011000990139424', 'MasterCard' => '5105105105105100', 'Visa' => '4000111111111115', - ); + ]; + + public static $amexPayWithPoints = [ + 'Success' => "371260714673002", + 'IneligibleCard' => "378267515471109", + 'InsufficientPoints' => "371544868764018", + ]; + public static $disputes = [ + 'Chargeback' => '4023898493988028', + ]; public static function getAll() { return array_merge( self::$amExes, self::$discoverCards, + self::$eloCards, self::$masterCards, self::$visas ); } } +class_alias('Braintree\Test\CreditCardNumbers', 'Braintree_Test_CreditCardNumbers'); diff --git a/lib/Braintree/Test/MerchantAccount.php b/lib/Braintree/Test/MerchantAccount.php index 85a7774..cc00c67 100644 --- a/lib/Braintree/Test/MerchantAccount.php +++ b/lib/Braintree/Test/MerchantAccount.php @@ -1,12 +1,13 @@ testing()->settle($transactionId); + } + + /** + * settlement confirm a transaction by id in sandbox + * + * @param string $id transaction id + * @param Configuration $config gateway config + * @return Transaction + */ + public static function settlementConfirm($transactionId) + { + return Configuration::gateway()->testing()->settlementConfirm($transactionId); + } + + /** + * settlement decline a transaction by id in sandbox + * + * @param string $id transaction id + * @param Configuration $config gateway config + * @return Transaction + */ + public static function settlementDecline($transactionId) + { + return Configuration::gateway()->testing()->settlementDecline($transactionId); + } + + /** + * settlement pending a transaction by id in sandbox + * + * @param string $id transaction id + * @param Configuration $config gateway config + * @return Transaction + */ + public static function settlementPending($transactionId) + { + return Configuration::gateway()->testing()->settlementPending($transactionId); + } +} +class_alias('Braintree\Test\Transaction', 'Braintree_Test_Transaction'); diff --git a/lib/Braintree/Test/TransactionAmounts.php b/lib/Braintree/Test/TransactionAmounts.php index 7dc1a5b..9258d72 100644 --- a/lib/Braintree/Test/TransactionAmounts.php +++ b/lib/Braintree/Test/TransactionAmounts.php @@ -1,11 +1,5 @@ _gateway = $gateway; + $this->_config = $gateway->config; + $this->_http = new Http($this->_config); + } + + public function settle($transactionId) + { + return self::_doTestRequest('/settle', $transactionId); + } + + public function settlementPending($transactionId) + { + return self::_doTestRequest('/settlement_pending', $transactionId); + } + + public function settlementConfirm($transactionId) + { + return self::_doTestRequest('/settlement_confirm', $transactionId); + } + + public function settlementDecline($transactionId) + { + return self::_doTestRequest('/settlement_decline', $transactionId); + } + + private function _doTestRequest($testPath, $transactionId) + { + self::_checkEnvironment(); + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . $testPath; + $response = $this->_http->put($path); + return Transaction::factory($response['transaction']); + } + + private function _checkEnvironment() + { + if (Configuration::$global->getEnvironment() === 'production') { + throw new Exception\TestOperationPerformedInProduction(); + } + } +} +class_alias('Braintree\TestingGateway', 'Braintree_TestingGateway'); diff --git a/lib/Braintree/TextNode.php b/lib/Braintree/TextNode.php index f193d1d..9f932a3 100644 --- a/lib/Braintree/TextNode.php +++ b/lib/Braintree/TextNode.php @@ -1,10 +1,12 @@ searchTerms["contains"] = strval($value); return $this; } } +class_alias('Braintree\TextNode', 'Braintree_TextNode'); diff --git a/lib/Braintree/ThreeDSecureInfo.php b/lib/Braintree/ThreeDSecureInfo.php new file mode 100644 index 0000000..f7abae2 --- /dev/null +++ b/lib/Braintree/ThreeDSecureInfo.php @@ -0,0 +1,35 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the three d secure info + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + +} +class_alias('Braintree\ThreeDSecureInfo', 'Braintree_ThreeDSecureInfo'); diff --git a/lib/Braintree/Transaction.php b/lib/Braintree/Transaction.php index eb9babf..e3cd6b6 100644 --- a/lib/Braintree/Transaction.php +++ b/lib/Braintree/Transaction.php @@ -1,13 +1,8 @@ Minimalistic example: * - * Braintree_Transaction::saleNoValidate(array( + * Transaction::saleNoValidate(array( * 'amount' => '100.00', * 'creditCard' => array( * 'number' => '5105105105105100', @@ -26,7 +21,7 @@ * * Full example: * - * Braintree_Transaction::saleNoValidate(array( + * Transaction::saleNoValidate(array( * 'amount' => '100.00', * 'orderId' => '123', * 'channel' => 'MyShoppingCardProvider', @@ -42,7 +37,7 @@ * 'id' => 'customer_123', * 'firstName' => 'Dan', * 'lastName' => 'Smith', - * 'company' => 'Braintree Payment Solutions', + * 'company' => 'Braintree', * 'email' => 'dan@example.com', * 'phone' => '419-555-1234', * 'fax' => '419-555-1235', @@ -82,7 +77,7 @@ * a transaction can be stored in the vault by setting * transaction[options][storeInVault] to true. * - * $transaction = Braintree_Transaction::saleNoValidate(array( + * $transaction = Transaction::saleNoValidate(array( * 'customer' => array( * 'firstName' => 'Adam', * 'lastName' => 'Williams' @@ -105,7 +100,7 @@ * To also store the billing address in the vault, pass the * addBillingAddressToPaymentMethod option. * - * Braintree_Transaction.saleNoValidate(array( + * Transaction.saleNoValidate(array( * ... * 'options' => array( * 'storeInVault' => true @@ -126,7 +121,7 @@ * $transaction[options][submitForSettlement] to true. * * - * $transaction = Braintree_Transaction::saleNoValidate(array( + * $transaction = Transaction::saleNoValidate(array( * 'amount' => '100.00', * 'creditCard' => array( * 'number' => '5105105105105100', @@ -140,35 +135,83 @@ * * == More information == * - * For more detailed information on Transactions, see {@link http://www.braintreepayments.com/gateway/transaction-api http://www.braintreepaymentsolutions.com/gateway/transaction-api} + * For more detailed information on Transactions, see {@link https://developers.braintreepayments.com/reference/response/transaction/php https://developers.braintreepayments.com/reference/response/transaction/php} * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions * * + * @property-read \Braintree\AddOn[] $addons + * @property-read string $additionalProcessorResponse raw response from processor + * @property-read string $amount transaction amount + * @property-read \Braintree\AmexExpressCheckoutCardDetails $amexExpressCheckoutCardDetails transaction Amex Express Checkout card info + * @property-read \Braintree\AndroidPayCardDetails $androidPayCardDetails transaction Android Pay card info + * @property-read \Braintree\ApplePayCardDetails $applePayCardDetails transaction Apple Pay card info + * @property-read \Braintree\AuthorizationAdjustment[] $authorizationAdjustments populated when a transaction has authorization adjustments created when submitted for settlement + * @property-read \DateTime $authorizationExpiresAt DateTime authorization will expire * @property-read string $avsErrorResponseCode * @property-read string $avsPostalCodeResponseCode * @property-read string $avsStreetAddressResponseCode + * @property-read \Braintree\Transaction\AddressDetails $billingDetails transaction billing address + * @property-read string $channel + * @property-read \Braintree\CoinbaseDetails $coinbaseDetails transaction Coinbase account info + * @property-read \DateTime $createdAt transaction created DateTime + * @property-read \Braintree\CreditCardDetails $creditCardDetails transaction credit card info + * @property-read string $currencyIsoCode + * @property-read array $customFields custom fields passed with the request + * @property-read \Braintree\Transaction\CustomerDetails $customerDetails transaction customer info * @property-read string $cvvResponseCode + * @property-read \Braintree\Descriptor $descriptor + * @property-read Braintree\DisbursementDetails $disbursementDetails populated when transaction is disbursed + * @property-read string $discountAmount + * @property-read \Braintree\Discount[] $discounts + * @property-read \Braintree\Dispute[] $disputes populated when transaction is disputed + * @property-read string $escrowStatus + * @property-read \Braintree\FacilitatedDetails $facilitatedDetails + * @property-read \Braintree\FacilitatorDetails $facilitatorDetails + * @property-read string $gatewayRejectionReason * @property-read string $id transaction id - * @property-read string $amount transaction amount - * @property-read object $billingDetails transaction billing address - * @property-read string $createdAt transaction created timestamp - * @property-read object $creditCardDetails transaction credit card info - * @property-read object $customerDetails transaction customer info - * @property-read array $customFields custom fields passed with the request + * @property-read \Braintree\IdealPayment $idealPaymentDetails transaction Ideal Payment info + * @property-read \Braintree\TransactionLineItem[] $lineItems + * @property-read \Braintree\MasterpassCardDetails $masterpassCardDetails transaction Masterpass card info + * @property-read string $merchantAccountId + * @property-read string $networkTransactionId + * @property-read string $orderId + * @property-read string $paymentInstrumentType + * @property-read \Braintree\PayPalDetails $paypalDetails transaction paypal account info + * @property-read string $planId + * @property-read string $processorAuthorizationCode * @property-read string $processorResponseCode gateway response code - * @property-read object $shippingDetails transaction shipping address + * @property-read string $processorResponseText + * @property-read string $processorResponseType + * @property-read string $processorSettlementResponseCode + * @property-read string $processorSettlementResponseText + * @property-read string $purchaseOrderNumber + * @property-read mixed $reccuring + * @property-read mixed $refundIds + * @property-read string $refundedTransactionId + * @property-read \Braintree\RiskData $riskData + * @property-read \Braintree\SamsungPayCardDetails $samsungPayCardDetails transaction Samsung Pay card info + * @property-read string $serviceFeeAmount + * @property-read string $settlementBatchId + * @property-read string $shippingAmount + * @property-read \Braintree\Transaction\AddressDetails $shippingDetails transaction shipping address * @property-read string $status transaction status - * @property-read array $statusHistory array of StatusDetails objects + * @property-read \Braintree\Transaction\StatusDetails[] $statusHistory array of StatusDetails objects + * @property-read \Braintree\Transaction\SubscriptionDetails $subscriptionDetails + * @property-read string $subscriptionId + * @property-read string $taxAmount + * @property-read string $taxExcempt + * @property-read \Braintree\ThreeDSecureInfo $threeDSecureInfo * @property-read string $type transaction type - * @property-read string $updatedAt transaction updated timestamp - * @property-read object $disbursementDetails populated when transaction is disbursed + * @property-read \DateTime $updatedAt transaction updated DateTime + * @property-read \Braintree\VenmoAccount $venmoAccountDetails transaction Venmo Account info + * @property-read \Braintree\VisaCheckoutCardDetails $visaCheckoutCardDetails transaction Visa Checkout card info + * @property-read string $voiceReferralName * */ -final class Braintree_Transaction extends Braintree +class Transaction extends Base { // Transaction Status const AUTHORIZATION_EXPIRED = 'authorization_expired'; @@ -182,6 +225,9 @@ final class Braintree_Transaction extends Braintree const SUBMITTED_FOR_SETTLEMENT = 'submitted_for_settlement'; const VOIDED = 'voided'; const UNRECOGNIZED = 'unrecognized'; + const SETTLEMENT_DECLINED = 'settlement_declined'; + const SETTLEMENT_PENDING = 'settlement_pending'; + const SETTLEMENT_CONFIRMED = 'settlement_confirmed'; // Transaction Escrow Status const ESCROW_HOLD_PENDING = 'hold_pending'; @@ -204,335 +250,130 @@ final class Braintree_Transaction extends Braintree const RECURRING = 'recurring'; // Gateway Rejection Reason - const AVS = 'avs'; - const AVS_AND_CVV = 'avs_and_cvv'; - const CVV = 'cvv'; - const DUPLICATE = 'duplicate'; - const FRAUD = 'fraud'; - - public static function cloneTransaction($transactionId, $attribs) - { - Braintree_Util::verifyKeys(self::cloneSignature(), $attribs); - return self::_doCreate('/transactions/' . $transactionId . '/clone', array('transactionClone' => $attribs)); - } - - /** - * @ignore - * @access public - * @param array $attribs - * @return object - */ - private static function create($attribs) - { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/transactions', array('transaction' => $attribs)); - } + const AVS = 'avs'; + const AVS_AND_CVV = 'avs_and_cvv'; + const CVV = 'cvv'; + const DUPLICATE = 'duplicate'; + const FRAUD = 'fraud'; + const THREE_D_SECURE = 'three_d_secure'; + const APPLICATION_INCOMPLETE = 'application_incomplete'; + + // Industry Types + const LODGING_INDUSTRY = 'lodging'; + const TRAVEL_AND_CRUISE_INDUSTRY = 'travel_cruise'; + const TRAVEL_AND_FLIGHT_INDUSTRY = 'travel_flight'; /** + * sets instance properties from an array of values * * @ignore - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError - */ - private static function createNoValidate($attribs) - { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - /** - * - * @access public - * @param array $attribs - * @return object - */ - public static function createFromTransparentRedirect($queryString) - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doCreate( - '/transactions/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); - } - /** - * - * @access public - * @param none - * @return string - */ - public static function createTransactionUrl() - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/transactions/all/create_via_transparent_redirect_request'; - } - - public static function cloneSignature() - { - return array('amount', 'channel', array('options' => array('submitForSettlement'))); - } - - /** - * creates a full array signature of a valid gateway request - * @return array gateway request signature format - */ - public static function createSignature() - { - return array( - 'amount', 'customerId', 'merchantAccountId', 'orderId', 'channel', 'paymentMethodToken', 'deviceSessionId', - 'purchaseOrderNumber', 'recurring', 'shippingAddressId', 'taxAmount', 'taxExempt', 'type', 'venmoSdkPaymentMethodCode', - 'serviceFeeAmount', 'deviceData', 'fraudMerchantId', 'billingAddressId', - array('creditCard' => - array('token', 'cardholderName', 'cvv', 'expirationDate', 'expirationMonth', 'expirationYear', 'number'), - ), - array('customer' => - array( - 'id', 'company', 'email', 'fax', 'firstName', - 'lastName', 'phone', 'website'), - ), - array('billing' => - array( - 'firstName', 'lastName', 'company', 'countryName', - 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', - 'extendedAddress', 'locality', 'postalCode', 'region', - 'streetAddress'), - ), - array('shipping' => - array( - 'firstName', 'lastName', 'company', 'countryName', - 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', - 'extendedAddress', 'locality', 'postalCode', 'region', - 'streetAddress'), - ), - array('options' => - array( - 'holdInEscrow', - 'storeInVault', - 'storeInVaultOnSuccess', - 'submitForSettlement', - 'addBillingAddressToPaymentMethod', - 'venmoSdkSession', - 'storeShippingAddressInVault'), - ), - array('customFields' => array('_anyKey_') - ), - array('descriptor' => array('name', 'phone')) - ); - } - - /** - * - * @access public - * @param array $attribs - * @return object - */ - public static function credit($attribs) - { - return self::create(array_merge($attribs, array('type' => Braintree_Transaction::CREDIT))); - } - - /** - * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError + * @access protected + * @param array $transactionAttribs array of transaction data + * @return void */ - public static function creditNoValidate($attribs) + protected function _initialize($transactionAttribs) { - $result = self::credit($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - + $this->_attributes = $transactionAttribs; - /** - * @access public - * - */ - public static function find($id) - { - self::_validateId($id); - try { - $response = Braintree_Http::get('/transactions/'.$id); - return self::factory($response['transaction']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'transaction with id ' . $id . ' not found' + if (isset($transactionAttribs['applePay'])) { + $this->_set('applePayCardDetails', + new Transaction\ApplePayCardDetails( + $transactionAttribs['applePay'] + ) ); } - } - /** - * new sale - * @param array $attribs - * @return array - */ - public static function sale($attribs) - { - return self::create(array_merge(array('type' => Braintree_Transaction::SALE), $attribs)); - } - - /** - * roughly equivalent to the ruby bang method - * @access public - * @param array $attribs - * @return array - * @throws Braintree_Exception_ValidationsFailed - */ - public static function saleNoValidate($attribs) - { - $result = self::sale($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - /** - * Returns a ResourceCollection of transactions matching the search query. - * - * If query is a string, the search will be a basic search. - * If query is a hash, the search will be an advanced search. - * For more detailed information and examples, see {@link http://www.braintreepayments.com/gateway/transaction-api#searching http://www.braintreepaymentsolutions.com/gateway/transaction-api} - * - * @param mixed $query search query - * @param array $options options such as page number - * @return object Braintree_ResourceCollection - * @throws InvalidArgumentException - */ - public static function search($query) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + if (isset($transactionAttribs['androidPayCard'])) { + $this->_set('androidPayCardDetails', + new Transaction\AndroidPayCardDetails( + $transactionAttribs['androidPayCard'] + ) + ); } - $response = Braintree_Http::post('/transactions/advanced_search_ids', array('search' => $criteria)); - if (array_key_exists('searchResults', $response)) { - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); - - return new Braintree_ResourceCollection($response, $pager); - } else { - throw new Braintree_Exception_DownForMaintenance(); + if (isset($transactionAttribs['masterpassCard'])) { + $this->_set('masterpassCardDetails', + new Transaction\MasterpassCardDetails( + $transactionAttribs['masterpassCard'] + ) + ); } - } - public static function fetch($query, $ids) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + if (isset($transactionAttribs['visaCheckoutCard'])) { + $this->_set('visaCheckoutCardDetails', + new Transaction\VisaCheckoutCardDetails( + $transactionAttribs['visaCheckoutCard'] + ) + ); } - $criteria["ids"] = Braintree_TransactionSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/transactions/advanced_search', array('search' => $criteria)); - - return Braintree_Util::extractattributeasarray( - $response['creditCardTransactions'], - 'transaction' - ); - } - - /** - * void a transaction by id - * - * @param string $id transaction id - * @return object Braintree_Result_Successful|Braintree_Result_Error - */ - public static function void($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put('/transactions/'. $transactionId . '/void'); - return self::_verifyGatewayResponse($response); - } - /** - * - */ - public static function voidNoValidate($transactionId) - { - $result = self::void($transactionId); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - public static function submitForSettlement($transactionId, $amount = null) - { - self::_validateId($transactionId); - $response = Braintree_Http::put( - '/transactions/'. $transactionId . '/submit_for_settlement', - array( 'transaction' => array( 'amount' => $amount)) - ); - return self::_verifyGatewayResponse($response); - } - - public static function submitForSettlementNoValidate($transactionId, $amount = null) - { - $result = self::submitForSettlement($transactionId, $amount); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - public static function holdInEscrow($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/hold_in_escrow', - array() - ); - return self::_verifyGatewayResponse($response); - } + if (isset($transactionAttribs['samsungPayCard'])) { + $this->_set('samsungPayCardDetails', + new Transaction\SamsungPayCardDetails( + $transactionAttribs['samsungPayCard'] + ) + ); + } - public static function releaseFromEscrow($transactionId) - { - self::_validateId($transactionId); + if (isset($transactionAttribs['amexExpressCheckoutCard'])) { + $this->_set('amexExpressCheckoutCardDetails', + new Transaction\AmexExpressCheckoutCardDetails( + $transactionAttribs['amexExpressCheckoutCard'] + ) + ); + } - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/release_from_escrow', - array() - ); - return self::_verifyGatewayResponse($response); - } + if (isset($transactionAttribs['venmoAccount'])) { + $this->_set('venmoAccountDetails', + new Transaction\VenmoAccountDetails( + $transactionAttribs['venmoAccount'] + ) + ); + } - public static function cancelRelease($transactionId) - { - self::_validateId($transactionId); + if (isset($transactionAttribs['creditCard'])) { + $this->_set('creditCardDetails', + new Transaction\CreditCardDetails( + $transactionAttribs['creditCard'] + ) + ); + } - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/cancel_release', - array() - ); - return self::_verifyGatewayResponse($response); - } + if (isset($transactionAttribs['coinbaseAccount'])) { + $this->_set('coinbaseDetails', + new Transaction\CoinbaseDetails( + $transactionAttribs['coinbaseAccount'] + ) + ); + } + if (isset($transactionAttribs['usBankAccount'])) { + $this->_set('usBankAccount', + new Transaction\UsBankAccountDetails( + $transactionAttribs['usBankAccount'] + ) + ); + } - /** - * sets instance properties from an array of values - * - * @ignore - * @access protected - * @param array $transactionAttribs array of transaction data - * @return none - */ - protected function _initialize($transactionAttribs) - { - $this->_attributes = $transactionAttribs; + if (isset($transactionAttribs['idealPayment'])) { + $this->_set('idealPayment', + new Transaction\IdealPaymentDetails( + $transactionAttribs['idealPayment'] + ) + ); + } - if (isset($transactionAttribs['creditCard'])) { - $this->_set('creditCardDetails', - new Braintree_Transaction_CreditCardDetails( - $transactionAttribs['creditCard'] + if (isset($transactionAttribs['paypal'])) { + $this->_set('paypalDetails', + new Transaction\PayPalDetails( + $transactionAttribs['paypal'] ) ); } if (isset($transactionAttribs['customer'])) { $this->_set('customerDetails', - new Braintree_Transaction_CustomerDetails( + new Transaction\CustomerDetails( $transactionAttribs['customer'] ) ); @@ -540,7 +381,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['billing'])) { $this->_set('billingDetails', - new Braintree_Transaction_AddressDetails( + new Transaction\AddressDetails( $transactionAttribs['billing'] ) ); @@ -548,7 +389,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['shipping'])) { $this->_set('shippingDetails', - new Braintree_Transaction_AddressDetails( + new Transaction\AddressDetails( $transactionAttribs['shipping'] ) ); @@ -556,7 +397,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['subscription'])) { $this->_set('subscriptionDetails', - new Braintree_Transaction_SubscriptionDetails( + new Transaction\SubscriptionDetails( $transactionAttribs['subscription'] ) ); @@ -564,7 +405,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['descriptor'])) { $this->_set('descriptor', - new Braintree_Descriptor( + new Descriptor( $transactionAttribs['descriptor'] ) ); @@ -572,34 +413,65 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['disbursementDetails'])) { $this->_set('disbursementDetails', - new Braintree_DisbursementDetails($transactionAttribs['disbursementDetails']) + new DisbursementDetails($transactionAttribs['disbursementDetails']) ); } - $statusHistory = array(); + $disputes = []; + if (isset($transactionAttribs['disputes'])) { + foreach ($transactionAttribs['disputes'] AS $dispute) { + $disputes[] = Dispute::factory($dispute); + } + } + + $this->_set('disputes', $disputes); + + $statusHistory = []; if (isset($transactionAttribs['statusHistory'])) { foreach ($transactionAttribs['statusHistory'] AS $history) { - $statusHistory[] = new Braintree_Transaction_StatusDetails($history); + $statusHistory[] = new Transaction\StatusDetails($history); } } $this->_set('statusHistory', $statusHistory); - $addOnArray = array(); + $addOnArray = []; if (isset($transactionAttribs['addOns'])) { foreach ($transactionAttribs['addOns'] AS $addOn) { - $addOnArray[] = Braintree_AddOn::factory($addOn); + $addOnArray[] = AddOn::factory($addOn); } } $this->_set('addOns', $addOnArray); - $discountArray = array(); + $discountArray = []; if (isset($transactionAttribs['discounts'])) { foreach ($transactionAttribs['discounts'] AS $discount) { - $discountArray[] = Braintree_Discount::factory($discount); + $discountArray[] = Discount::factory($discount); } } $this->_set('discounts', $discountArray); + + $authorizationAdjustments = []; + if (isset($transactionAttribs['authorizationAdjustments'])) { + foreach ($transactionAttribs['authorizationAdjustments'] AS $authorizationAdjustment) { + $authorizationAdjustments[] = AuthorizationAdjustment::factory($authorizationAdjustment); + } + } + + $this->_set('authorizationAdjustments', $authorizationAdjustments); + + if(isset($transactionAttribs['riskData'])) { + $this->_set('riskData', RiskData::factory($transactionAttribs['riskData'])); + } + if(isset($transactionAttribs['threeDSecureInfo'])) { + $this->_set('threeDSecureInfo', ThreeDSecureInfo::factory($transactionAttribs['threeDSecureInfo'])); + } + if(isset($transactionAttribs['facilitatedDetails'])) { + $this->_set('facilitatedDetails', FacilitatedDetails::factory($transactionAttribs['facilitatedDetails'])); + } + if(isset($transactionAttribs['facilitatorDetails'])) { + $this->_set('facilitatorDetails', FacilitatorDetails::factory($transactionAttribs['facilitatorDetails'])); + } } /** @@ -609,24 +481,17 @@ protected function _initialize($transactionAttribs) public function __toString() { // array of attributes to print - $display = array( + $display = [ 'id', 'type', 'amount', 'status', 'createdAt', 'creditCardDetails', 'customerDetails' - ); + ]; - $displayAttributes = array(); + $displayAttributes = []; foreach ($display AS $attrib) { $displayAttributes[$attrib] = $this->$attrib; } return __CLASS__ . '[' . - Braintree_Util::attributesToString($displayAttributes) .']'; - } - - public static function refund($transactionId, $amount = null) - { - $params = array('transaction' => array('amount' => $amount)); - $response = Braintree_Http::post('/transactions/' . $transactionId . '/refund', $params); - return self::_verifyGatewayResponse($response); + Util::attributesToString($displayAttributes) .']'; } public function isEqual($otherTx) @@ -641,10 +506,11 @@ public function vaultCreditCard() return null; } else { - return Braintree_CreditCard::find($token); + return CreditCard::find($token); } } + /** @return void|Braintree\Customer */ public function vaultCustomer() { $customerId = $this->customerDetails->id; @@ -652,91 +518,135 @@ public function vaultCustomer() return null; } else { - return Braintree_Customer::find($customerId); + return Customer::find($customerId); } } + /** @return boolean */ public function isDisbursed() { return $this->disbursementDetails->isValid(); } + /** @return line items */ + public function lineItems() { + return Configuration::gateway()->transactionLineItem()->findAll($this->id); + } + /** - * sends the create request to the gateway + * factory method: returns an instance of Transaction + * to the requesting method, with populated properties * * @ignore - * @param var $url - * @param array $params - * @return mixed + * @return Transaction */ - public static function _doCreate($url, $params) + public static function factory($attributes) { - $response = Braintree_Http::post($url, $params); + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + - return self::_verifyGatewayResponse($response); + // static methods redirecting to gateway + + public static function cloneTransaction($transactionId, $attribs) + { + return Configuration::gateway()->transaction()->cloneTransaction($transactionId, $attribs); } - /** - * verifies that a valid transaction id is being used - * @ignore - * @param string transaction id - * @throws InvalidArgumentException - */ - private static function _validateId($id = null) { - if (empty($id)) { - throw new InvalidArgumentException( - 'expected transaction id to be set' - ); - } - if (!preg_match('/^[0-9a-z]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid transaction id.' - ); - } + public static function createFromTransparentRedirect($queryString) + { + return Configuration::gateway()->transaction()->createFromTransparentRedirect($queryString); } + public static function createTransactionUrl() + { + return Configuration::gateway()->transaction()->createTransactionUrl(); + } - /* private class methods */ + public static function credit($attribs) + { + return Configuration::gateway()->transaction()->credit($attribs); + } - /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_Transaction object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. - * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected - */ - private static function _verifyGatewayResponse($response) + public static function creditNoValidate($attribs) { - if (isset($response['transaction'])) { - // return a populated instance of Braintree_Transaction - return new Braintree_Result_Successful( - self::factory($response['transaction']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected transaction or apiErrorResponse" - ); - } + return Configuration::gateway()->transaction()->creditNoValidate($attribs); } - /** - * factory method: returns an instance of Braintree_Transaction - * to the requesting method, with populated properties - * - * @ignore - * @return object instance of Braintree_Transaction - */ - public static function factory($attributes) + public static function find($id) { - $instance = new self(); - $instance->_initialize($attributes); - return $instance; + return Configuration::gateway()->transaction()->find($id); + } + + public static function sale($attribs) + { + return Configuration::gateway()->transaction()->sale($attribs); + } + + public static function saleNoValidate($attribs) + { + return Configuration::gateway()->transaction()->saleNoValidate($attribs); + } + + public static function search($query) + { + return Configuration::gateway()->transaction()->search($query); + } + + public static function fetch($query, $ids) + { + return Configuration::gateway()->transaction()->fetch($query, $ids); + } + + public static function void($transactionId) + { + return Configuration::gateway()->transaction()->void($transactionId); + } + + public static function voidNoValidate($transactionId) + { + return Configuration::gateway()->transaction()->voidNoValidate($transactionId); + } + + public static function submitForSettlement($transactionId, $amount = null, $attribs = []) + { + return Configuration::gateway()->transaction()->submitForSettlement($transactionId, $amount, $attribs); + } + + public static function submitForSettlementNoValidate($transactionId, $amount = null, $attribs = []) + { + return Configuration::gateway()->transaction()->submitForSettlementNoValidate($transactionId, $amount, $attribs); + } + + public static function updateDetails($transactionId, $attribs = []) + { + return Configuration::gateway()->transaction()->updateDetails($transactionId, $attribs); + } + + public static function submitForPartialSettlement($transactionId, $amount, $attribs = []) + { + return Configuration::gateway()->transaction()->submitForPartialSettlement($transactionId, $amount, $attribs); + } + + public static function holdInEscrow($transactionId) + { + return Configuration::gateway()->transaction()->holdInEscrow($transactionId); + } + + public static function releaseFromEscrow($transactionId) + { + return Configuration::gateway()->transaction()->releaseFromEscrow($transactionId); + } + + public static function cancelRelease($transactionId) + { + return Configuration::gateway()->transaction()->cancelRelease($transactionId); + } + + public static function refund($transactionId, $amount = null) + { + return Configuration::gateway()->transaction()->refund($transactionId, $amount); } } +class_alias('Braintree\Transaction', 'Braintree_Transaction'); diff --git a/lib/Braintree/Transaction/AddressDetails.php b/lib/Braintree/Transaction/AddressDetails.php index dc89573..597a208 100644 --- a/lib/Braintree/Transaction/AddressDetails.php +++ b/lib/Braintree/Transaction/AddressDetails.php @@ -1,11 +1,7 @@ _attributes['cardType'] = $this->virtualCardType; + $this->_attributes['last4'] = $this->virtualCardLast4; + } +} +class_alias('Braintree\Transaction\AndroidPayCardDetails', 'Braintree_Transaction_AndroidPayCardDetails'); diff --git a/lib/Braintree/Transaction/ApplePayCardDetails.php b/lib/Braintree/Transaction/ApplePayCardDetails.php new file mode 100644 index 0000000..f0a2be4 --- /dev/null +++ b/lib/Braintree/Transaction/ApplePayCardDetails.php @@ -0,0 +1,39 @@ +_attributes['expirationDate'] = $this->expirationMonth . '/' . $this->expirationYear; + $this->_attributes['maskedNumber'] = $this->bin . '******' . $this->last4; + + } +} +class_alias('Braintree\Transaction\MasterpassCardDetails', 'Braintree_Transaction_MasterpassCardDetails'); diff --git a/lib/Braintree/Transaction/PayPalDetails.php b/lib/Braintree/Transaction/PayPalDetails.php new file mode 100644 index 0000000..4c13a42 --- /dev/null +++ b/lib/Braintree/Transaction/PayPalDetails.php @@ -0,0 +1,51 @@ +_attributes['expirationDate'] = $this->expirationMonth . '/' . $this->expirationYear; + $this->_attributes['maskedNumber'] = $this->bin . '******' . $this->last4; + + } +} +class_alias('Braintree\Transaction\SamsungPayCardDetails', 'Braintree_Transaction_SamsungPayCardDetails'); diff --git a/lib/Braintree/Transaction/StatusDetails.php b/lib/Braintree/Transaction/StatusDetails.php index c105ced..b8e1f8f 100644 --- a/lib/Braintree/Transaction/StatusDetails.php +++ b/lib/Braintree/Transaction/StatusDetails.php @@ -1,25 +1,21 @@ achMandate = $achMandate; + } +} +class_alias('Braintree\Transaction\UsBankAccountDetails', 'Braintree_Transaction_UsBankAccountDetails'); diff --git a/lib/Braintree/Transaction/VenmoAccountDetails.php b/lib/Braintree/Transaction/VenmoAccountDetails.php new file mode 100644 index 0000000..b48baed --- /dev/null +++ b/lib/Braintree/Transaction/VenmoAccountDetails.php @@ -0,0 +1,38 @@ +_attributes['expirationDate'] = $this->expirationMonth . '/' . $this->expirationYear; + $this->_attributes['maskedNumber'] = $this->bin . '******' . $this->last4; + + } +} +class_alias('Braintree\Transaction\VisaCheckoutCardDetails', 'Braintree_Transaction_VisaCheckoutCardDetails'); diff --git a/lib/Braintree/TransactionGateway.php b/lib/Braintree/TransactionGateway.php new file mode 100644 index 0000000..4f5e32f --- /dev/null +++ b/lib/Braintree/TransactionGateway.php @@ -0,0 +1,585 @@ +== More information == + * + * For more detailed information on Transactions, see {@link https://developers.braintreepayments.com/reference/response/transaction/php https://developers.braintreepayments.com/reference/response/transaction/php} + * + * @package Braintree + * @category Resources + */ + +class TransactionGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function cloneTransaction($transactionId, $attribs) + { + Util::verifyKeys(self::cloneSignature(), $attribs); + return $this->_doCreate('/transactions/' . $transactionId . '/clone', ['transactionClone' => $attribs]); + } + + /** + * @ignore + * @access private + * @param array $attribs + * @return Result\Successful|Result\Error + */ + private function create($attribs) + { + Util::verifyKeys(self::createSignature(), $attribs); + return $this->_doCreate('/transactions', ['transaction' => $attribs]); + } + + /** + * @ignore + * @access private + * @param array $attribs + * @return object + * @throws Exception\ValidationError + */ + private function createNoValidate($attribs) + { + $result = $this->create($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + /** + * + * @deprecated since version 2.3.0 + * @access public + * @param array $attribs + * @return object + */ + public function createFromTransparentRedirect($queryString) + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE); + $params = TransparentRedirect::parseAndValidateQueryString( + $queryString + ); + return $this->_doCreate( + '/transactions/all/confirm_transparent_redirect_request', + ['id' => $params['id']] + ); + } + /** + * + * @deprecated since version 2.3.0 + * @access public + * @param none + * @return string + */ + public function createTransactionUrl() + { + trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE); + return $this->_config->baseUrl() . $this->_config->merchantPath() . + '/transactions/all/create_via_transparent_redirect_request'; + } + + public static function cloneSignature() + { + return ['amount', 'channel', ['options' => ['submitForSettlement']]]; + } + + /** + * creates a full array signature of a valid gateway request + * @return array gateway request signature format + */ + public static function createSignature() + { + return [ + 'amount', + 'billingAddressId', + 'channel', + 'customerId', + 'deviceData', + 'deviceSessionId', + 'fraudMerchantId', + 'merchantAccountId', + 'orderId', + 'paymentMethodNonce', + 'paymentMethodToken', + 'purchaseOrderNumber', + 'recurring', + 'serviceFeeAmount', + 'sharedPaymentMethodToken', + 'sharedPaymentMethodNonce', + 'sharedCustomerId', + 'sharedShippingAddressId', + 'sharedBillingAddressId', + 'shippingAddressId', + 'taxAmount', + 'taxExempt', + 'threeDSecureToken', + 'transactionSource', + 'type', + 'venmoSdkPaymentMethodCode', + 'shippingAmount', + 'discountAmount', + 'shipsFromPostalCode', + ['riskData' => + ['customerBrowser', 'customerIp', 'customer_browser', 'customer_ip'] + ], + ['creditCard' => + ['token', 'cardholderName', 'cvv', 'expirationDate', 'expirationMonth', 'expirationYear', 'number'], + ], + ['customer' => + [ + 'id', 'company', 'email', 'fax', 'firstName', + 'lastName', 'phone', 'website'], + ], + ['billing' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'postalCode', 'region', + 'streetAddress'], + ], + ['shipping' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'postalCode', 'region', + 'streetAddress'], + ], + ['threeDSecurePassThru' => + [ + 'eciFlag', + 'cavv', + 'xid'], + ], + ['options' => + [ + 'holdInEscrow', + 'storeInVault', + 'storeInVaultOnSuccess', + 'submitForSettlement', + 'addBillingAddressToPaymentMethod', + 'venmoSdkSession', + 'storeShippingAddressInVault', + 'payeeId', + 'payeeEmail', + 'skipAdvancedFraudChecking', + 'skipAvs', + 'skipCvv', + ['threeDSecure' => + ['required'] + ], + # TODO: Snake case version included for backwards compatiblity. Remove in the next major version + ['three_d_secure' => + ['required'] + ], + ['paypal' => + [ + 'payeeId', + 'payeeEmail', + 'customField', + 'description', + ['supplementaryData' => ['_anyKey_']], + ] + ], + ['amexRewards' => + [ + 'requestId', + 'points', + 'currencyAmount', + 'currencyIsoCode' + ] + ], + ['venmo' => + [ + # TODO: Snake case version included for backwards compatiblity. Remove in the next major version + 'profile_id', + 'profileId' + ] + ] + ], + ], + ['customFields' => ['_anyKey_']], + ['descriptor' => ['name', 'phone', 'url']], + ['paypalAccount' => ['payeeId', 'payeeEmail', 'payerId', 'paymentId']], + # TODO: Snake case version included for backwards compatiblity. Remove in the next major version + ['apple_pay_card' => ['number', 'cardholder_name', 'cryptogram', 'expiration_month', 'expiration_year', 'eci_indicator']], + + ['applePayCard' => ['number', 'cardholderName', 'cryptogram', 'expirationMonth', 'expirationYear', 'eciIndicator']], + ['industry' => + ['industryType', + ['data' => + [ + 'folioNumber', + 'checkInDate', + 'checkOutDate', + 'travelPackage', + 'departureDate', + 'lodgingCheckInDate', + 'lodgingCheckOutDate', + 'lodgingName', + 'roomRate', + 'passengerFirstName', + 'passengerLastName', + 'passengerMiddleInitial', + 'passengerTitle', + 'issuedDate', + 'travelAgencyName', + 'travelAgencyCode', + 'ticketNumber', + 'issuingCarrierCode', + 'customerCode', + 'fareAmount', + 'feeAmount', + 'taxAmount', + 'restrictedTicket', + ['legs' => + [ + 'conjunctionTicket', + 'exchangeTicket', + 'couponNumber', + 'serviceClass', + 'carrierCode', + 'fareBasisCode', + 'flightNumber', + 'departureDate', + 'departureAirportCode', + 'departureTime', + 'arrivalAirportCode', + 'arrivalTime', + 'stopoverPermitted', + 'fareAmount', + 'feeAmount', + 'taxAmount', + 'endorsementOrRestrictions' + ] + ] + ] + ] + ] + ], + ['lineItems' => ['quantity', 'name', 'description', 'kind', 'unitAmount', 'unitTaxAmount', 'totalAmount', 'discountAmount', 'taxAmount', 'unitOfMeasure', 'productCode', 'commodityCode', 'url']], + ['externalVault' => + ['status' , 'previousNetworkTransactionId'], + ] + ]; + } + + public static function submitForSettlementSignature() + { + return ['orderId', ['descriptor' => ['name', 'phone', 'url']]]; + } + + public static function updateDetailsSignature() + { + return ['amount', 'orderId', ['descriptor' => ['name', 'phone', 'url']]]; + } + + public static function refundSignature() + { + return ['amount', 'orderId']; + } + + /** + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function credit($attribs) + { + return $this->create(array_merge($attribs, ['type' => Transaction::CREDIT])); + } + + /** + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + * @throws Exception\ValidationError + */ + public function creditNoValidate($attribs) + { + $result = $this->credit($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + /** + * @access public + * @param string id + * @return Transaction + */ + public function find($id) + { + $this->_validateId($id); + try { + $path = $this->_config->merchantPath() . '/transactions/' . $id; + $response = $this->_http->get($path); + return Transaction::factory($response['transaction']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'transaction with id ' . $id . ' not found' + ); + } + } + /** + * new sale + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function sale($attribs) + { + return $this->create(array_merge(['type' => Transaction::SALE], $attribs)); + } + + /** + * roughly equivalent to the ruby bang method + * @access public + * @param array $attribs + * @return array + * @throws Exception\ValidationsFailed + */ + public function saleNoValidate($attribs) + { + $result = $this->sale($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + /** + * Returns a ResourceCollection of transactions matching the search query. + * + * If query is a string, the search will be a basic search. + * If query is a hash, the search will be an advanced search. + * For more detailed information and examples, see {@link https://developers.braintreepayments.com/reference/request/transaction/search/php https://developers.braintreepayments.com/reference/request/transaction/search/php} + * + * @param mixed $query search query + * @param array $options options such as page number + * @return ResourceCollection + * @throws InvalidArgumentException + */ + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + $path = $this->_config->merchantPath() . '/transactions/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + if (array_key_exists('searchResults', $response)) { + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } else { + throw new Exception\DownForMaintenance(); + } + } + + public function fetch($query, $ids) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $criteria["ids"] = TransactionSearch::ids()->in($ids)->toparam(); + $path = $this->_config->merchantPath() . '/transactions/advanced_search'; + $response = $this->_http->post($path, ['search' => $criteria]); + + if (array_key_exists('creditCardTransactions', $response)) { + return Util::extractattributeasarray( + $response['creditCardTransactions'], + 'transaction' + ); + } else { + throw new Exception\DownForMaintenance(); + } + } + + /** + * void a transaction by id + * + * @param string $id transaction id + * @return Result\Successful|Result\Error + */ + public function void($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/void'; + $response = $this->_http->put($path); + return $this->_verifyGatewayResponse($response); + } + /** + * + */ + public function voidNoValidate($transactionId) + { + $result = $this->void($transactionId); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + public function submitForSettlement($transactionId, $amount = null, $attribs = []) + { + $this->_validateId($transactionId); + Util::verifyKeys(self::submitForSettlementSignature(), $attribs); + $attribs['amount'] = $amount; + + $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/submit_for_settlement'; + $response = $this->_http->put($path, ['transaction' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function submitForSettlementNoValidate($transactionId, $amount = null, $attribs = []) + { + $result = $this->submitForSettlement($transactionId, $amount, $attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + public function updateDetails($transactionId, $attribs = []) + { + $this->_validateId($transactionId); + Util::verifyKeys(self::updateDetailsSignature(), $attribs); + + $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/update_details'; + $response = $this->_http->put($path, ['transaction' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function submitForPartialSettlement($transactionId, $amount, $attribs = []) + { + $this->_validateId($transactionId); + Util::verifyKeys(self::submitForSettlementSignature(), $attribs); + $attribs['amount'] = $amount; + + $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/submit_for_partial_settlement'; + $response = $this->_http->post($path, ['transaction' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function holdInEscrow($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/hold_in_escrow'; + $response = $this->_http->put($path, []); + return $this->_verifyGatewayResponse($response); + } + + public function releaseFromEscrow($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/release_from_escrow'; + $response = $this->_http->put($path, []); + return $this->_verifyGatewayResponse($response); + } + + public function cancelRelease($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/cancel_release'; + $response = $this->_http->put($path, []); + return $this->_verifyGatewayResponse($response); + } + + public function refund($transactionId, $amount_or_options = null) + { + self::_validateId($transactionId); + + if(gettype($amount_or_options) == "array") { + $options = $amount_or_options; + } else { + $options = [ + "amount" => $amount_or_options + ]; + } + Util::verifyKeys(self::refundSignature(), $options); + + $params = ['transaction' => $options]; + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/refund'; + $response = $this->_http->post($path, $params); + return $this->_verifyGatewayResponse($response); + } + + /** + * sends the create request to the gateway + * + * @ignore + * @param var $subPath + * @param array $params + * @return Result\Successful|Result\Error + */ + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * verifies that a valid transaction id is being used + * @ignore + * @param string transaction id + * @throws InvalidArgumentException + */ + private function _validateId($id = null) { + if (empty($id)) { + throw new InvalidArgumentException( + 'expected transaction id to be set' + ); + } + if (!preg_match('/^[0-9a-z]+$/', $id)) { + throw new InvalidArgumentException( + $id . ' is an invalid transaction id.' + ); + } + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new Transaction object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['transaction'])) { + // return a populated instance of Transaction + return new Result\Successful( + Transaction::factory($response['transaction']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected transaction or apiErrorResponse" + ); + } + } +} +class_alias('Braintree\TransactionGateway', 'Braintree_TransactionGateway'); diff --git a/lib/Braintree/TransactionLineItem.php b/lib/Braintree/TransactionLineItem.php new file mode 100644 index 0000000..69aa69f --- /dev/null +++ b/lib/Braintree/TransactionLineItem.php @@ -0,0 +1,55 @@ +transactionLineItem()->findAll($transactionId); + } +} +class_alias('Braintree\TransactionLineItem', 'Braintree_TransactionLineItem'); +class_alias('Braintree\TransactionLineItem', 'Braintree\Transaction\LineItem'); +class_alias('Braintree\TransactionLineItem', 'Braintree_Transaction_LineItem'); diff --git a/lib/Braintree/TransactionLineItemGateway.php b/lib/Braintree/TransactionLineItemGateway.php new file mode 100644 index 0000000..7b1db2f --- /dev/null +++ b/lib/Braintree/TransactionLineItemGateway.php @@ -0,0 +1,67 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * @access public + * @param string id + * @return Transaction + */ + public function findAll($id) + { + $this->_validateId($id); + try { + $path = $this->_config->merchantPath() . '/transactions/' . $id . '/line_items'; + $response = $this->_http->get($path); + + $lineItems = []; + if (isset($response['lineItems'])) { + foreach ($response['lineItems'] AS $lineItem) { + $lineItems[] = new TransactionLineItem($lineItem); + } + } + return $lineItems; + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('transaction line items with id ' . $id . ' not found'); + } + } + + /** + * verifies that a valid transaction id is being used + * @ignore + * @param string transaction id + * @throws InvalidArgumentException + */ + private function _validateId($id = null) { + if (empty($id)) { + throw new InvalidArgumentException('expected transaction id to be set'); + } + if (!preg_match('/^[0-9a-z]+$/', $id)) { + throw new InvalidArgumentException($id . ' is an invalid transaction id.'); + } + } +} +class_alias('Braintree\TransactionLineItemGateway', 'Braintree_TransactionLineItemGateway'); diff --git a/lib/Braintree/TransactionSearch.php b/lib/Braintree/TransactionSearch.php index 487d781..5271b0b 100644 --- a/lib/Braintree/TransactionSearch.php +++ b/lib/Braintree/TransactionSearch.php @@ -1,125 +1,131 @@ - * $trData = Braintree_TransparentRedirect::createCustomerData(array( + * $trData = TransparentRedirect::createCustomerData(array( * 'redirectUrl => 'http://example.com/redirect_back_to_merchant_site', * )); * @@ -32,7 +25,7 @@ * amount, include the amount in the trData. * * - * $trData = Braintree_TransparentRedirect::transactionData(array( + * $trData = TransparentRedirect::transactionData(array( * 'redirectUrl' => 'http://example.com/complete_transaction', * 'transaction' => array('amount' => '100.00'), * )); @@ -41,9 +34,8 @@ * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions */ -class Braintree_TransparentRedirect +class TransparentRedirect { // Request Kinds const CREATE_TRANSACTION = 'create_transaction'; @@ -52,276 +44,57 @@ class Braintree_TransparentRedirect const CREATE_CUSTOMER = 'create_customer'; const UPDATE_CUSTOMER = 'update_customer'; - /** - * - * @ignore - */ - private static $_transparentRedirectKeys = 'redirectUrl'; - private static $_createCustomerSignature; - private static $_updateCustomerSignature; - private static $_transactionSignature; - private static $_createCreditCardSignature; - private static $_updateCreditCardSignature; - - /** * @ignore * don't permit an explicit call of the constructor! - * (like $t = new Braintree_TransparentRedirect()) + * (like $t = new TransparentRedirect()) */ protected function __construct() { } - /** - * create signatures for different call types - * @ignore - */ - public static function init() - { - self::$_createCustomerSignature = array( - self::$_transparentRedirectKeys, - array('customer' => Braintree_Customer::createSignature()), - ); - self::$_updateCustomerSignature = array( - self::$_transparentRedirectKeys, - 'customerId', - array('customer' => Braintree_Customer::updateSignature()), - ); - self::$_transactionSignature = array( - self::$_transparentRedirectKeys, - array('transaction' => Braintree_Transaction::createSignature()), - ); - self::$_createCreditCardSignature = array( - self::$_transparentRedirectKeys, - array('creditCard' => Braintree_CreditCard::createSignature()), - ); - self::$_updateCreditCardSignature = array( - self::$_transparentRedirectKeys, - 'paymentMethodToken', - array('creditCard' => Braintree_CreditCard::updateSignature()), - ); - } + // static methods redirecting to gateway public static function confirm($queryString) { - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - $confirmationKlasses = array( - Braintree_TransparentRedirect::CREATE_TRANSACTION => 'Braintree_Transaction', - Braintree_TransparentRedirect::CREATE_CUSTOMER => 'Braintree_Customer', - Braintree_TransparentRedirect::UPDATE_CUSTOMER => 'Braintree_Customer', - Braintree_TransparentRedirect::CREATE_PAYMENT_METHOD => 'Braintree_CreditCard', - Braintree_TransparentRedirect::UPDATE_PAYMENT_METHOD => 'Braintree_CreditCard' - ); - return call_user_func(array($confirmationKlasses[$params["kind"]], '_doCreate'), - '/transparent_redirect_requests/' . $params['id'] . '/confirm', - array() - ); + return Configuration::gateway()->transparentRedirect()->confirm($queryString); } - /** - * returns the trData string for creating a credit card, - * @param array $params - * @return string - */ public static function createCreditCardData($params) { - Braintree_Util::verifyKeys( - self::$_createCreditCardSignature, - $params - ); - $params["kind"] = Braintree_TransparentRedirect::CREATE_PAYMENT_METHOD; - return self::_data($params); + return Configuration::gateway()->transparentRedirect()->createCreditCardData($params); } - /** - * returns the trData string for creating a customer. - * @param array $params - * @return string - */ public static function createCustomerData($params) { - Braintree_Util::verifyKeys( - self::$_createCustomerSignature, - $params - ); - $params["kind"] = Braintree_TransparentRedirect::CREATE_CUSTOMER; - return self::_data($params); - + return Configuration::gateway()->transparentRedirect()->createCustomerData($params); } public static function url() { - return Braintree_Configuration::merchantUrl() . "/transparent_redirect_requests"; + return Configuration::gateway()->transparentRedirect()->url(); } - /** - * returns the trData string for creating a transaction - * @param array $params - * @return string - */ public static function transactionData($params) { - Braintree_Util::verifyKeys( - self::$_transactionSignature, - $params - ); - $params["kind"] = Braintree_TransparentRedirect::CREATE_TRANSACTION; - $transactionType = isset($params['transaction']['type']) ? - $params['transaction']['type'] : - null; - if ($transactionType != Braintree_Transaction::SALE && $transactionType != Braintree_Transaction::CREDIT) { - throw new InvalidArgumentException( - 'expected transaction[type] of sale or credit, was: ' . - $transactionType - ); - } - - return self::_data($params); + return Configuration::gateway()->transparentRedirect()->transactionData($params); } - /** - * Returns the trData string for updating a credit card. - * - * The paymentMethodToken of the credit card to update is required. - * - * - * $trData = Braintree_TransparentRedirect::updateCreditCardData(array( - * 'redirectUrl' => 'http://example.com/redirect_here', - * 'paymentMethodToken' => 'token123', - * )); - * - * - * @param array $params - * @return string - */ public static function updateCreditCardData($params) { - Braintree_Util::verifyKeys( - self::$_updateCreditCardSignature, - $params - ); - if (!isset($params['paymentMethodToken'])) { - throw new InvalidArgumentException( - 'expected params to contain paymentMethodToken.' - ); - } - $params["kind"] = Braintree_TransparentRedirect::UPDATE_PAYMENT_METHOD; - return self::_data($params); + return Configuration::gateway()->transparentRedirect()->updateCreditCardData($params); } - /** - * Returns the trData string for updating a customer. - * - * The customerId of the customer to update is required. - * - * - * $trData = Braintree_TransparentRedirect::updateCustomerData(array( - * 'redirectUrl' => 'http://example.com/redirect_here', - * 'customerId' => 'customer123', - * )); - * - * - * @param array $params - * @return string - */ public static function updateCustomerData($params) { - Braintree_Util::verifyKeys( - self::$_updateCustomerSignature, - $params - ); - if (!isset($params['customerId'])) { - throw new InvalidArgumentException( - 'expected params to contain customerId of customer to update' - ); - } - $params["kind"] = Braintree_TransparentRedirect::UPDATE_CUSTOMER; - return self::_data($params); + return Configuration::gateway()->transparentRedirect()->updateCustomerData($params); } public static function parseAndValidateQueryString($queryString) { - // parse the params into an array - parse_str($queryString, $params); - // remove the hash - $queryStringWithoutHash = null; - if(preg_match('/^(.*)&hash=[a-f0-9]+$/', $queryString, $match)) { - $queryStringWithoutHash = $match[1]; - } - - if($params['http_status'] != '200') { - $message = null; - if(array_key_exists('bt_message', $params)) { - $message = $params['bt_message']; - } - Braintree_Util::throwStatusCodeException($params['http_status'], $message); - } - - // recreate the hash and compare it - if(self::_hash($queryStringWithoutHash) == $params['hash']) { - return $params; - } else { - throw new Braintree_Exception_ForgedQueryString(); - } - } - - - /** - * - * @ignore - */ - private static function _data($params) - { - if (!isset($params['redirectUrl'])) { - throw new InvalidArgumentException( - 'expected params to contain redirectUrl' - ); - } - $params = self::_underscoreKeys($params); - $now = new DateTime('now', new DateTimeZone('UTC')); - $trDataParams = array_merge($params, - array( - 'api_version' => Braintree_Configuration::API_VERSION, - 'public_key' => Braintree_Configuration::publicKey(), - 'time' => $now->format('YmdHis'), - ) - ); - ksort($trDataParams); - $trDataSegment = http_build_query($trDataParams, null, '&'); - $trDataHash = self::_hash($trDataSegment); - return "$trDataHash|$trDataSegment"; - } - - private static function _underscoreKeys($array) - { - foreach($array as $key=>$value) - { - $newKey = Braintree_Util::camelCaseToDelimiter($key, '_'); - unset($array[$key]); - if (is_array($value)) - { - $array[$newKey] = self::_underscoreKeys($value); - } - else - { - $array[$newKey] = $value; - } - } - return $array; - } - - /** - * @ignore - */ - private static function _hash($string) - { - return Braintree_Digest::hexDigest($string); + return Configuration::gateway()->transparentRedirect()->parseAndValidateQueryString($queryString); } - } -Braintree_TransparentRedirect::init(); +class_alias('Braintree\TransparentRedirect', 'Braintree_TransparentRedirect'); diff --git a/lib/Braintree/TransparentRedirectGateway.php b/lib/Braintree/TransparentRedirectGateway.php new file mode 100644 index 0000000..3308af0 --- /dev/null +++ b/lib/Braintree/TransparentRedirectGateway.php @@ -0,0 +1,289 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + } + + /** + * + * @ignore + */ + private static $_transparentRedirectKeys = 'redirectUrl'; + private static $_createCustomerSignature; + private static $_updateCustomerSignature; + private static $_transactionSignature; + private static $_createCreditCardSignature; + private static $_updateCreditCardSignature; + + /** + * create signatures for different call types + * @ignore + */ + public static function init() + { + + self::$_createCustomerSignature = [ + self::$_transparentRedirectKeys, + ['customer' => CustomerGateway::createSignature()], + ]; + self::$_updateCustomerSignature = [ + self::$_transparentRedirectKeys, + 'customerId', + ['customer' => CustomerGateway::updateSignature()], + ]; + self::$_transactionSignature = [ + self::$_transparentRedirectKeys, + ['transaction' => TransactionGateway::createSignature()], + ]; + self::$_createCreditCardSignature = [ + self::$_transparentRedirectKeys, + ['creditCard' => CreditCardGateway::createSignature()], + ]; + self::$_updateCreditCardSignature = [ + self::$_transparentRedirectKeys, + 'paymentMethodToken', + ['creditCard' => CreditCardGateway::updateSignature()], + ]; + } + + public function confirm($queryString) + { + $params = TransparentRedirect::parseAndValidateQueryString( + $queryString + ); + $confirmationKlasses = [ + TransparentRedirect::CREATE_TRANSACTION => 'Braintree\TransactionGateway', + TransparentRedirect::CREATE_CUSTOMER => 'Braintree\CustomerGateway', + TransparentRedirect::UPDATE_CUSTOMER => 'Braintree\CustomerGateway', + TransparentRedirect::CREATE_PAYMENT_METHOD => 'Braintree\CreditCardGateway', + TransparentRedirect::UPDATE_PAYMENT_METHOD => 'Braintree\CreditCardGateway', + ]; + $confirmationGateway = new $confirmationKlasses[$params["kind"]]($this->_gateway); + return $confirmationGateway->_doCreate('/transparent_redirect_requests/' . $params['id'] . '/confirm', []); + } + + /** + * returns the trData string for creating a credit card, + * @param array $params + * @return string + */ + public function createCreditCardData($params) + { + Util::verifyKeys( + self::$_createCreditCardSignature, + $params + ); + $params["kind"] = TransparentRedirect::CREATE_PAYMENT_METHOD; + return $this->_data($params); + } + + /** + * returns the trData string for creating a customer. + * @param array $params + * @return string + */ + public function createCustomerData($params) + { + Util::verifyKeys( + self::$_createCustomerSignature, + $params + ); + $params["kind"] = TransparentRedirect::CREATE_CUSTOMER; + return $this->_data($params); + + } + + public function url() + { + return $this->_config->baseUrl() . $this->_config->merchantPath() . '/transparent_redirect_requests'; + } + + /** + * returns the trData string for creating a transaction + * @param array $params + * @return string + */ + public function transactionData($params) + { + Util::verifyKeys( + self::$_transactionSignature, + $params + ); + $params["kind"] = TransparentRedirect::CREATE_TRANSACTION; + $transactionType = isset($params['transaction']['type']) ? + $params['transaction']['type'] : + null; + if ($transactionType != Transaction::SALE && $transactionType != Transaction::CREDIT) { + throw new InvalidArgumentException( + 'expected transaction[type] of sale or credit, was: ' . + $transactionType + ); + } + + return $this->_data($params); + } + + /** + * Returns the trData string for updating a credit card. + * + * The paymentMethodToken of the credit card to update is required. + * + * + * $trData = TransparentRedirect::updateCreditCardData(array( + * 'redirectUrl' => 'http://example.com/redirect_here', + * 'paymentMethodToken' => 'token123', + * )); + * + * + * @param array $params + * @return string + */ + public function updateCreditCardData($params) + { + Util::verifyKeys( + self::$_updateCreditCardSignature, + $params + ); + if (!isset($params['paymentMethodToken'])) { + throw new InvalidArgumentException( + 'expected params to contain paymentMethodToken.' + ); + } + $params["kind"] = TransparentRedirect::UPDATE_PAYMENT_METHOD; + return $this->_data($params); + } + + /** + * Returns the trData string for updating a customer. + * + * The customerId of the customer to update is required. + * + * + * $trData = TransparentRedirect::updateCustomerData(array( + * 'redirectUrl' => 'http://example.com/redirect_here', + * 'customerId' => 'customer123', + * )); + * + * + * @param array $params + * @return string + */ + public function updateCustomerData($params) + { + Util::verifyKeys( + self::$_updateCustomerSignature, + $params + ); + if (!isset($params['customerId'])) { + throw new InvalidArgumentException( + 'expected params to contain customerId of customer to update' + ); + } + $params["kind"] = TransparentRedirect::UPDATE_CUSTOMER; + return $this->_data($params); + } + + public function parseAndValidateQueryString($queryString) + { + // parse the params into an array + parse_str($queryString, $params); + // remove the hash + $queryStringWithoutHash = null; + if (preg_match('/^(.*)&hash=[a-f0-9]+$/', $queryString, $match)) { + $queryStringWithoutHash = $match[1]; + } + + if($params['http_status'] != '200') { + $message = null; + if(array_key_exists('bt_message', $params)) { + $message = $params['bt_message']; + } + Util::throwStatusCodeException(isset($params['http_status']) ? $params['http_status'] : null, $message); + } + + // recreate the hash and compare it + if ($this->_hash($queryStringWithoutHash) == $params['hash']) { + return $params; + } else { + throw new Exception\ForgedQueryString(); + } + } + + + /** + * + * @ignore + */ + private function _data($params) + { + if (!isset($params['redirectUrl'])) { + throw new InvalidArgumentException( + 'expected params to contain redirectUrl' + ); + } + $params = $this->_underscoreKeys($params); + $now = new DateTime('now', new DateTimeZone('UTC')); + $trDataParams = array_merge($params, + [ + 'api_version' => Configuration::API_VERSION, + 'public_key' => $this->_config->publicKey(), + 'time' => $now->format('YmdHis'), + ] + ); + ksort($trDataParams); + $urlEncodedData = http_build_query($trDataParams, null, "&"); + $signatureService = new SignatureService( + $this->_config->privateKey(), + "Braintree\Digest::hexDigestSha1" + ); + return $signatureService->sign($urlEncodedData); + } + + private function _underscoreKeys($array) + { + foreach($array as $key=>$value) + { + $newKey = Util::camelCaseToDelimiter($key, '_'); + unset($array[$key]); + if (is_array($value)) + { + $array[$newKey] = $this->_underscoreKeys($value); + } + else + { + $array[$newKey] = $value; + } + } + return $array; + } + + /** + * @ignore + */ + private function _hash($string) + { + return Digest::hexDigestSha1($this->_config->privateKey(), $string); + } +} +TransparentRedirectGateway::init(); +class_alias('Braintree\TransparentRedirectGateway', 'Braintree_TransparentRedirectGateway'); diff --git a/lib/Braintree/UnknownPaymentMethod.php b/lib/Braintree/UnknownPaymentMethod.php new file mode 100644 index 0000000..74e7289 --- /dev/null +++ b/lib/Braintree/UnknownPaymentMethod.php @@ -0,0 +1,69 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $token + * @property-read string $imageUrl + */ +class UnknownPaymentMethod extends Base +{ + + + /** + * factory method: returns an instance of UnknownPaymentMethod + * to the requesting method, with populated properties + * + * @ignore + * @return UnknownPaymentMethod + */ + public static function factory($attributes) + { + $instance = new self(); + $values = array_values($attributes); + $instance->_initialize(array_shift($values)); + return $instance; + } + + /* instance methods */ + + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $unknownPaymentMethodAttribs array of unknownPaymentMethod data + * @return void + */ + protected function _initialize($unknownPaymentMethodAttribs) + { + // set the attributes + $this->imageUrl = 'https://assets.braintreegateway.com/payment_method_logo/unknown.png'; + $this->_attributes = $unknownPaymentMethodAttribs; + } + +} +class_alias('Braintree\UnknownPaymentMethod', 'Braintree_UnknownPaymentMethod'); diff --git a/lib/Braintree/UsBankAccount.php b/lib/Braintree/UsBankAccount.php new file mode 100644 index 0000000..4ebc420 --- /dev/null +++ b/lib/Braintree/UsBankAccount.php @@ -0,0 +1,117 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $customerId + * @property-read string $email + * @property-read string $token + * @property-read string $imageUrl + * @property-read string $routingNumber + * @property-read string $accountType + * @property-read string $accountHolderName + * @property-read string $last4 + * @property-read string $bankName + * @property-read string $achMandate + * @property-read boolean $default + * @property-read boolean $verified + */ +class UsBankAccount extends Base +{ + /** + * factory method: returns an instance of UsBankAccount + * to the requesting method, with populated properties + * + * @ignore + * @return UsBankAccount + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $usBankAccountAttribs array of usBankAccount data + * @return void + */ + protected function _initialize($usBankAccountAttribs) + { + // set the attributes + $this->_attributes = $usBankAccountAttribs; + + $achMandate = isset($usBankAccountAttribs['achMandate']) ? + AchMandate::factory($usBankAccountAttribs['achMandate']) : + null; + $this->_set('achMandate', $achMandate); + + if (isset($usBankAccountAttribs['verifications'])) { + $verification_records = $usBankAccountAttribs['verifications']; + + $verifications = array(); + for ($i = 0; $i < count($verification_records); $i++) { + $verifications[$i] = UsBankAccountVerification::factory($verification_records[$i]); + } + $this->_set('verifications', $verifications); + } else { + $this->_set('verifications', null); + } + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } + + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + // static methods redirecting to gateway + + public static function find($token) + { + return Configuration::gateway()->usBankAccount()->find($token); + } + + public static function sale($token, $transactionAttribs) + { + $transactionAttribs['options'] = [ + 'submitForSettlement' => true + ]; + return Configuration::gateway()->usBankAccount()->sale($token, $transactionAttribs); + } +} +class_alias('Braintree\UsBankAccount', 'Braintree_UsBankAccount'); diff --git a/lib/Braintree/UsBankAccountGateway.php b/lib/Braintree/UsBankAccountGateway.php new file mode 100644 index 0000000..6e3393c --- /dev/null +++ b/lib/Braintree/UsBankAccountGateway.php @@ -0,0 +1,106 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class UsBankAccountGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + + /** + * find a usBankAccount by token + * + * @access public + * @param string $token paypal accountunique id + * @return UsBankAccount + * @throws Exception\NotFound + */ + public function find($token) + { + try { + $path = $this->_config->merchantPath() . '/payment_methods/us_bank_account/' . $token; + $response = $this->_http->get($path); + return UsBankAccount::factory($response['usBankAccount']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'US bank account with token ' . $token . ' not found' + ); + } + + } + + /** + * create a new sale for the current UsBank account + * + * @param string $token + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + * @see Transaction::sale() + */ + public function sale($token, $transactionAttribs) + { + return Transaction::sale( + array_merge( + $transactionAttribs, + ['paymentMethodToken' => $token] + ) + ); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new UsBankAccount object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['usBankAccount'])) { + // return a populated instance of UsBankAccount + return new Result\Successful( + UsBankAccount::factory($response['usBankAccount']) + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + 'Expected US bank account or apiErrorResponse' + ); + } + } +} +class_alias('Braintree\UsBankAccountGateway', 'Braintree_UsBankAccountGateway'); diff --git a/lib/Braintree/UsBankAccountVerification.php b/lib/Braintree/UsBankAccountVerification.php new file mode 100644 index 0000000..a062a55 --- /dev/null +++ b/lib/Braintree/UsBankAccountVerification.php @@ -0,0 +1,102 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + */ +class UsBankAccountVerification extends Result\UsBankAccountVerification +{ + /** + * factory method: returns an instance of UsBankAccountVerification + * to the requesting method, with populated properties + * + * @ignore + * @return UsBankAccountVerification + */ + public static function factory($attributes) + { + $instance = new self($attributes); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $usBankAccountVerificationAttribs array of usBankAccountVerification data + * @return void + */ + protected function _initialize($usBankAccountVerificationAttribs) + { + // set the attributes + $this->_attributes = $usBankAccountVerificationAttribs; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . Util::attributesToString($this->_attributes) . ']'; + } + + + // static methods redirecting to gateway + + /** + * finds a US bank account verification + * + * @access public + * @param string $token unique id + * @return UsBankAccountVerification + */ + public static function find($token) + { + return Configuration::gateway()->usBankAccountVerification()->find($token); + } + + /** + * Returns a ResourceCollection of US bank account verifications matching the search query. + * + * @access public + * @param mixed $query search query + * @return ResourceCollection + */ + public static function search($query) + { + return Configuration::gateway()->usBankAccountVerification()->search($query); + } + + /** + * Returns a ResourceCollection of US bank account verifications matching the search query. + * + * @access public + * @param string $token unique id + * @param array $amounts micro transfer amounts + * @return ResourceCollection + */ + public static function confirmMicroTransferAmounts($token, $amounts) + { + return Configuration::gateway()->usBankAccountVerification()->confirmMicroTransferAmounts($token, $amounts); + } +} +class_alias('Braintree\UsBankAccountVerification', 'Braintree_UsBankAccountVerification'); diff --git a/lib/Braintree/UsBankAccountVerificationGateway.php b/lib/Braintree/UsBankAccountVerificationGateway.php new file mode 100644 index 0000000..31397ad --- /dev/null +++ b/lib/Braintree/UsBankAccountVerificationGateway.php @@ -0,0 +1,129 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class UsBankAccountVerificationGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * find a usBankAccountVerification by token + * + * @access public + * @param string $token unique id + * @return UsBankAccountVerification + * @throws Exception\NotFound + */ + public function find($token) + { + try { + $path = $this->_config->merchantPath() . '/us_bank_account_verifications/' . $token; + $response = $this->_http->get($path); + return UsBankAccountVerification::factory($response['usBankAccountVerification']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'US bank account with token ' . $token . ' not found' + ); + } + } + + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + $path = $this->_config->merchantPath() . '/us_bank_account_verifications/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } + + /** + * complete micro transfer verification by confirming the transfer amounts + * + * @access public + * @param string $token unique id + * @param array $amounts amounts deposited in micro transfer + * @return UsBankAccountVerification + * @throws Exception\Unexpected + */ + public function confirmMicroTransferAmounts($token, $amounts) + { + try { + $path = $this->_config->merchantPath() . '/us_bank_account_verifications/' . $token . '/confirm_micro_transfer_amounts'; + $response = $this->_http->put($path, [ + "us_bank_account_verification" => ["deposit_amounts" => $amounts] + ]); + return $this->_verifyGatewayResponse($response); + } catch (Exception\Unexpected $e) { + throw new Exception\Unexpected( + 'Unexpected exception.' + ); + } + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new UsBankAccountVerification object and encapsulates + * it inside a Result\Successful object, or + * encapsulates a Errors object inside a Result\Error + * alternatively, throws an Unexpected exception if the response is invalid. + * + * @ignore + * @param array $response gateway response values + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else if (isset($response['usBankAccountVerification'])) { + // return a populated instance of UsBankAccountVerification + return new Result\Successful( + UsBankAccountVerification::factory($response['usBankAccountVerification']) + ); + } else { + throw new Exception\Unexpected( + 'Expected US bank account or apiErrorResponse' + ); + } + } +} + +class_alias('Braintree\UsBankAccountVerificationGateway', 'Braintree_UsBankAccountVerificationGateway'); diff --git a/lib/Braintree/UsBankAccountVerificationSearch.php b/lib/Braintree/UsBankAccountVerificationSearch.php new file mode 100644 index 0000000..a6336dc --- /dev/null +++ b/lib/Braintree/UsBankAccountVerificationSearch.php @@ -0,0 +1,64 @@ +success) { + return $resultObj->$resultObjName; + } else { + throw new Exception\ValidationsFailed(); } } /** - * removes the Braintree_ header from a classname + * removes the header from a classname * - * @param string $name Braintree_ClassName - * @return camelCased classname minus Braintree_ header + * @param string $name ClassName + * @return camelCased classname minus header */ public static function cleanClassName($name) { - $classNamesToResponseKeys = array( - 'CreditCard' => 'creditCard', - 'Customer' => 'customer', - 'Subscription' => 'subscription', - 'Transaction' => 'transaction', - 'CreditCardVerification' => 'verification', - 'AddOn' => 'addOn', - 'Discount' => 'discount', - 'Plan' => 'plan', - 'Address' => 'address', - 'SettlementBatchSummary' => 'settlementBatchSummary', - 'MerchantAccount' => 'merchantAccount' - ); + $classNamesToResponseKeys = [ + 'Braintree\CreditCard' => 'creditCard', + 'Braintree_CreditCard' => 'creditCard', + 'Braintree\CreditCardGateway' => 'creditCard', + 'Braintree_CreditCardGateway' => 'creditCard', + 'Braintree\Customer' => 'customer', + 'Braintree_Customer' => 'customer', + 'Braintree\CustomerGateway' => 'customer', + 'Braintree_CustomerGateway' => 'customer', + 'Braintree\Subscription' => 'subscription', + 'Braintree_Subscription' => 'subscription', + 'Braintree\SubscriptionGateway' => 'subscription', + 'Braintree_SubscriptionGateway' => 'subscription', + 'Braintree\Transaction' => 'transaction', + 'Braintree_Transaction' => 'transaction', + 'Braintree\TransactionGateway' => 'transaction', + 'Braintree_TransactionGateway' => 'transaction', + 'Braintree\CreditCardVerification' => 'verification', + 'Braintree_CreditCardVerification' => 'verification', + 'Braintree\CreditCardVerificationGateway' => 'verification', + 'Braintree_CreditCardVerificationGateway' => 'verification', + 'Braintree\AddOn' => 'addOn', + 'Braintree_AddOn' => 'addOn', + 'Braintree\AddOnGateway' => 'addOn', + 'Braintree_AddOnGateway' => 'addOn', + 'Braintree\Discount' => 'discount', + 'Braintree_Discount' => 'discount', + 'Braintree\DiscountGateway' => 'discount', + 'Braintree_DiscountGateway' => 'discount', + 'Braintree\Dispute' => 'dispute', + 'Braintree_Dispute' => 'dispute', + 'Braintree\Dispute\EvidenceDetails' => 'evidence', + 'Braintree_Dispute_EvidenceDetails' => 'evidence', + 'Braintree\DocumentUpload' => 'documentUpload', + 'Braintree_DocumentUpload' => 'doumentUpload', + 'Braintree\Plan' => 'plan', + 'Braintree_Plan' => 'plan', + 'Braintree\PlanGateway' => 'plan', + 'Braintree_PlanGateway' => 'plan', + 'Braintree\Address' => 'address', + 'Braintree_Address' => 'address', + 'Braintree\AddressGateway' => 'address', + 'Braintree_AddressGateway' => 'address', + 'Braintree\SettlementBatchSummary' => 'settlementBatchSummary', + 'Braintree_SettlementBatchSummary' => 'settlementBatchSummary', + 'Braintree\SettlementBatchSummaryGateway' => 'settlementBatchSummary', + 'Braintree_SettlementBatchSummaryGateway' => 'settlementBatchSummary', + 'Braintree\Merchant' => 'merchant', + 'Braintree_Merchant' => 'merchant', + 'Braintree\MerchantGateway' => 'merchant', + 'Braintree_MerchantGateway' => 'merchant', + 'Braintree\MerchantAccount' => 'merchantAccount', + 'Braintree_MerchantAccount' => 'merchantAccount', + 'Braintree\MerchantAccountGateway' => 'merchantAccount', + 'Braintree_MerchantAccountGateway' => 'merchantAccount', + 'Braintree\OAuthCredentials' => 'credentials', + 'Braintree_OAuthCredentials' => 'credentials', + 'Braintree\OAuthResult' => 'result', + 'Braintree_OAuthResult' => 'result', + 'Braintree\PayPalAccount' => 'paypalAccount', + 'Braintree_PayPalAccount' => 'paypalAccount', + 'Braintree\PayPalAccountGateway' => 'paypalAccount', + 'Braintree_PayPalAccountGateway' => 'paypalAccount', + 'Braintree\UsBankAccountVerification' => 'usBankAccountVerification', + 'Braintree_UsBankAccountVerification' => 'usBankAccountVerification', + ]; - $name = str_replace('Braintree_', '', $name); return $classNamesToResponseKeys[$name]; } /** * * @param string $name className - * @return string Braintree_ClassName + * @return string ClassName */ public static function buildClassName($name) { - $responseKeysToClassNames = array( - 'creditCard' => 'CreditCard', - 'customer' => 'Customer', - 'subscription' => 'Subscription', - 'transaction' => 'Transaction', - 'verification' => 'CreditCardVerification', - 'addOn' => 'AddOn', - 'discount' => 'Discount', - 'plan' => 'Plan', - 'address' => 'Address', - 'settlementBatchSummary' => 'SettlementBatchSummary', - 'merchantAccount' => 'MerchantAccount' - ); + $responseKeysToClassNames = [ + 'creditCard' => 'Braintree\CreditCard', + 'customer' => 'Braintree\Customer', + 'dispute' => 'Braintree\Dispute', + 'documentUpload' => 'Braintree\DocumentUpload', + 'subscription' => 'Braintree\Subscription', + 'transaction' => 'Braintree\Transaction', + 'verification' => 'Braintree\CreditCardVerification', + 'addOn' => 'Braintree\AddOn', + 'discount' => 'Braintree\Discount', + 'plan' => 'Braintree\Plan', + 'address' => 'Braintree\Address', + 'settlementBatchSummary' => 'Braintree\SettlementBatchSummary', + 'merchantAccount' => 'Braintree\MerchantAccount', + ]; - return 'Braintree_' . $responseKeysToClassNames[$name]; + return (string) $responseKeysToClassNames[$name]; } /** @@ -134,16 +247,16 @@ public static function buildClassName($name) * * @access public * @param string $string + * @param null|string $delimiter * @return string modified string */ public static function delimiterToCamelCase($string, $delimiter = '[\-\_]') { - // php doesn't garbage collect functions created by create_function() - // so use a static variable to avoid adding a new function to memory - // every time this function is called. static $callback = null; if ($callback === null) { - $callback = create_function('$matches', 'return strtoupper($matches[1]);'); + $callback = function ($matches) { + return strtoupper($matches[1]); + }; } return preg_replace_callback('/' . $delimiter . '(\w)/', $callback, $string); @@ -166,20 +279,60 @@ public static function delimiterToUnderscore($string) * find capitals and convert to delimiter + lowercase * * @access public - * @param var $string - * @return var modified string + * @param string $string + * @param null|string $delimiter + * @return string modified string */ public static function camelCaseToDelimiter($string, $delimiter = '-') { - // php doesn't garbage collect functions created by create_function() - // so use a static variable to avoid adding a new function to memory - // every time this function is called. - static $callbacks = array(); - if (!isset($callbacks[$delimiter])) { - $callbacks[$delimiter] = create_function('$matches', "return '$delimiter' . strtolower(\$matches[1]);"); + return strtolower(preg_replace('/([A-Z])/', "$delimiter\\1", $string)); + } + + public static function delimiterToCamelCaseArray($array, $delimiter = '[\-\_]') + { + $converted = []; + foreach ($array as $key => $value) { + if (is_string($key)) { + $key = self::delimiterToCamelCase($key, $delimiter); + } + + if (is_array($value)) { + // Make an exception for custom fields, which must be underscore (can't be + // camelCase). + if ($key === 'customFields') { + $value = self::delimiterToUnderscoreArray($value); + } else { + $value = self::delimiterToCamelCaseArray($value, $delimiter); + } + } + $converted[$key] = $value; } + return $converted; + } + + public static function camelCaseToDelimiterArray($array, $delimiter = '-') + { + $converted = []; + foreach ($array as $key => $value) { + if (is_string($key)) { + $key = self::camelCaseToDelimiter($key, $delimiter); + } + if (is_array($value)) { + $value = self::camelCaseToDelimiterArray($value, $delimiter); + } + $converted[$key] = $value; + } + return $converted; + } - return preg_replace_callback('/([A-Z])/', $callbacks[$delimiter], $string); + public static function delimiterToUnderscoreArray($array) + { + $converted = []; + foreach ($array as $key => $value) { + $key = self::delimiterToUnderscore($key); + $converted[$key] = $value; + } + return $converted; } /** @@ -187,24 +340,27 @@ public static function camelCaseToDelimiter($string, $delimiter = '-') * @param array $array associative array to implode * @param string $separator (optional, defaults to =) * @param string $glue (optional, defaults to ', ') + * @return bool */ public static function implodeAssociativeArray($array, $separator = '=', $glue = ', ') { // build a new array with joined keys and values $tmpArray = null; foreach ($array AS $key => $value) { - $tmpArray[] = $key . $separator . $value; - + if ($value instanceof DateTime) { + $value = $value->format('r'); + } + $tmpArray[] = $key . $separator . $value; } // implode and return the new array return (is_array($tmpArray)) ? implode($glue, $tmpArray) : false; } public static function attributesToString($attributes) { - $printableAttribs = array(); + $printableAttribs = []; foreach ($attributes AS $key => $value) { if (is_array($value)) { - $pAttrib = Braintree_Util::attributesToString($value); + $pAttrib = self::attributesToString($value); } else if ($value instanceof DateTime) { $pAttrib = $value->format(DateTime::RFC850); } else { @@ -212,7 +368,7 @@ public static function attributesToString($attributes) { } $printableAttribs[$key] = sprintf('%s', $pAttrib); } - return Braintree_Util::implodeAssociativeArray($printableAttribs); + return self::implodeAssociativeArray($printableAttribs); } /** @@ -234,7 +390,7 @@ public static function verifyKeys($signature, $attributes) if(!empty($invalidKeys)) { asort($invalidKeys); $sortedList = join(', ', $invalidKeys); - throw new InvalidArgumentException('invalid keys: '. $sortedList); + throw new InvalidArgumentException('invalid keys: ' . $sortedList); } } /** @@ -245,7 +401,7 @@ public static function verifyKeys($signature, $attributes) */ private static function _flattenArray($keys, $namespace = null) { - $flattenedArray = array(); + $flattenedArray = []; foreach($keys AS $key) { if(is_array($key)) { $theKeys = array_keys($key); @@ -264,7 +420,7 @@ private static function _flattenArray($keys, $namespace = null) private static function _flattenUserKeys($keys, $namespace = null) { - $flattenedArray = array(); + $flattenedArray = []; foreach($keys AS $key => $value) { $fullKey = empty($namespace) ? $key : $namespace; @@ -306,3 +462,4 @@ private static function _removeWildcardKeys($validKeys, $invalidKeys) return $invalidKeys; } } +class_alias('Braintree\Util', 'Braintree_Util'); diff --git a/lib/Braintree/VenmoAccount.php b/lib/Braintree/VenmoAccount.php new file mode 100644 index 0000000..91f0da3 --- /dev/null +++ b/lib/Braintree/VenmoAccount.php @@ -0,0 +1,75 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $imageUrl + * @property-read string $sourceDescription + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read \DateTime $updatedAt + * @property-read string $username + * @property-read string $venmoUserId + */ +class VenmoAccount extends Base +{ + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * factory method: returns an instance of VenmoAccount + * to the requesting method, with populated properties + * + * @ignore + * @return VenmoAccount + */ + public static function factory($attributes) + { + + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $venmoAccountAttribs array of Venmo account properties + * @return void + */ + protected function _initialize($venmoAccountAttribs) + { + $this->_attributes = $venmoAccountAttribs; + + $subscriptionArray = array(); + if (isset($venmoAccountAttribs['subscriptions'])) { + foreach ($venmoAccountAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } +} +class_alias('Braintree\VenmoAccount', 'Braintree_VenmoAccount'); diff --git a/lib/Braintree/Version.php b/lib/Braintree/Version.php index 5a04005..35eb484 100644 --- a/lib/Braintree/Version.php +++ b/lib/Braintree/Version.php @@ -1,23 +1,17 @@ == More information == + * + * For more detailed information on CreditCard verifications, see {@link https://developers.braintreepayments.com/reference/response/credit-card-verification/php https://developers.braintreepayments.com/reference/response/credit-card-verification/php} + * + * @package Braintree + * @category Resources + * + * @property-read \Braintree\Address $billingAddress + * @property-read string $bin + * @property-read string $callId + * @property-read string $cardType + * @property-read string $cardholderName + * @property-read string $commercial + * @property-read string $countryOfIssuance + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read string $customerLocation + * @property-read string $debit + * @property-read boolean $default + * @property-read string $durbinRegulated + * @property-read string $expirationDate + * @property-read string $expirationMonth + * @property-read string $expirationYear + * @property-read boolean $expired + * @property-read string $healthcare + * @property-read string $imageUrl + * @property-read string $issuingBank + * @property-read string $last4 + * @property-read string $maskedNumber + * @property-read string $payroll + * @property-read string $prepaid + * @property-read string $productId + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read string $uniqueNumberIdentifier + * @property-read \DateTime $updatedAt + */ +class VisaCheckoutCard extends Base +{ + /* instance methods */ + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + /** + * checks whether the card is expired based on the current date + * + * @return boolean + */ + public function isExpired() + { + return $this->expired; + } + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $creditCardAttribs array of creditcard data + * @return void + */ + protected function _initialize($creditCardAttribs) + { + // set the attributes + $this->_attributes = $creditCardAttribs; + + // map each address into its own object + $billingAddress = isset($creditCardAttribs['billingAddress']) ? + Address::factory($creditCardAttribs['billingAddress']) : + null; + + $subscriptionArray = []; + if (isset($creditCardAttribs['subscriptions'])) { + foreach ($creditCardAttribs['subscriptions'] AS $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + $this->_set('billingAddress', $billingAddress); + $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); + $this->_set('maskedNumber', $this->bin . '******' . $this->last4); + + if(isset($creditCardAttribs['verifications']) && count($creditCardAttribs['verifications']) > 0) { + $verifications = $creditCardAttribs['verifications']; + usort($verifications, [$this, '_compareCreatedAtOnVerifications']); + + $this->_set('verification', CreditCardVerification::factory($verifications[0])); + } + } + + private function _compareCreatedAtOnVerifications($verificationAttrib1, $verificationAttrib2) + { + return ($verificationAttrib2['createdAt'] < $verificationAttrib1['createdAt']) ? -1 : 1; + } + + /** + * returns false if comparing object is not a VisaCheckoutCard, + * or is a VisaCheckoutCard with a different id + * + * @param object $otherVisaCheckoutCard customer to compare against + * @return boolean + */ + public function isEqual($otherVisaCheckoutCard) + { + return !($otherVisaCheckoutCard instanceof self) ? false : $this->token === $otherVisaCheckoutCard->token; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) .']'; + } + + /** + * factory method: returns an instance of VisaCheckoutCard + * to the requesting method, with populated properties + * + * @ignore + * @return VisaCheckoutCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } +} +class_alias('Braintree\VisaCheckoutCard', 'Braintree_VisaCheckoutCard'); diff --git a/lib/Braintree/WebhookNotification.php b/lib/Braintree/WebhookNotification.php index 873211d..c669dc8 100644 --- a/lib/Braintree/WebhookNotification.php +++ b/lib/Braintree/WebhookNotification.php @@ -1,5 +1,7 @@ webhookNotification()->parse($signature, $payload); } - public static function verify($challenge) - { - $publicKey = Braintree_Configuration::publicKey(); - $digest = Braintree_Digest::hexDigest($challenge); - return "{$publicKey}|{$digest}"; + public static function verify($challenge) { + return Configuration::gateway()->webhookNotification()->verify($challenge); } public static function factory($attributes) @@ -40,34 +49,14 @@ public static function factory($attributes) return $instance; } - private static function _matchingSignature($signaturePairs) - { - foreach ($signaturePairs as $pair) - { - $components = preg_split("/\|/", $pair); - if ($components[0] == Braintree_Configuration::publicKey()) { - return $components[1]; - } - } - - return null; - } - - private static function _validateSignature($signature, $payload) - { - $signaturePairs = preg_split("/&/", $signature); - $matchingSignature = self::_matchingSignature($signaturePairs); - - $payloadSignature = Braintree_Digest::hexDigest($payload); - if (!Braintree_Digest::secureCompare($matchingSignature, $payloadSignature)) { - throw new Braintree_Exception_InvalidSignature("webhook notification signature invalid"); - } - } - protected function _initialize($attributes) { $this->_attributes = $attributes; + if (!isset($attributes['sourceMerchantId'])) { + $this->_set('sourceMerchantId', null); + } + if (isset($attributes['subject']['apiErrorResponse'])) { $wrapperNode = $attributes['subject']['apiErrorResponse']; } else { @@ -75,28 +64,65 @@ protected function _initialize($attributes) } if (isset($wrapperNode['subscription'])) { - $this->_set('subscription', Braintree_Subscription::factory($attributes['subject']['subscription'])); + $this->_set('subscription', Subscription::factory($attributes['subject']['subscription'])); } if (isset($wrapperNode['merchantAccount'])) { - $this->_set('merchantAccount', Braintree_MerchantAccount::factory($wrapperNode['merchantAccount'])); + $this->_set('merchantAccount', MerchantAccount::factory($wrapperNode['merchantAccount'])); } if (isset($wrapperNode['transaction'])) { - $this->_set('transaction', Braintree_Transaction::factory($wrapperNode['transaction'])); + $this->_set('transaction', Transaction::factory($wrapperNode['transaction'])); } if (isset($wrapperNode['disbursement'])) { - $this->_set('disbursement', Braintree_Disbursement::factory($wrapperNode['disbursement'])); + $this->_set('disbursement', Disbursement::factory($wrapperNode['disbursement'])); } if (isset($wrapperNode['partnerMerchant'])) { - $this->_set('partnerMerchant', Braintree_PartnerMerchant::factory($wrapperNode['partnerMerchant'])); + $this->_set('partnerMerchant', PartnerMerchant::factory($wrapperNode['partnerMerchant'])); + } + + if (isset($wrapperNode['oauthApplicationRevocation'])) { + $this->_set('oauthAccessRevocation', OAuthAccessRevocation::factory($wrapperNode['oauthApplicationRevocation'])); + } + + if (isset($wrapperNode['connectedMerchantStatusTransitioned'])) { + $this->_set('connectedMerchantStatusTransitioned', ConnectedMerchantStatusTransitioned::factory($wrapperNode['connectedMerchantStatusTransitioned'])); + } + + if (isset($wrapperNode['connectedMerchantPaypalStatusChanged'])) { + $this->_set('connectedMerchantPayPalStatusChanged', ConnectedMerchantPayPalStatusChanged::factory($wrapperNode['connectedMerchantPaypalStatusChanged'])); + } + + if (isset($wrapperNode['dispute'])) { + $this->_set('dispute', Dispute::factory($wrapperNode['dispute'])); + } + + if (isset($wrapperNode['accountUpdaterDailyReport'])) { + $this->_set('accountUpdaterDailyReport', AccountUpdaterDailyReport::factory($wrapperNode['accountUpdaterDailyReport'])); + } + + if (isset($wrapperNode['idealPayment'])) { + $this->_set('idealPayment', IdealPayment::factory($wrapperNode['idealPayment'])); + } + + if (isset($wrapperNode['grantedPaymentInstrumentUpdate'])) { + $this->_set('grantedPaymentInstrumentUpdate', GrantedPaymentInstrumentUpdate::factory($wrapperNode['grantedPaymentInstrumentUpdate'])); + } + + if ($attributes['kind'] == self::GRANTED_PAYMENT_METHOD_REVOKED) { + $this->_set('revokedPaymentMethodMetadata', RevokedPaymentMethodMetadata::factory($wrapperNode)); + } + + if (isset($wrapperNode['localPayment'])) { + $this->_set('localPaymentCompleted', LocalPaymentCompleted::factory($wrapperNode['localPayment'])); } if (isset($wrapperNode['errors'])) { - $this->_set('errors', new Braintree_Error_ValidationErrorCollection($wrapperNode['errors'])); + $this->_set('errors', new Error\ValidationErrorCollection($wrapperNode['errors'])); $this->_set('message', $wrapperNode['message']); } } } +class_alias('Braintree\WebhookNotification', 'Braintree_WebhookNotification'); diff --git a/lib/Braintree/WebhookNotificationGateway.php b/lib/Braintree/WebhookNotificationGateway.php new file mode 100644 index 0000000..17ef2bb --- /dev/null +++ b/lib/Braintree/WebhookNotificationGateway.php @@ -0,0 +1,77 @@ +config = $gateway->config; + $this->config->assertHasAccessTokenOrKeys(); + } + + public function parse($signature, $payload) + { + if (is_null($signature)) { + throw new Exception\InvalidSignature("signature cannot be null"); + } + + if (is_null($payload)) { + throw new Exception\InvalidSignature("payload cannot be null"); + } + + if (preg_match("/[^A-Za-z0-9+=\/\n]/", $payload) === 1) { + throw new Exception\InvalidSignature("payload contains illegal characters"); + } + + self::_validateSignature($signature, $payload); + + $xml = base64_decode($payload); + $attributes = Xml::buildArrayFromXml($xml); + return WebhookNotification::factory($attributes['notification']); + } + + public function verify($challenge) + { + if (!preg_match('/^[a-f0-9]{20,32}$/', $challenge)) { + throw new Exception\InvalidChallenge("challenge contains non-hex characters"); + } + $publicKey = $this->config->getPublicKey(); + $digest = Digest::hexDigestSha1($this->config->getPrivateKey(), $challenge); + return "{$publicKey}|{$digest}"; + } + + private function _payloadMatches($signature, $payload) + { + $payloadSignature = Digest::hexDigestSha1($this->config->getPrivateKey(), $payload); + return Digest::secureCompare($signature, $payloadSignature); + } + + private function _validateSignature($signatureString, $payload) + { + $signaturePairs = preg_split("/&/", $signatureString); + $signature = self::_matchingSignature($signaturePairs); + if (!$signature) { + throw new Exception\InvalidSignature("no matching public key"); + } + + if (!(self::_payloadMatches($signature, $payload) || self::_payloadMatches($signature, $payload . "\n"))) { + throw new Exception\InvalidSignature("signature does not match payload - one has been modified"); + } + } + + private function _matchingSignature($signaturePairs) + { + foreach ($signaturePairs as $pair) + { + $components = preg_split("/\|/", $pair); + if ($components[0] == $this->config->getPublicKey()) { + return $components[1]; + } + } + + return null; + } +} + +class_alias('Braintree\WebhookNotificationGateway', 'Braintree_WebhookNotificationGateway'); diff --git a/lib/Braintree/WebhookTesting.php b/lib/Braintree/WebhookTesting.php index f8d74da..350591c 100644 --- a/lib/Braintree/WebhookTesting.php +++ b/lib/Braintree/WebhookTesting.php @@ -1,217 +1,11 @@ $signature, - 'payload' => $payload - ); - } - - private static function _sampleXml($kind, $id) - { - switch ($kind) { - case Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED: - $subjectXml = self::_merchantAccountApprovedSampleXml($id); - break; - case Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED: - $subjectXml = self::_merchantAccountDeclinedSampleXml($id); - break; - case Braintree_WebhookNotification::TRANSACTION_DISBURSED: - $subjectXml = self::_transactionDisbursedSampleXml($id); - break; - case Braintree_WebhookNotification::DISBURSEMENT_EXCEPTION: - $subjectXml = self::_disbursementExceptionSampleXml($id); - break; - case Braintree_WebhookNotification::DISBURSEMENT: - $subjectXml = self::_disbursementSampleXml($id); - break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_CONNECTED: - $subjectXml = self::_partnerMerchantConnectedSampleXml($id); - break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_DISCONNECTED: - $subjectXml = self::_partnerMerchantDisconnectedSampleXml($id); - break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_DECLINED: - $subjectXml = self::_partnerMerchantDeclinedSampleXml($id); - break; - default: - $subjectXml = self::_subscriptionSampleXml($id); - break; - } - $timestamp = self::_timestamp(); - return " - - {$timestamp} - {$kind} - {$subjectXml} - - "; - } - - private static function _merchantAccountApprovedSampleXml($id) - { - return " - - {$id} - - master_ma_for_{$id} - active - - active - - "; - } - - private static function _merchantAccountDeclinedSampleXml($id) - { - return " - - Credit score is too low - - - - - - 82621 - Credit score is too low - base - - - - - - {$id} - suspended - - master_ma_for_{$id} - suspended - - - - "; - } - - private static function _transactionDisbursedSampleXml($id) - { - return " - - ${id} - 100 - - 2013-07-09 - - - "; - } - - private static function _disbursementExceptionSampleXml($id) - { - return " - - ${id} - - asdfg - qwert - - false - false - - merchant_account_token - USD - false - active - - 100.00 - 2014-02-10 - bank_rejected - update_funding_information - - "; - } - - private static function _disbursementSampleXml($id) - { - return " - - ${id} - - asdfg - qwert - - true - false - - merchant_account_token - USD - false - active - - 100.00 - 2014-02-10 - - - - "; - } - - private static function _subscriptionSampleXml($id) - { - return " - - {$id} - - - - - - - - "; - } - - private static function _partnerMerchantConnectedSampleXml($id) - { - return " - - public_id - public_key - private_key - abc123 - cse_key - - "; - } +namespace Braintree; - private static function _partnerMerchantDisconnectedSampleXml($id) - { - return " - - abc123 - - "; - } - - private static function _partnerMerchantDeclinedSampleXml($id) - { - return " - - abc123 - - "; - } - - private static function _timestamp() +class WebhookTesting +{ + public static function sampleNotification($kind, $id, $sourceMerchantId = null) { - $originalZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $timestamp = strftime('%Y-%m-%dT%TZ'); - date_default_timezone_set($originalZone); - - return $timestamp; + return Configuration::gateway()->webhookTesting()->sampleNotification($kind, $id, $sourceMerchantId); } } +class_alias('Braintree\WebhookTesting', 'Braintree_WebhookTesting'); diff --git a/lib/Braintree/WebhookTestingGateway.php b/lib/Braintree/WebhookTestingGateway.php new file mode 100644 index 0000000..ce675c9 --- /dev/null +++ b/lib/Braintree/WebhookTestingGateway.php @@ -0,0 +1,553 @@ +config = $gateway->config; + $this->config->assertHasAccessTokenOrKeys(); + } + + public function sampleNotification($kind, $id, $sourceMerchantId = null) + { + $xml = self::_sampleXml($kind, $id, $sourceMerchantId); + $payload = base64_encode($xml) . "\n"; + $signature = $this->config->getPublicKey() . "|" . Digest::hexDigestSha1($this->config->getPrivateKey(), $payload); + + return [ + 'bt_signature' => $signature, + 'bt_payload' => $payload + ]; + } + + private static function _sampleXml($kind, $id, $sourceMerchantId) + { + switch ($kind) { + case WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED: + $subjectXml = self::_merchantAccountApprovedSampleXml($id); + break; + case WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED: + $subjectXml = self::_merchantAccountDeclinedSampleXml($id); + break; + case WebhookNotification::TRANSACTION_DISBURSED: + $subjectXml = self::_transactionDisbursedSampleXml($id); + break; + case WebhookNotification::TRANSACTION_SETTLED: + $subjectXml = self::_transactionSettledSampleXml($id); + break; + case WebhookNotification::TRANSACTION_SETTLEMENT_DECLINED: + $subjectXml = self::_transactionSettlementDeclinedSampleXml($id); + break; + case WebhookNotification::DISBURSEMENT_EXCEPTION: + $subjectXml = self::_disbursementExceptionSampleXml($id); + break; + case WebhookNotification::DISBURSEMENT: + $subjectXml = self::_disbursementSampleXml($id); + break; + case WebhookNotification::PARTNER_MERCHANT_CONNECTED: + $subjectXml = self::_partnerMerchantConnectedSampleXml($id); + break; + case WebhookNotification::PARTNER_MERCHANT_DISCONNECTED: + $subjectXml = self::_partnerMerchantDisconnectedSampleXml($id); + break; + case WebhookNotification::PARTNER_MERCHANT_DECLINED: + $subjectXml = self::_partnerMerchantDeclinedSampleXml($id); + break; + case WebhookNotification::OAUTH_ACCESS_REVOKED: + $subjectXml = self::_oauthAccessRevocationSampleXml($id); + break; + case WebhookNotification::CONNECTED_MERCHANT_STATUS_TRANSITIONED: + $subjectXml = self::_connectedMerchantStatusTransitionedSampleXml($id); + break; + case WebhookNotification::CONNECTED_MERCHANT_PAYPAL_STATUS_CHANGED: + $subjectXml = self::_connectedMerchantPayPalStatusChangedSampleXml($id); + break; + case WebhookNotification::DISPUTE_OPENED: + $subjectXml = self::_disputeOpenedSampleXml($id); + break; + case WebhookNotification::DISPUTE_LOST: + $subjectXml = self::_disputeLostSampleXml($id); + break; + case WebhookNotification::DISPUTE_WON: + $subjectXml = self::_disputeWonSampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_CHARGED_SUCCESSFULLY: + $subjectXml = self::_subscriptionChargedSuccessfullySampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_CHARGED_UNSUCCESSFULLY: + $subjectXml = self::_subscriptionChargedUnsuccessfullySampleXml($id); + break; + case WebhookNotification::CHECK: + $subjectXml = self::_checkSampleXml(); + break; + case WebhookNotification::ACCOUNT_UPDATER_DAILY_REPORT: + $subjectXml = self::_accountUpdaterDailyReportSampleXml($id); + break; + case WebhookNotification::IDEAL_PAYMENT_COMPLETE: + $subjectXml = self::_idealPaymentCompleteSampleXml($id); + break; + case WebhookNotification::IDEAL_PAYMENT_FAILED: + $subjectXml = self::_idealPaymentFailedSampleXml($id); + break; + case WebhookNotification::GRANTED_PAYMENT_INSTRUMENT_UPDATE: + $subjectXml = self::_grantedPaymentInstrumentUpdateSampleXml(); + break; + case WebhookNotification::LOCAL_PAYMENT_COMPLETED: + $subjectXml = self::_localPaymentCompletedSampleXml(); + break; + default: + $subjectXml = self::_subscriptionSampleXml($id); + break; + } + $timestamp = self::_timestamp(); + + $sourceMerchantIdXml = ''; + if (!is_null($sourceMerchantId)) { + $sourceMerchantIdXml = "{$sourceMerchantId}"; + } + + return " + + {$timestamp} + {$kind} + {$sourceMerchantIdXml} + {$subjectXml} + + "; + } + + private static function _merchantAccountApprovedSampleXml($id) + { + return " + + {$id} + + master_ma_for_{$id} + active + + active + + "; + } + + private static function _merchantAccountDeclinedSampleXml($id) + { + return " + + Credit score is too low + + + + + + 82621 + Credit score is too low + base + + + + + + {$id} + suspended + + master_ma_for_{$id} + suspended + + + + "; + } + + private static function _transactionDisbursedSampleXml($id) + { + return " + + ${id} + 100 + + 2013-07-09 + + + "; + } + + private static function _transactionSettledSampleXml($id) + { + return " + + ${id} + settled + sale + USD + 100.00 + ogaotkivejpfayqfeaimuktty + us_bank_account + + 123456789 + 1234 + checking + Dan Schulman + + + "; + } + + private static function _transactionSettlementDeclinedSampleXml($id) + { + return " + + ${id} + settlement_declined + sale + USD + 100.00 + ogaotkivejpfayqfeaimuktty + us_bank_account + + 123456789 + 1234 + checking + Dan Schulman + + + "; + } + + private static function _disbursementExceptionSampleXml($id) + { + return " + + ${id} + + asdfg + qwert + + false + false + + merchant_account_token + USD + false + active + + 100.00 + 2014-02-10 + bank_rejected + update_funding_information + + "; + } + + private static function _disbursementSampleXml($id) + { + return " + + ${id} + + asdfg + qwert + + true + false + + merchant_account_token + USD + false + active + + 100.00 + 2014-02-10 + + + + "; + } + + private static function _disputeOpenedSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + open + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + + "; + } + + private static function _disputeLostSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + lost + fraud + ${id} + + ${id} + 250.00 + 2020-02-10 + + 2014-03-21 + + "; + } + + private static function _disputeWonSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + won + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + 2014-03-22 + + "; + } + + private static function _subscriptionSampleXml($id) + { + return " + + {$id} + + + + + + + + "; + } + + private static function _subscriptionChargedSuccessfullySampleXml($id) + { + return " + + {$id} + 2016-03-21 + 2017-03-31 + + + {$id} + submitted_for_settlement + 49.99 + + + + + + + + "; + } + + private static function _subscriptionChargedUnsuccessfullySampleXml($id) + { + return " + + {$id} + 2016-03-21 + 2017-03-31 + + + {$id} + failed + 49.99 + + + + + + + + "; + } + + private static function _checkSampleXml() + { + return " + true + "; + } + + private static function _partnerMerchantConnectedSampleXml($id) + { + return " + + public_id + public_key + private_key + abc123 + cse_key + + "; + } + + private static function _partnerMerchantDisconnectedSampleXml($id) + { + return " + + abc123 + + "; + } + + private static function _partnerMerchantDeclinedSampleXml($id) + { + return " + + abc123 + + "; + } + + private static function _oauthAccessRevocationSampleXml($id) + { + return " + + {$id} + oauth_application_client_id + + "; + } + + private static function _accountUpdaterDailyReportSampleXml($id) + { + return " + + 2016-01-14 + link-to-csv-report + + "; + } + + private static function _connectedMerchantStatusTransitionedSampleXml($id) + { + return " + + {$id} + new_status + oauth_application_client_id + + "; + } + + private static function _connectedMerchantPayPalStatusChangedSampleXml($id) + { + return " + + {$id} + link + oauth_application_client_id + + "; + } + + private static function _idealPaymentCompleteSampleXml($id) + { + return " + + {$id} + COMPLETE + ABCISSUER + ORDERABC + EUR + 10.00 + 2016-11-29T23:27:34.547Z + https://example.com + 1234567890 + + "; + } + + private static function _idealPaymentFailedSampleXml($id) + { + return " + + {$id} + FAILED + ABCISSUER + ORDERABC + EUR + 10.00 + 2016-11-29T23:27:34.547Z + https://example.com + 1234567890 + + "; + } + + private static function _grantedPaymentInstrumentUpdateSampleXml() + { + return " + + vczo7jqrpwrsi2px + cf0i8wgarszuy6hc + + ee257d98-de40-47e8-96b3-a6954ea7a9a4 + false + false + + abc123z + + expiration-month + expiration-year + + + "; + } + + private static function _localPaymentCompletedSampleXml() + { + return " + + a-payment-id + a-payer-id + + "; + } + + private static function _timestamp() + { + $originalZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $timestamp = strftime('%Y-%m-%dT%TZ'); + date_default_timezone_set($originalZone); + + return $timestamp; + } +} +class_alias('Braintree\WebhookTestingGateway', 'Braintree_WebhookTestingGateway'); diff --git a/lib/Braintree/Xml.php b/lib/Braintree/Xml.php index a6e5119..e938c40 100644 --- a/lib/Braintree/Xml.php +++ b/lib/Braintree/Xml.php @@ -1,17 +1,12 @@ openMemory(); @@ -33,7 +37,7 @@ public static function arrayToXml($aData) $aKeys = array_keys($aData); $rootElementName = $aKeys[0]; // open the root element - $writer->startElement(Braintree_Util::camelCaseToDelimiter($rootElementName)); + $writer->startElement($rootElementName); // create the body self::_createElementsFromArray($writer, $aData[$rootElementName], $rootElementName); @@ -52,7 +56,7 @@ public static function arrayToXml($aData) * @static * @param object $writer XMLWriter object * @param array $aData contains attributes and values - * @return none + * @return void */ private static function _createElementsFromArray(&$writer, $aData) { @@ -64,9 +68,7 @@ private static function _createElementsFromArray(&$writer, $aData) } return; } - foreach ($aData AS $index => $element) { - // convert the style back to gateway format - $elementName = Braintree_Util::camelCaseToDelimiter($index, '-'); + foreach ($aData AS $elementName => $element) { // handle child elements $writer->startElement($elementName); if (is_array($element)) { @@ -104,28 +106,30 @@ private static function _createElementsFromArray(&$writer, $aData) private static function _generateXmlAttribute($value) { if ($value instanceof DateTime) { - return array('type', 'datetime', self::_dateTimeToXmlTimestamp($value)); + return ['type', 'datetime', self::_dateTimeToXmlTimestamp($value)]; } if (is_int($value)) { - return array('type', 'integer', $value); + return ['type', 'integer', $value]; } if (is_bool($value)) { - return array('type', 'boolean', ($value ? 'true' : 'false')); + return ['type', 'boolean', ($value ? 'true' : 'false')]; } if ($value === NULL) { - return array('nil', 'true', $value); + return ['nil', 'true', $value]; } } /** * converts datetime back to xml schema format * @access protected * @param object $dateTime - * @return var XML schema formatted timestamp + * @return string XML schema formatted timestamp */ private static function _dateTimeToXmlTimestamp($dateTime) { - $dateTime->setTimeZone(new DateTimeZone('UTC')); - return ($dateTime->format('Y-m-d\TH:i:s') . 'Z'); + $dateTimeForUTC = clone $dateTime; + + $dateTimeForUTC->setTimeZone(new DateTimeZone('UTC')); + return ($dateTimeForUTC->format('Y-m-d\TH:i:s') . 'Z'); } private static function _castDateTime($string) @@ -142,3 +146,4 @@ private static function _castDateTime($string) } } } +class_alias('Braintree\Xml\Generator', 'Braintree_Xml_Generator'); diff --git a/lib/Braintree/Xml/Parser.php b/lib/Braintree/Xml/Parser.php index e4fea27..1c0d05e 100644 --- a/lib/Braintree/Xml/Parser.php +++ b/lib/Braintree/Xml/Parser.php @@ -1,179 +1,140 @@ getName()); - $type = $iterator->attributes()->type; - - self::$_xmlRoot = $iterator->getName(); - self::$_responseType = $type; + $document = new DOMDocument('1.0', 'UTF-8'); + $document->loadXML($xml); - // return the mapped array with the root element as the header - return array($xmlRoot => self::_iteratorToArray($iterator)); + $root = $document->documentElement->nodeName; + return Util::delimiterToCamelCaseArray([ + $root => self::_nodeToValue($document->childNodes->item(0)), + ]); } /** - * processes SimpleXMLIterator objects recursively + * Converts a node to an array of values or nodes * - * @access protected - * @param object $iterator - * @return array xml converted to array + * @param DOMNode @node + * @return mixed */ - private static function _iteratorToArray($iterator) + private static function _nodeToArray($node) { - $xmlArray = array(); - $value = null; - - // rewind the iterator and check if the position is valid - // if not, return the string it contains - $iterator->rewind(); - if (!$iterator->valid()) { - return self::_typecastXmlValue($iterator); + $type = null; + if ($node instanceof DOMElement) { + $type = $node->getAttribute('type'); } - for ($iterator->rewind(); $iterator->valid(); $iterator->next()) { - - $tmpArray = null; - $value = null; - - // get the attribute type string for use in conditions below - $attributeType = $iterator->attributes()->type; - - // extract the parent element via xpath query - $parentElement = $iterator->xpath($iterator->key() . '/..'); - if ($parentElement[0] instanceof SimpleXMLIterator) { - $parentElement = $parentElement[0]; - $parentKey = Braintree_Util::delimiterToCamelCase($parentElement->getName()); - } else { - $parentElement = null; - } - - - if ($parentKey == "customFields") { - $key = Braintree_Util::delimiterToUnderscore($iterator->key()); - } else { - $key = Braintree_Util::delimiterToCamelCase($iterator->key()); - } - - // process children recursively - if ($iterator->hasChildren()) { - // return the child elements - $value = self::_iteratorToArray($iterator->current()); - // if the element is an array type, - // use numeric keys to allow multiple values - if ($attributeType != 'array') { - $tmpArray[$key] = $value; + switch($type) { + case 'array': + $array = []; + foreach ($node->childNodes as $child) { + $value = self::_nodeToValue($child); + if ($value !== null) { + $array[] = $value; } - } else { - // cast values according to attributes - $tmpArray[$key] = self::_typecastXmlValue($iterator->current()); } - - // set the output string - $output = isset($value) ? $value : $tmpArray[$key]; - - // determine if there are multiple tags of this name at the same level - if (isset($parentElement) && - ($parentElement->attributes()->type == 'collection') && - $iterator->hasChildren()) { - $xmlArray[$key][] = $output; - continue; + return $array; + case 'collection': + $collection = []; + foreach ($node->childNodes as $child) { + $value = self::_nodetoValue($child); + if ($value !== null) { + if (!isset($collection[$child->nodeName])) { + $collection[$child->nodeName] = []; + } + $collection[$child->nodeName][] = self::_nodeToValue($child); + } } - - // if the element was an array type, output to a numbered key - // otherwise, use the element name - if ($attributeType == 'array') { - $xmlArray[] = $output; + return $collection; + default: + $values = []; + if ($node->childNodes->length === 1 && $node->childNodes->item(0) instanceof DOMText) { + return $node->childNodes->item(0)->nodeValue; } else { - $xmlArray[$key] = $output; + foreach ($node->childNodes as $child) { + if (!$child instanceof DOMText) { + $values[$child->nodeName] = self::_nodeToValue($child); + } + } + return $values; } } - - return $xmlArray; } /** - * typecast xml value based on attributes - * @param object $valueObj SimpleXMLElement - * @return mixed value for placing into array + * Converts a node to a PHP value + * + * @param DOMNode $node + * @return mixed */ - private static function _typecastXmlValue($valueObj) + private static function _nodeToValue($node) { - // get the element attributes - $attribs = $valueObj->attributes(); - // the element is null, so jump out here - if (isset($attribs->nil) && $attribs->nil) { - return null; - } - // switch on the type attribute - // switch works even if $attribs->type isn't set - switch ($attribs->type) { - case 'datetime': - return self::_timestampToUTC((string) $valueObj); - break; - case 'date': - return new DateTime((string)$valueObj); - break; - case 'integer': - return (int) $valueObj; - break; - case 'boolean': - $value = (string) $valueObj; - // look for a number inside the string - if(is_numeric($value)) { - return (bool) $value; - } else { - // look for the string "true", return false in all other cases - return ($value != "true") ? FALSE : TRUE; - } - break; - case 'array': - return array(); - default: - return (string) $valueObj; + $type = null; + if ($node instanceof DOMElement) { + $type = $node->getAttribute('type'); } + switch($type) { + case 'datetime': + return self::_timestampToUTC((string) $node->nodeValue); + case 'date': + return new DateTime((string) $node->nodeValue); + case 'integer': + return (int) $node->nodeValue; + case 'boolean': + $value = (string) $node->nodeValue; + if(is_numeric($value)) { + return (bool) $value; + } else { + return ($value !== "true") ? false : true; + } + case 'array': + case 'collection': + return self::_nodeToArray($node); + default: + if ($node->hasChildNodes()) { + return self::_nodeToArray($node); + } elseif (trim($node->nodeValue) === '') { + return null; + } else { + return $node->nodeValue; + } + } } + /** - * convert xml timestamps into DateTime + * Converts XML timestamps into DateTime instances + * * @param string $timestamp - * @return string UTC formatted datetime string + * @return DateTime */ private static function _timestampToUTC($timestamp) { $tz = new DateTimeZone('UTC'); - // strangely DateTime requires an explicit set below - // to show the proper time zone $dateTime = new DateTime($timestamp, $tz); $dateTime->setTimezone($tz); return $dateTime; } } +class_alias('Braintree\Xml\Parser', 'Braintree_Xml_Parser'); diff --git a/lib/autoload.php b/lib/autoload.php new file mode 100644 index 0000000..1b5edc0 --- /dev/null +++ b/lib/autoload.php @@ -0,0 +1,21 @@ + true|false, - * "error_message" => "Error message", - * "transaction_id" => "XXX", - * - * //If the payment is captured in this method, return a "captured_payment" array with the following information about the payment - * "captured_payment" => ["is_success"=>true|false, "error_message" => "error message", "transaction_id" => "xxx", "amount" => 20] - * ] - * @since 1.0 - * @return void - */ + * Enqueue scripts. + * + * @since 1.4.0 + * @return array + */ + public function scripts() { + $scripts = []; + + if( $settings = $this->get_plugin_settings() ) { + $scripts = [ + [ + 'handle' => 'braintree_client', + 'src' => 'https://js.braintreegateway.com/web/3.43.0/js/client.min.js', + 'version' => $this->_version, + 'deps' => [], + 'enqueue' => [ + [$this, 'aft_enabled'] + ] + ], + [ + 'handle' => 'braintree_data_collector', + 'src' => 'https://js.braintreegateway.com/web/3.43.0/js/data-collector.min.js', + 'version' => $this->_version, + 'deps' => [], + 'enqueue' => [ + [$this, 'aft_enabled'] + ] + ], + [ + 'handle' => 'braintree_data_processing', + 'src' => $this->get_base_url() . '/../assets/js/braintree-data-processing.js', + 'version' => $this->_version, + 'deps' => ['jquery'], + 'strings' => [ + 'bt_magic' => $settings['tokenization-key'], + 'bt_field' => $this->get_device_data_field_value() + ], + 'enqueue' => [ + [$this, 'aft_enabled'] + ] + ], + ]; + } + + return array_merge(parent::scripts(), $scripts); + } + + /** + * After form has been submitted, send CC details to Braintree and ensure the card is going to work + * If not, void the validation result (processed elsewhere) and have the submit the form again + * + * @param $feed - Current configured payment feed + * @param $submission_data - Contains form field data submitted by the user as well as payment information (i.e. payment amount, setup fee, line items, etc...) + * @param $form - Current form array containing all form settings + * @param $entry - Current entry array containing entry information (i.e data submitted by users). NOTE: the entry hasn't been saved to the database at this point, so this $entry object does not have the "ID" property and is only a memory representation of the entry. + * @return array - Return an $authorization array in the following format: + * [ + * "is_authorized" => true|false, + * "error_message" => "Error message", + * "transaction_id" => "XXX", + * + * //If the payment is captured in this method, return a "captured_payment" array with the following information about the payment + * "captured_payment" => ["is_success"=>true|false, "error_message" => "error message", "transaction_id" => "xxx", "amount" => 20] + * ] + * @since 1.0 + * @return void + */ public function authorize( $feed, $submission_data, $form, $entry ) { // Prepare authorization response payload @@ -85,6 +133,9 @@ public function authorize( $feed, $submission_data, $form, $entry ) { $card_number = str_replace( array( '-', ' ' ), '', $submission_data['card_number'] ); // Prepare Braintree payload + $namePieces = explode(' ', $submission_data['card_name']); + $lastName = array_pop($namePieces); + $firstName = implode(' ', $namePieces); $args = array( 'amount' => $submission_data['payment_amount'], 'creditCard' => array( @@ -94,43 +145,56 @@ public function authorize( $feed, $submission_data, $form, $entry ) { 'cvv' => $submission_data['card_security_code'] ), 'customer' => array( - 'firstName' => $submission_data['card_name'] - ), + 'lastName' => $lastName, + 'firstName' => $firstName, + 'email' => $submission_data['email'] + ), 'billing' => array( - 'firstName' => $submission_data['card_name'], + 'lastName' => $lastName, + 'firstName' => $firstName, 'streetAddress' => $submission_data['address'], 'locality' => $submission_data['city'], 'postalCode' => $submission_data['zip'] - ) + ) ); try { // Configure Braintree environment - Braintree_Configuration::environment( strtolower( $settings['environment'] ) ); - Braintree_Configuration::merchantId( $settings['merchant-id']); - Braintree_Configuration::publicKey( $settings['public-key'] ); - Braintree_Configuration::privateKey( $settings['private-key'] ); + Braintree\Configuration::environment( strtolower( $settings['environment'] ) ); + Braintree\Configuration::merchantId( $settings['merchant-id']); + Braintree\Configuration::publicKey( $settings['public-key'] ); + Braintree\Configuration::privateKey( $settings['private-key'] ); - // Set to auto settlemt if applicable + // Set to auto settlement if applicable if( $settings['settlement'] == 'Yes' ) { $args['options']['submitForSettlement'] = 'true'; } + if ($feed['meta']['taxExempt'] == 1) { + $args['taxExempt'] = 'true'; + } + + if ($feed['meta']['enableAFT'] == 1 && !empty($submission_data['device_data'])) { + $args['deviceData'] = $submission_data['device_data']; + } + GFCommon::log_debug('Braintree Transaction Args: ' . print_r( $args, true )); + // Send transaction to Braintree - $result = Braintree_Transaction::sale( $args ); + $result = Braintree\Transaction::sale( $args ); + GFCommon::log_debug('Braintree Transaction Sale Result: ' . print_r( $result, true )); // Update response to reflect successful payment if( $result->success == '1' ) { $authorization['is_authorized'] = true; $authorization['error_message'] = ''; - $authorization['transaction_id'] = $result->transaction->_attributes['id']; + $authorization['transaction_id'] = $result->transaction->id; $authorization['captured_payment'] = array( 'is_success' => true, - 'transaction_id' => $result->transaction->_attributes['id'], - 'amount' => $result->transaction->_attributes['amount'], + 'transaction_id' => $result->transaction->id, + 'amount' => $result->transaction->amount, 'error_message' => '', 'payment_method' => 'Credit Card' ); @@ -140,15 +204,16 @@ public function authorize( $feed, $submission_data, $form, $entry ) { // Append gateway response text to error message if it exists. If it doesn't exist, a more hardcore // failure has occured and it won't do the user any good to see it other than a general error message - if( isset( $result->_attributes['transaction']->_attributes['processorResponseText'] ) ) { - $authorization['error_message'] .= sprintf( '. Your bank said: %s.', $result->_attributes['transaction']->_attributes['processorResponseText'] ); + if( isset( $result->transaction->processorResponseText ) ) { + $authorization['error_message'] .= sprintf( '. Your bank said: %s.', $result->transaction->processorResponseText ); } } } catch( Exception $e ) { - // Do nothing with exception object, just fallback to generic failure + // Log exception object message, then fallback to generic failure + GFCommon::log_debug('Braintree Exception: ' . print_r( $e->getMessage(), true )); } return $authorization; @@ -160,58 +225,280 @@ public function authorize( $feed, $submission_data, $form, $entry ) { } /** - * Create and display feed settings fields. - * - * @since 1.0 - * @return void - */ + * Override this method to add integration code to the payment processor in order to create a subscription. This method is executed during the form validation process and allows + * the form submission process to fail with a validation error if there is anything wrong when creating the subscription. + * + * @param $feed - Current configured payment feed + * @param $submission_data - Contains form field data submitted by the user as well as payment information (i.e. payment amount, setup fee, line items, etc...) + * @param $form - Current form array containing all form settings + * @param $entry - Current entry array containing entry information (i.e data submitted by users). NOTE: the entry hasn't been saved to the database at this point, so this $entry object does not have the 'ID' property and is only a memory representation of the entry. + * + * @since 1.2.0 + * @return array - Return an $subscription array in the following format: + * [ + * 'is_success'=>true|false, + * 'error_message' => 'error message', + * 'subscription_id' => 'xxx', + * 'amount' => 10 + * + * //To implement an initial/setup fee for gateways that don't support setup fees as part of subscriptions, manually capture the funds for the setup fee as a separate transaction and send that payment + * //information in the following 'captured_payment' array + * 'captured_payment' => ['name' => 'Setup Fee', 'is_success'=>true|false, 'error_message' => 'error message', 'transaction_id' => 'xxx', 'amount' => 20] + * ] + */ + public function subscribe( $feed, $submission_data, $form, $entry ) { + + // Prepare authorization response payload + $authorization = [ + 'is_authorized' => false, + 'error_message' => apply_filters( 'gform_braintree_credit_card_failure_message', __( 'Your card could not be billed. Please ensure the details you entered are correct and try again.', 'gravity-forms-braintree' ) ), + 'subscription_id' => '', + 'amount' => '', + 'captured_payment' => [ + 'is_success' => false, + 'error_message' => '', + 'subscription_id' => '', + 'amount' => $submission_data['payment_amount'] + ] + ]; + + if( $settings = $this->get_plugin_settings() ) { + + // Sanitize card number, removing dashes and spaces + $card_number = str_replace(array('-', ' '), '', $submission_data['card_number']); + + // Prepare Braintree payload + $namePieces = explode(' ', $submission_data['card_name']); + $args = [ + 'creditCard' => [ + 'billingAddress' => [ + 'countryName' => $submission_data['country'], + 'streetAddress' => $submission_data['address'], + 'locality' => $submission_data['city'], + 'region' => $submission_data['state'], + 'postalCode' => $submission_data['zip'] + ], + 'number' => $card_number, + 'expirationDate' => sprintf('%s/%s', $submission_data['card_expiration_date'][0], $submission_data['card_expiration_date'][1]), + 'cardholderName' => $submission_data['card_name'], + 'cvv' => $submission_data['card_security_code'] + ], + 'email' => $submission_data['email'], + 'lastName' => array_pop($namePieces), + 'firstName' => implode(' ', $namePieces) + ]; + + try { + // Configure Braintree environment + Braintree\Configuration::environment(strtolower($settings['environment'])); + Braintree\Configuration::merchantId($settings['merchant-id']); + Braintree\Configuration::publicKey($settings['public-key']); + Braintree\Configuration::privateKey($settings['private-key']); + + $plans = Braintree\Plan::all(); + + // See if there is a plan with a matching dollar value. + $thePlan = null; + if (count($plans) == 1) { + $thePlan = $plans[0]; + } else if (count($plans) > 1) { + foreach ($plans as $plan) { + if ((float)$submission_data['payment_amount'] == (float)$plan->price) { + $thePlan = $plan; + break; + } + } + if (empty($thePlan)) { + $thePlan = $plans[0]; + } + } else { + $authorization['error_message'] = apply_filters('gform_braintree_no_plans_failure_message', __('No subscription plans are available.', 'gravity-forms-braintree')); + } + + if (!empty($thePlan)) { + $collection = Braintree\Customer::search([ + Braintree\CustomerSearch::email()->is($args['email']) + ]); + + if ($collection->maximumCount() > 0) { + foreach ($collection as $customer) { + $result = $customer; + } + } else { + if ($feed['meta']['enableAFT'] == 1 && !empty($submission_data['device_data'])) { + $args['deviceData'] = $submission_data['device_data']; + } + + $result = Braintree\Customer::create($args); + } + + if (!empty($result)) { + if (get_class($result) != 'Braintree\Customer') { + $result = $result->customer; + } + + $subscription = [ + 'paymentMethodToken' => $result->creditCards[0]->token, + 'planId' => $thePlan->id + ]; + + if (!empty($submission_data['first_bill_date'])) { + $dateTime = new DateTime($submission_data['first_bill_date']); + $subscription['firstBillingDate'] = $dateTime; + } + + if ((float)$submission_data['payment_amount'] != (float)$plan->price) { + $subscription['price'] = (float)$submission_data['payment_amount']; + } + + $subscriptionResult = Braintree\Subscription::create($subscription); + + if ($subscriptionResult->success == true) { + + $authorization['is_success'] = true; + $authorization['error_message'] = ''; + $authorization['subscription_id'] = $subscriptionResult->subscription->id; + $authorization['amount'] = $subscriptionResult->subscription->price; + + $authorization['captured_payment'] = [ + 'is_success' => true, + 'subscription_id' => $subscriptionResult->subscription->id, + 'transaction_id' => $subscriptionResult->subscription->transactions[0]->id, + 'amount' => $subscriptionResult->subscription->price, + 'error_message' => '' + ]; + + } + } else { + $authorization['error_message'] = apply_filters('gform_braintree_customer_create_failure_message', __('Failed to create a customer.', 'gravity-forms-braintree')); + } + } else { + $authorization['error_message'] = apply_filters('gform_braintree_no_plan_message', __('No subscription plan found.', 'gravity-forms-braintree')); + } + + } catch (Exception $e) { + // Log exception object message, then fallback to generic failure + GFCommon::log_debug('Braintree Exception: ' . print_r( $e->getMessage(), true )); + } + } + + return $authorization; + } + + /** + * Create and display feed settings fields. + * + * @since 1.0 + * @return array + */ public function feed_settings_fields () { // Get defaults from GFPaymentAddOn $settings = parent::feed_settings_fields(); - // Remove billing information - //$settings = $this->remove_field( 'billingInformation', $settings ); - // Remove options $settings = $this->remove_field( 'options', $settings ); - // Remove the subscription option from transaction type dropdown $transaction_type = $this->get_field( 'transactionType', $settings ); - foreach( $transaction_type['choices'] as $index => $choice ) { - if( $choice['value'] == 'subscription' ) { - unset( $transaction_type['choices'][$index] ); - } - } - $settings = $this->replace_field( 'transactionType', $transaction_type, $settings ); + // Add tax exempt field to feed settings. + $fields = array( + array( + 'label' => 'Tax Exempt', + 'type' => 'checkbox', + 'name' => 'taxExempt', + 'choices' => array( + array( + 'label' => 'Enabled', + 'name' => 'taxExempt' + ) + ) + ), + array( + 'label' => 'Enable Advanced Fraud Tools', + 'type' => 'checkbox', + 'name' => 'enableAFT', + 'tooltip' => esc_html__( '
Advanced Fraud Tools
Add a hidden field as the first field in the form and set it below for Device Data.', 'simplefeedaddon' ), + 'choices' => array( + array( + 'label' => 'Enabled', + 'name' => 'enableAFT' + ) + ) + ) + ); + $settings = $this->add_field_after( 'transactionType', $fields, $settings ); + // Return sanitized settings return $settings; } /** - * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed - * - * @since 1.0 - * @return void - */ + * Update billing fields. + * + * @since 1.2.1 + * @return array + */ + public function billing_info_fields() { + $default_settings = parent::billing_info_fields(); + + $default_settings[] = array( 'name' => 'first_bill_date', 'label' => __( 'First Billing Date', 'gravityforms' ), 'required' => false ); + $default_settings[] = array( 'name' => 'device_data', 'label' => __( 'Device Data', 'gravityforms' ), 'required' => false ); + + return $default_settings; + } + + /** + * Check if the enableAFT box is checked for script enqueueing. + * + * @param $form + * @since 1.4.0 + * @return bool + */ + public function aft_enabled( $form ) { + if ($form && $this->has_feed( $form['id'] )) { + $feed = $this->get_feed($form['id']); + + return !empty($feed['meta']) && !empty($feed['meta']['enableAFT']); + } + + return false; + } + + /** + * Get the name of the device data field. + * + * @since 1.4.0 + * @return string + */ + public function get_device_data_field_value( ) { + // @todo Need to get the setting of the field name that device_data is set to. + return 'input_11'; + } + + /** + * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed + * + * @since 1.0 + * @return array + */ public function plugin_settings_fields () { return array( - array( - 'title' => 'Account Settings', - 'fields' => array( - array( - 'name' => 'merchant-id', - 'tooltip' => 'Your Braintree Merchant ID', - 'label' => 'Merchant ID', - 'type' => 'text', - 'class' => 'medium' - ), + array( + 'title' => 'Account Settings', + 'fields' => array( + array( + 'name' => 'merchant-id', + 'tooltip' => 'Your Braintree Merchant ID', + 'label' => 'Merchant ID', + 'type' => 'text', + 'class' => 'medium' + ), array( 'name' => 'public-key', 'tooltip' => 'Your Braintree Account Public Key', @@ -225,9 +512,16 @@ public function plugin_settings_fields () { 'label' => 'Private Key', 'type' => 'text', 'class' => 'medium' + ), + array( + 'name' => 'tokenization-key', + 'tooltip' => 'Your Braintree Account Tokenization Key', + 'label' => 'Tokenization Key', + 'type' => 'text', + 'class' => 'medium' ) - ) - ), + ) + ), array( 'title' => 'Payment Settings', 'fields' => array( @@ -271,17 +565,17 @@ public function plugin_settings_fields () { ) ) - ); + ); } /** - * Helper function to determine if all Braintree settings have been set. - * Does not check if they are correct, only that they have been set, IE not null - * @param @settings Plugin settings to check if valid - * @since 1.0 - * @return void - */ + * Helper function to determine if all Braintree settings have been set. + * Does not check if they are correct, only that they have been set, IE not null + * @param @settings Plugin settings to check if valid + * @since 1.0 + * @return boolean + */ public function settings_are_valid ( $settings ) { if( empty( $settings ) ) { @@ -299,11 +593,11 @@ public function settings_are_valid ( $settings ) { } /** - * Get plugin settings - * - * @since 1.0 - * @return void - */ + * Get plugin settings + * + * @since 1.0 + * @return array|boolean + */ public function get_plugin_settings () { $settings = parent::get_plugin_settings(); diff --git a/lib/ssl/api_braintreegateway_com.ca.crt b/lib/ssl/api_braintreegateway_com.ca.crt index e142318..7dcf4af 100644 --- a/lib/ssl/api_braintreegateway_com.ca.crt +++ b/lib/ssl/api_braintreegateway_com.ca.crt @@ -1,68 +1,4 @@ -----BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp @@ -91,20 +27,6 @@ WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz @@ -150,35 +72,71 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI 4uJEvlz36hz1 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW @@ -212,27 +170,6 @@ DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs -IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg -R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A -PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 -Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL -TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL -5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 -S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe -2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE -FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap -EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td -EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv -/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN -A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 -abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF -I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz -4iIprn2DQKi6bA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG @@ -253,99 +190,52 @@ hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= -----END CERTIFICATE----- diff --git a/readme.txt b/readme.txt index 05daa4d..2c6ff23 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: Plugify, hello@lukerollans.me Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=hello%40plugify%2eio&lc=GB&item_name=Plugin%20Development%20Donation¤cy_code=USD Tags: credit card,braintree,gravity form,payment Requires at least: 3.8 -Tested up to: 3.9 -Stable tag: 1.1.1 +Tested up to: 5.1.1 +Stable tag: 1.4.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -61,6 +61,19 @@ No filters are currently available for this pre-release version == Changelog == += 1.4.0 = +* Add Braintree's Advanced Fraud Tools integration. + += 1.3.1 = +* Fix bug with tax exempt flag. + += 1.3.0 = +* Update Braintree SDK files to 3.39.0 version. + += 1.2.0 = +* Update Braintree SDK files to the latest version. +* Add ability to use subscriptions. + = 1.1.1 = * Dashes and spaces are now removed from credit card number before sending to Braintree