From 8f4f7419b8fc590853d6228330f91e9e7abc3c89 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Thu, 21 Jul 2016 12:32:26 -0600 Subject: [PATCH 01/19] Update to latest version of Braintree PHP library. --- .gitignore | 2 + lib/.DS_Store | Bin 0 -> 6148 bytes lib/Braintree.php | 168 +---- lib/Braintree/AccountUpdaterDailyReport.php | 45 ++ lib/Braintree/AddOn.php | 33 +- lib/Braintree/AddOnGateway.php | 53 ++ lib/Braintree/Address.php | 315 ++------- lib/Braintree/AddressGateway.php | 310 +++++++++ lib/Braintree/AmexExpressCheckoutCard.php | 81 +++ lib/Braintree/AndroidPayCard.php | 90 +++ lib/Braintree/ApplePayCard.php | 100 +++ lib/Braintree/Base.php | 77 +++ lib/Braintree/ClientToken.php | 49 ++ lib/Braintree/ClientTokenGateway.php | 129 ++++ lib/Braintree/CoinbaseAccount.php | 112 +++ lib/Braintree/Collection.php | 25 +- lib/Braintree/Configuration.php | 454 +++++++++---- lib/Braintree/CredentialsParser.php | 148 ++++ lib/Braintree/CreditCard.php | 556 ++++----------- lib/Braintree/CreditCardGateway.php | 485 +++++++++++++ lib/Braintree/CreditCardVerification.php | 50 +- .../CreditCardVerificationGateway.php | 74 ++ .../CreditCardVerificationSearch.php | 75 +- lib/Braintree/Customer.php | 524 +++++--------- lib/Braintree/CustomerGateway.php | 626 +++++++++++++++++ lib/Braintree/CustomerSearch.php | 52 +- lib/Braintree/Descriptor.php | 5 +- lib/Braintree/Digest.php | 29 +- lib/Braintree/Disbursement.php | 21 +- lib/Braintree/DisbursementDetails.php | 18 +- lib/Braintree/Discount.php | 25 +- lib/Braintree/DiscountGateway.php | 31 + lib/Braintree/Dispute.php | 82 +++ lib/Braintree/Dispute/TransactionDetails.php | 27 + lib/Braintree/EqualityNode.php | 4 +- lib/Braintree/Error/Codes.php | 499 +++++++++----- lib/Braintree/Error/ErrorCollection.php | 38 +- lib/Braintree/Error/Validation.php | 25 +- .../Error/ValidationErrorCollection.php | 29 +- lib/Braintree/EuropeBankAccount.php | 68 ++ lib/Braintree/Exception.php | 14 +- lib/Braintree/Exception/Authentication.php | 17 +- lib/Braintree/Exception/Authorization.php | 15 +- lib/Braintree/Exception/Configuration.php | 15 +- .../Exception/DownForMaintenance.php | 15 +- lib/Braintree/Exception/ForgedQueryString.php | 18 +- lib/Braintree/Exception/InvalidChallenge.php | 9 + lib/Braintree/Exception/InvalidSignature.php | 8 +- lib/Braintree/Exception/NotFound.php | 15 +- lib/Braintree/Exception/SSLCaFileNotFound.php | 15 +- lib/Braintree/Exception/SSLCertificate.php | 15 +- lib/Braintree/Exception/ServerError.php | 15 +- .../TestOperationPerformedInProduction.php | 16 + lib/Braintree/Exception/Timeout.php | 17 + lib/Braintree/Exception/TooManyRequests.php | 17 + lib/Braintree/Exception/Unexpected.php | 15 +- lib/Braintree/Exception/UpgradeRequired.php | 9 +- lib/Braintree/Exception/ValidationsFailed.php | 15 +- lib/Braintree/FacilitatorDetails.php | 30 + lib/Braintree/Gateway.php | 198 ++++++ lib/Braintree/Http.php | 172 +++-- lib/Braintree/Instance.php | 32 +- lib/Braintree/IsNode.php | 13 +- lib/Braintree/KeyValueNode.php | 11 +- lib/Braintree/Merchant.php | 36 + lib/Braintree/MerchantAccount.php | 162 +---- .../MerchantAccount/AddressDetails.php | 9 +- .../MerchantAccount/BusinessDetails.php | 8 +- .../MerchantAccount/FundingDetails.php | 8 +- .../MerchantAccount/IndividualDetails.php | 8 +- lib/Braintree/MerchantAccountGateway.php | 154 +++++ lib/Braintree/MerchantGateway.php | 42 ++ lib/Braintree/Modification.php | 17 +- lib/Braintree/MultipleValueNode.php | 18 +- lib/Braintree/MultipleValueOrTextNode.php | 20 +- lib/Braintree/OAuthCredentials.php | 36 + lib/Braintree/OAuthGateway.php | 123 ++++ lib/Braintree/OAuthResult.php | 36 + lib/Braintree/PartialMatchNode.php | 8 +- lib/Braintree/PartnerMerchant.php | 16 +- lib/Braintree/PayPalAccount.php | 111 +++ lib/Braintree/PayPalAccountGateway.php | 180 +++++ lib/Braintree/PaymentInstrumentType.php | 13 + lib/Braintree/PaymentMethod.php | 47 ++ lib/Braintree/PaymentMethodGateway.php | 300 ++++++++ lib/Braintree/PaymentMethodNonce.php | 55 ++ lib/Braintree/PaymentMethodNonceGateway.php | 67 ++ lib/Braintree/Plan.php | 40 +- lib/Braintree/PlanGateway.php | 34 + lib/Braintree/RangeNode.php | 18 +- lib/Braintree/ResourceCollection.php | 51 +- .../Result/CreditCardVerification.php | 39 +- lib/Braintree/Result/Error.php | 59 +- lib/Braintree/Result/Successful.php | 67 +- lib/Braintree/RiskData.php | 30 + lib/Braintree/SettlementBatchSummary.php | 75 +- .../SettlementBatchSummaryGateway.php | 106 +++ lib/Braintree/SignatureService.php | 24 + lib/Braintree/Subscription.php | 255 ++----- lib/Braintree/Subscription/StatusDetails.php | 23 + lib/Braintree/SubscriptionGateway.php | 215 ++++++ lib/Braintree/SubscriptionSearch.php | 61 +- lib/Braintree/Test/CreditCardNumbers.php | 52 +- lib/Braintree/Test/MerchantAccount.php | 7 +- lib/Braintree/Test/Nonces.php | 70 ++ lib/Braintree/Test/Transaction.php | 64 ++ lib/Braintree/Test/TransactionAmounts.php | 13 +- lib/Braintree/Test/VenmoSdk.php | 7 +- lib/Braintree/TestingGateway.php | 52 ++ lib/Braintree/TextNode.php | 6 +- lib/Braintree/ThreeDSecureInfo.php | 30 + lib/Braintree/Transaction.php | 640 +++++++----------- lib/Braintree/Transaction/AddressDetails.php | 18 +- .../AmexExpressCheckoutCardDetails.php | 45 ++ .../Transaction/AndroidPayCardDetails.php | 49 ++ .../Transaction/ApplePayCardDetails.php | 41 ++ lib/Braintree/Transaction/CoinbaseDetails.php | 40 ++ .../Transaction/CreditCardDetails.php | 20 +- lib/Braintree/Transaction/CustomerDetails.php | 17 +- .../Transaction/EuropeBankAccountDetails.php | 25 + lib/Braintree/Transaction/PayPalDetails.php | 43 ++ lib/Braintree/Transaction/StatusDetails.php | 17 +- .../Transaction/SubscriptionDetails.php | 16 +- .../Transaction/VenmoAccountDetails.php | 40 ++ lib/Braintree/TransactionGateway.php | 520 ++++++++++++++ lib/Braintree/TransactionSearch.php | 213 +++--- lib/Braintree/TransparentRedirect.php | 260 +------ lib/Braintree/TransparentRedirectGateway.php | 290 ++++++++ lib/Braintree/UnknownPaymentMethod.php | 71 ++ lib/Braintree/Util.php | 268 +++++--- lib/Braintree/VenmoAccount.php | 75 ++ lib/Braintree/Version.php | 19 +- lib/Braintree/WebhookNotification.php | 67 +- lib/Braintree/WebhookTesting.php | 179 ++++- lib/Braintree/Xml.php | 16 +- lib/Braintree/Xml/Generator.php | 41 +- lib/Braintree/Xml/Parser.php | 225 +++--- lib/autoload.php | 21 + lib/ssl/api_braintreegateway_com.ca.crt | 290 ++------ 139 files changed, 8886 insertions(+), 3802 deletions(-) create mode 100644 .gitignore create mode 100644 lib/.DS_Store create mode 100644 lib/Braintree/AccountUpdaterDailyReport.php create mode 100644 lib/Braintree/AddOnGateway.php create mode 100644 lib/Braintree/AddressGateway.php create mode 100644 lib/Braintree/AmexExpressCheckoutCard.php create mode 100644 lib/Braintree/AndroidPayCard.php create mode 100644 lib/Braintree/ApplePayCard.php create mode 100644 lib/Braintree/Base.php create mode 100644 lib/Braintree/ClientToken.php create mode 100644 lib/Braintree/ClientTokenGateway.php create mode 100644 lib/Braintree/CoinbaseAccount.php create mode 100644 lib/Braintree/CredentialsParser.php create mode 100644 lib/Braintree/CreditCardGateway.php create mode 100644 lib/Braintree/CreditCardVerificationGateway.php create mode 100644 lib/Braintree/CustomerGateway.php create mode 100644 lib/Braintree/DiscountGateway.php create mode 100644 lib/Braintree/Dispute.php create mode 100644 lib/Braintree/Dispute/TransactionDetails.php create mode 100644 lib/Braintree/EuropeBankAccount.php create mode 100644 lib/Braintree/Exception/InvalidChallenge.php create mode 100644 lib/Braintree/Exception/TestOperationPerformedInProduction.php create mode 100644 lib/Braintree/Exception/Timeout.php create mode 100644 lib/Braintree/Exception/TooManyRequests.php create mode 100644 lib/Braintree/FacilitatorDetails.php create mode 100644 lib/Braintree/Gateway.php create mode 100644 lib/Braintree/Merchant.php create mode 100644 lib/Braintree/MerchantAccountGateway.php create mode 100644 lib/Braintree/MerchantGateway.php create mode 100644 lib/Braintree/OAuthCredentials.php create mode 100644 lib/Braintree/OAuthGateway.php create mode 100644 lib/Braintree/OAuthResult.php create mode 100644 lib/Braintree/PayPalAccount.php create mode 100644 lib/Braintree/PayPalAccountGateway.php create mode 100644 lib/Braintree/PaymentInstrumentType.php create mode 100644 lib/Braintree/PaymentMethod.php create mode 100644 lib/Braintree/PaymentMethodGateway.php create mode 100644 lib/Braintree/PaymentMethodNonce.php create mode 100644 lib/Braintree/PaymentMethodNonceGateway.php create mode 100644 lib/Braintree/PlanGateway.php create mode 100644 lib/Braintree/RiskData.php create mode 100644 lib/Braintree/SettlementBatchSummaryGateway.php create mode 100644 lib/Braintree/SignatureService.php create mode 100644 lib/Braintree/Subscription/StatusDetails.php create mode 100644 lib/Braintree/SubscriptionGateway.php create mode 100644 lib/Braintree/Test/Nonces.php create mode 100644 lib/Braintree/Test/Transaction.php create mode 100644 lib/Braintree/TestingGateway.php create mode 100644 lib/Braintree/ThreeDSecureInfo.php create mode 100644 lib/Braintree/Transaction/AmexExpressCheckoutCardDetails.php create mode 100644 lib/Braintree/Transaction/AndroidPayCardDetails.php create mode 100644 lib/Braintree/Transaction/ApplePayCardDetails.php create mode 100644 lib/Braintree/Transaction/CoinbaseDetails.php create mode 100644 lib/Braintree/Transaction/EuropeBankAccountDetails.php create mode 100644 lib/Braintree/Transaction/PayPalDetails.php create mode 100644 lib/Braintree/Transaction/VenmoAccountDetails.php create mode 100644 lib/Braintree/TransactionGateway.php create mode 100644 lib/Braintree/TransparentRedirectGateway.php create mode 100644 lib/Braintree/UnknownPaymentMethod.php create mode 100644 lib/Braintree/VenmoAccount.php create mode 100644 lib/autoload.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2acce55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Created by .ignore support plugin (hsz.mobi) +.idea/ diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0_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('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'); +require_once(__DIR__ . DIRECTORY_SEPARATOR . 'autoload.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'); + $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.'); diff --git a/lib/Braintree/AccountUpdaterDailyReport.php b/lib/Braintree/AccountUpdaterDailyReport.php new file mode 100644 index 0000000..802983b --- /dev/null +++ b/lib/Braintree/AccountUpdaterDailyReport.php @@ -0,0 +1,45 @@ +_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/AddOn.php b/lib/Braintree/AddOn.php index 98a4c47..04c1705 100644 --- a/lib/Braintree/AddOn.php +++ b/lib/Braintree/AddOn.php @@ -1,22 +1,29 @@ $response['addOns']); - - return Braintree_Util::extractAttributeAsArray( - $addOns, - 'addOn' - ); - } +namespace Braintree; +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..9373132 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 +61,7 @@ public function __toString() * @ignore * @access protected * @param array $addressAttribs array of address data - * @return none + * @return void */ protected function _initialize($addressAttribs) { @@ -236,117 +70,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..504ce57 --- /dev/null +++ b/lib/Braintree/AddressGateway.php @@ -0,0 +1,310 @@ +_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']); + return $this->_doCreate( + '/customers/' . $customerId . '/addresses', + ['address' => $attribs] + ); + } + + /** + * 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..a17d938 --- /dev/null +++ b/lib/Braintree/AmexExpressCheckoutCard.php @@ -0,0 +1,81 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @property-read string $createdAt + * @property-read string $default + * @property-read string $updatedAt + * @property-read string $customerId + * @property-read string $cardType + * @property-read string $bin + * @property-read string $cardMemberExpiryDate + * @property-read string $cardMemberNumber + * @property-read string $cardType + * @property-read string $sourceDescription + * @property-read string $token + * @property-read string $imageUrl + * @property-read string $expirationMonth + * @property-read string $expirationYear + */ +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..22879cd --- /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 + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @property-read string $bin + * @property-read string $cardType + * @property-read string $createdAt + * @property-read string $customerId + * @property-read string $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 string $token + * @property-read string $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..c7b1dea --- /dev/null +++ b/lib/Braintree/ApplePayCard.php @@ -0,0 +1,100 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @property-read string $cardType + * @property-read string $createdAt + * @property-read string $customerId + * @property-read string $expirationDate + * @property-read string $expirationMonth + * @property-read string $expirationYear + * @property-read string $imageUrl + * @property-read string $last4 + * @property-read string $token + * @property-read string $paymentInstrumentName + * @property-read string $sourceDescription + * @property-read string $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/Base.php b/lib/Braintree/Base.php new file mode 100644 index 0000000..1d661fb --- /dev/null +++ b/lib/Braintree/Base.php @@ -0,0 +1,77 @@ +_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; + } +} diff --git a/lib/Braintree/ClientToken.php b/lib/Braintree/ClientToken.php new file mode 100644 index 0000000..269bd98 --- /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..cf72b28 --- /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 mixed + */ + 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", "sepaMandateType", "sepaMandateAcceptanceLocation"]; + } + + /** + * + * @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..32fb583 --- /dev/null +++ b/lib/Braintree/CoinbaseAccount.php @@ -0,0 +1,112 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @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..e44b877 100644 --- a/lib/Braintree/Collection.php +++ b/lib/Braintree/Collection.php @@ -1,30 +1,32 @@ '', - '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 (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 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); + } - if (!isset(self::$_cache[$key])) { - throw new Braintree_Exception_Configuration($key . - ' is not a valid configuration setting.'); + /** + * 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)) { + return self::$global->getProxyPort(); } + self::$global->setProxyPort($value); + } + /** + * 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)) { - throw new InvalidArgumentException($key . ' cannot be empty.'); + return self::$global->getProxyType(); } + self::$global->setProxyType($value); + } - return true; + /** + * 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() + { + $proxyHost = self::$global->getProxyHost(); + $proxyPort = self::$global->getProxyPort(); + return !empty($proxyHost) && !empty($proxyPort); } - private static function set($key, $value) + public static function assertGlobalHasAccessTokenOrKeys() { - // this method will raise an exception on invalid data - self::validate($key, $value); - // set the value in the cache - self::$_cache[$key] = $value; + 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.'); + } + } + } + public function assertHasClientCredentials() + { + $this->assertHasClientId(); + $this->assertHasClientSecret(); } - private static function get($key) + public function assertHasClientId() { - // 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($this->_clientId)) { + throw new Exception\Configuration('clientId needs to be passed to Braintree\\Gateway.'); } + } - if (array_key_exists($key, self::$_cache)) { - return self::$_cache[$key]; + public function assertHasClientSecret() + { + if (empty($this->_clientSecret)) { + throw new Exception\Configuration('clientSecret needs to be passed to Braintree\\Gateway.'); } + } - // return null by default to prevent __set from overloading - return null; + public function getEnvironment() + { + return $this->_environment; } + /** + * Do not use this method directly. Pass in the environment to the constructor. + */ + public function setEnvironment($value) + { + $this->_environment = $value; + } - private static function setOrGet($name, $value = null) + public function getMerchantId() { - if (!empty($value) && is_array($value)) { - $value = $value[0]; - } - if (!empty($value)) { - self::set($name, $value); - } else { - return self::get($name); - } - return true; + return $this->_merchantId; } - /**#@+ - * 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 + + /** + * Do not use this method directly. Pass in the merchantId to the constructor. */ - public static function environment($value = null) + public function setMerchantId($value) { - return self::setOrGet(__FUNCTION__, $value); + $this->_merchantId = $value; } - public static function merchantId($value = null) + public function getPublicKey() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_publicKey; } - public static function publicKey($value = null) + public function getClientId() + { + return $this->_clientId; + } + + /** + * Do not use this method directly. Pass in the publicKey to the constructor. + */ + public function setPublicKey($value) { - return self::setOrGet(__FUNCTION__, $value); + $this->_publicKey = $value; } - public static function privateKey($value = null) + public function getPrivateKey() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_privateKey; + } + + public function getClientSecret() + { + return $this->_clientSecret; } - /**#@-*/ /** - * 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 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 self::baseUrl() . - self::merchantPath(); + return $this->_proxyPort; } + private function setProxyType($value) + { + $this->_proxyType = $value; + } + + public function getProxyType() + { + return $this->_proxyType; + } + + private function setTimeout($value) + { + $this->_timeout = $value; + } + + public function getTimeout() + { + return $this->_timeout; + } + + 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()); } /** * 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,13 +385,12 @@ 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; @@ -256,36 +400,35 @@ public static function portNumber() * 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 +437,40 @@ public static function serverName() return $serverName; } + 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 +491,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/CredentialsParser.php b/lib/Braintree/CredentialsParser.php new file mode 100644 index 0000000..d61e1ae --- /dev/null +++ b/lib/Braintree/CredentialsParser.php @@ -0,0 +1,148 @@ + $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..9adba03 100644 --- a/lib/Braintree/CreditCard.php +++ b/lib/Braintree/CreditCard.php @@ -1,13 +1,8 @@ == More information == @@ -17,7 +12,7 @@ * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions + * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $billingAddress * @property-read string $bin @@ -34,7 +29,7 @@ * @property-read string $token * @property-read string $updatedAt */ -class Braintree_CreditCard extends Braintree +class CreditCard extends Base { // Card Type const AMEX = 'American Express'; @@ -51,7 +46,7 @@ class Braintree_CreditCard extends Braintree const VISA = 'Visa'; const UNKNOWN = 'Unknown'; - // Credit card origination location + // Credit card origination location const INTERNATIONAL = "international"; const US = "us"; @@ -82,522 +77,239 @@ class Braintree_CreditCard extends Braintree const COUNTRY_OF_ISSUANCE_UNKNOWN = "Unknown"; const ISSUING_BANK_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 a Braintree_Transaction object on success + * returns false if comparing object is not a CreditCard, + * or is a CreditCard with a different id * - * @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' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; } - /** - * 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) + + // static methods redirecting to gateway + + public static function create($attribs) { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - self::_validateId($token); - return self::_doUpdate('put', '/payment_methods/' . $token, array('creditCard' => $attributes)); + return Configuration::gateway()->creditCard()->create($attribs); } - /** - * 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) + public static function createNoValidate($attribs) { - $result = self::update($token, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); + return Configuration::gateway()->creditCard()->createNoValidate($attribs); } - /** - * - * @access public - * @param none - * @return string - */ - public static function updateCreditCardUrl() + + public static function createFromTransparentRedirect($queryString) { - 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()->createFromTransparentRedirect($queryString); } - /** - * update a customer from a TransparentRedirect operation - * - * @access public - * @param array $attribs - * @return object - */ - public static function updateFromTransparentRedirect($queryString) + public static function createCreditCardUrl() { - 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()->createCreditCardUrl(); } - /* instance methods */ - /** - * returns false if default is null or false - * - * @return boolean - */ - public function isDefault() + public static function expired() { - return $this->default; + return Configuration::gateway()->creditCard()->expired(); } - /** - * checks whether the card is expired based on the current date - * - * @return boolean - */ - public function isExpired() + public static function fetchExpired($ids) { - return $this->expired; + return Configuration::gateway()->creditCard()->fetchExpired($ids); } - /** - * checks whether the card is associated with venmo sdk - * - * @return boolean - */ - public function isVenmoSdk() + public static function expiringBetween($startDate, $endDate) { - return $this->venmoSdk; + return Configuration::gateway()->creditCard()->expiringBetween($startDate, $endDate); } - public static function delete($token) + public static function fetchExpiring($startDate, $endDate, $ids) { - self::_validateId($token); - Braintree_Http::delete('/payment_methods/' . $token); - return new Braintree_Result_Successful(); + return Configuration::gateway()->creditCard()->fetchExpiring($startDate, $endDate, $ids); } - /** - * 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 find($token) { - // 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()->find($token); } - /** - * 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 fromNonce($nonce) { - return !($otherCreditCard instanceof Braintree_CreditCard) ? false : $this->token === $otherCreditCard->token; + return Configuration::gateway()->creditCard()->fromNonce($nonce); } - private static function baseOptions() + public static function credit($token, $transactionAttribs) { - return array('makeDefault', 'verificationMerchantAccountId', 'verifyCard', 'venmoSdkSession'); + return Configuration::gateway()->creditCard()->credit($token, $transactionAttribs); } - private static function baseSignature($options) + public static function creditNoValidate($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()->creditNoValidate($token, $transactionAttribs); } - public static function createSignature() + public static function sale($token, $transactionAttribs) { - $options = self::baseOptions(); - $options[] = "failOnDuplicatePaymentMethod"; - $signature = self::baseSignature($options); - $signature[] = 'customerId'; - return $signature; + return Configuration::gateway()->creditCard()->sale($token, $transactionAttribs); } - public static function updateSignature() + public static function saleNoValidate($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()->saleNoValidate($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 update($token, $attributes) { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->creditCard()->update($token, $attributes); } - /** - * create a printable representation of the object as: - * ClassName[property=value, property=value] - * @return string - */ - public function __toString() + public static function updateNoValidate($token, $attributes) { - return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + return Configuration::gateway()->creditCard()->updateNoValidate($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 updateCreditCardUrl() { - 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()->updateCreditCardUrl(); } - /** - * 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 updateFromTransparentRedirect($queryString) { - $response = Braintree_Http::$httpVerb($url, $params); - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->creditCard()->updateFromTransparentRedirect($queryString); } - /** - * 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 delete($token) { - 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()->delete($token); } - /** - * 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) + /** @return array */ + public static function allCardTypes() { - $defaultAttributes = array( - 'bin' => '', - 'expirationMonth' => '', - 'expirationYear' => '', - 'last4' => '', - ); - - $instance = new self(); - $instance->_initialize(array_merge($defaultAttributes, $attributes)); - return $instance; + return [ + CreditCard::AMEX, + CreditCard::CARTE_BLANCHE, + CreditCard::CHINA_UNION_PAY, + CreditCard::DINERS_CLUB_INTERNATIONAL, + CreditCard::DISCOVER, + 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..b632797 --- /dev/null +++ b/lib/Braintree/CreditCardGateway.php @@ -0,0 +1,485 @@ +== 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} + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + */ +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() + { + $signature = self::baseSignature(self::baseOptions()); + + $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..ab9075c 100644 --- a/lib/Braintree/CreditCardVerification.php +++ b/lib/Braintree/CreditCardVerification.php @@ -1,5 +1,7 @@ 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 == @@ -16,12 +11,19 @@ * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions + * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read array $addresses + * @property-read array $paymentMethods * @property-read string $company * @property-read string $createdAt * @property-read array $creditCards + * @property-read array $paypalAccounts + * @property-read array $applePayCards + * @property-read array $androidPayCards + * @property-read array $amexExpressCheckoutCards + * @property-read array $venmoAccounts + * @property-read array $coinbaseAccounts * @property-read array $customFields custom fields passed with the request * @property-read string $email * @property-read string $fax @@ -32,359 +34,188 @@ * @property-read string $updatedAt * @property-read string $website */ -class Braintree_Customer extends Braintree +class Customer extends Base { - 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); - } - - 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' - ); - } - /** - * 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 Customer[] */ - public static function create($attribs = array()) + public static function all() { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/customers', array('customer' => $attribs)); + return Configuration::gateway()->customer()->all(); } /** - * 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 + * @param string $query + * @param int[] $ids + * @return Customer|Customer[] */ - public static function createNoValidate($attribs = array()) + public static function fetch($query, $ids) { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); + return Configuration::gateway()->customer()->fetch($query, $ids); } + /** - * create a customer from a TransparentRedirect operation * - * @access public * @param array $attribs - * @return object + * @return Result\Successful|Result\Error */ - public static function createFromTransparentRedirect($queryString) + public static function create($attribs = []) { - 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()->create($attribs); } /** * - * @access public - * @param none - * @return string + * @param array $attribs + * @return Customer */ - public static function createCustomerUrl() + public static function createNoValidate($attribs = []) { - 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()->createNoValidate($attribs); } - /** - * creates a full array signature of a valid create request - * @return array gateway create request format + * @deprecated since version 2.3.0 + * @param string $queryString + * @return Result\Successful */ - public static function createSignature() + public static function createFromTransparentRedirect($queryString) { - - $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; + return Configuration::gateway()->customer()->createFromTransparentRedirect($queryString); } /** - * creates a full array signature of a valid update request - * @return array update request format + * @deprecated since version 2.3.0 + * @return string */ - public static function updateSignature() + public static function createCustomerUrl() { - $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; + return Configuration::gateway()->customer()->createCustomerUrl(); } - /** - * 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) { - 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); } /** - * 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 +226,85 @@ 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', $creditCardArray); + + $coinbaseAccountArray = []; + if (isset($customerAttribs['coinbaseAccounts'])) { + foreach ($customerAttribs['coinbaseAccounts'] AS $coinbaseAccount) { + $coinbaseAccountArray[] = CoinbaseAccount::factory($coinbaseAccount); } } - $this->_set('creditCards', $ccArray); + $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); + + $this->_set('paymentMethods', array_merge( + $this->creditCards, + $this->paypalAccounts, + $this->applePayCards, + $this->coinbaseAccounts, + $this->androidPayCards, + $this->amexExpressCheckoutCards, + $this->venmoAccounts + )); } /** @@ -430,19 +314,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 +363,7 @@ public function isEqual($otherCust) * @access protected * @var array registry of customer data */ - protected $_attributes = array( + protected $_attributes = [ 'addresses' => '', 'company' => '', 'creditCards' => '', @@ -464,101 +376,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..61a6ce0 --- /dev/null +++ b/lib/Braintree/CustomerGateway.php @@ -0,0 +1,626 @@ +== More information == + * + * For more detailed information on Customers, see {@link http://www.braintreepayments.com/gateway/customer-api http://www.braintreepaymentsolutions.com/gateway/customer-api} + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + */ +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 Braintree_Result_Successful|Braintree_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' => + ['customer_browser', 'customer_ip'] + ], + ['creditCard' => $creditCardSignature], + ['customFields' => ['_anyKey_']], + ]; + 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_']], + ]; + return $signature; + } + + + /** + * find a customer by id + * + * @access public + * @param string id customer Id + * @return Customer|boolean The customer object or false if the request fails. + * @throws Exception\NotFound + */ + public function find($id) + { + $this->_validateId($id); + try { + $path = $this->_config->merchantPath() . '/customers/' . $id; + $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 http://www.braintreepayments.com/gateway/customer-api#searching http://www.braintreepaymentsolutions.com/gateway/customer-api} + * + * @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 +35,18 @@ public static function factory($attributes) public function __toString() { - $display = array( + $display = [ 'id', 'merchantAccountDetails', 'exceptionMessage', 'amount', 'disbursementDate', 'followUpAction', 'retry', 'success', 'transactionIds' - ); + ]; - $displayAttributes = array(); + $displayAttributes = []; foreach ($display AS $attrib) { $displayAttributes[$attrib] = $this->$attrib; } return __CLASS__ . '[' . - Braintree_Util::attributesToString($displayAttributes) .']'; + Util::attributesToString($displayAttributes) .']'; } } +class_alias('Braintree\Disbursement', 'Braintree_Disbursement'); diff --git a/lib/Braintree/DisbursementDetails.php b/lib/Braintree/DisbursementDetails.php index 471a163..7eb5928 100644 --- a/lib/Braintree/DisbursementDetails.php +++ b/lib/Braintree/DisbursementDetails.php @@ -1,17 +1,13 @@ disbursementDate); } } +class_alias('Braintree\DisbursementDetails', 'Braintree_DisbursementDetails'); diff --git a/lib/Braintree/Discount.php b/lib/Braintree/Discount.php index 0c8e12e..da780d0 100644 --- a/lib/Braintree/Discount.php +++ b/lib/Braintree/Discount.php @@ -1,22 +1,21 @@ $response['discounts']); - - return Braintree_Util::extractAttributeAsArray( - $discounts, - 'discount' - ); - } +namespace Braintree; +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..b05b116 --- /dev/null +++ b/lib/Braintree/Dispute.php @@ -0,0 +1,82 @@ +_attributes = $disputeAttribs; + + if (isset($disputeAttribs['transaction'])) { + $this->_set('transactionDetails', + 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) .']'; + } +} +class_alias('Braintree\Dispute', 'Braintree_Dispute'); diff --git a/lib/Braintree/Dispute/TransactionDetails.php b/lib/Braintree/Dispute/TransactionDetails.php new file mode 100644 index 0000000..f0240ce --- /dev/null +++ b/lib/Braintree/Dispute/TransactionDetails.php @@ -0,0 +1,27 @@ +_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 +82,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 +93,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 +121,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..7442f0c 100644 --- a/lib/Braintree/Error/Validation.php +++ b/lib/Braintree/Error/Validation.php @@ -1,11 +1,7 @@ $value) { $varName = "_$name"; - $this->$varName = Braintree_Util::delimiterToCamelCase($value, '_'); + $this->$varName = Util::delimiterToCamelCase($value, '_'); } } @@ -62,3 +58,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..91de9aa 100644 --- a/lib/Braintree/Error/ValidationErrorCollection.php +++ b/lib/Braintree/Error/ValidationErrorCollection.php @@ -1,11 +1,7 @@ _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 +71,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 +101,7 @@ public function __get($name) */ public function __toString() { - $output = array(); + $output = []; // TODO: implement scope if (!empty($this->_errors)) { @@ -133,3 +129,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..cae9354 --- /dev/null +++ b/lib/Braintree/EuropeBankAccount.php @@ -0,0 +1,68 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @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..c3ad854 100644 --- a/lib/Braintree/Exception.php +++ b/lib/Braintree/Exception.php @@ -1,20 +1,14 @@ _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\FacilitatorDetails', 'Braintree_FacilitatorDetails'); diff --git a/lib/Braintree/Gateway.php b/lib/Braintree/Gateway.php new file mode 100644 index 0000000..4f8c9f5 --- /dev/null +++ b/lib/Braintree/Gateway.php @@ -0,0 +1,198 @@ +config = $config; + } + + /** + * + * @return AddOnGateway + */ + public function addOn() + { + return new AddOnGateway($this); + } + + /** + * + * @return AddressGateway + */ + public function address() + { + return new AddressGateway($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 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 TransparentRedirectGateway + */ + public function transparentRedirect() + { + return new TransparentRedirectGateway($this); + } +} +class_alias('Braintree\Gateway', 'Braintree_Gateway'); diff --git a/lib/Braintree/Http.php b/lib/Braintree/Http.php index fb05985..7936b65 100644 --- a/lib/Braintree/Http.php +++ b/lib/Braintree/Http.php @@ -1,104 +1,188 @@ _config = $config; + } + + public function delete($path) + { + $response = $this->_doRequest('DELETE', $path); if($response['status'] === 200) { return true; } else { - Braintree_Util::throwStatusCodeException($response['status']); + Util::throwStatusCodeException($response['status']); } } - public static function get($path) + public function get($path) { - $response = self::_doRequest('GET', $path); - if($response['status'] === 200) { - return Braintree_Xml::buildArrayFromXml($response['body']); + $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 post($path, $params = null) + public function post($path, $params = null) { - $response = self::_doRequest('POST', $path, self::_buildXml($params)); + $response = $this->_doRequest('POST', $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); } } - 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 { + Util::throwStatusCodeException($responseCode); + } + } + + private function _buildXml($params) + { + return empty($params) ? null : Xml::buildXmlFromArray($params); + } + + private function _getHeaders() + { + return [ + 'Accept: application/xml', + 'Content-Type: application/xml', + ]; + } + + 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 { - Braintree_Util::throwStatusCodeException($responseCode); + return [ + 'user' => $this->_config->getPublicKey(), + 'password' => $this->_config->getPrivateKey(), + ]; } } - private static function _buildXml($params) + public function useClientCredentials() { - return empty($params) ? null : Braintree_Xml::buildXmlFromArray($params); + $this->_useClientCredentials = true; } - private static function _doRequest($httpVerb, $path, $requestBody = null) + private function _doRequest($httpVerb, $path, $requestBody = null) { - return self::_doUrlRequest($httpVerb, Braintree_Configuration::merchantUrl() . $path, $requestBody); + return $this->_doUrlRequest($httpVerb, $this->_config->baseUrl() . $path, $requestBody); } - public static function _doUrlRequest($httpVerb, $url, $requestBody = null) + public function _doUrlRequest($httpVerb, $url, $requestBody = 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()); + + $headers = $this->_getHeaders($curl); + $headers[] = 'User-Agent: Braintree PHP Library ' . Version::get(); + $headers[] = 'X-ApiVersion: ' . Configuration::API_VERSION; + + $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']; + } + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + // curl_setopt($curl, CURLOPT_VERBOSE, true); - if (Braintree_Configuration::sslOn()) { + 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)) { curl_setopt($curl, CURLOPT_POSTFIELDS, $requestBody); } + if($this->_config->isUsingProxy()) { + $proxyHost = $this->_config->getProxyHost(); + $proxyPort = $this->_config->getProxyPort(); + $proxyType = $this->_config->getProxyType(); + curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort); + if(!empty($proxyType)) { + curl_setopt($curl, CURLOPT_PROXYTYPE, $proxyType); + } + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($curl); $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $error_code = curl_errno($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(); } } - return array('status' => $httpStatus, 'body' => $response); + return ['status' => $httpStatus, 'body' => $response]; + } + + 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 $memo; } } +class_alias('Braintree\Http', 'Braintree_Http'); diff --git a/lib/Braintree/Instance.php b/lib/Braintree/Instance.php index c048254..2f8e46e 100644 --- a/lib/Braintree/Instance.php +++ b/lib/Braintree/Instance.php @@ -1,25 +1,19 @@ _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 +72,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/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..5bde533 100644 --- a/lib/Braintree/MerchantAccount.php +++ b/lib/Braintree/MerchantAccount.php @@ -1,6 +1,7 @@ $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 +24,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..891edb7 --- /dev/null +++ b/lib/Braintree/MerchantAccountGateway.php @@ -0,0 +1,154 @@ +_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 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['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/OAuthCredentials.php b/lib/Braintree/OAuthCredentials.php new file mode 100644 index 0000000..d2f35f2 --- /dev/null +++ b/lib/Braintree/OAuthCredentials.php @@ -0,0 +1,36 @@ +_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..9cf892f --- /dev/null +++ b/lib/Braintree/OAuthGateway.php @@ -0,0 +1,123 @@ +_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)); + $url = $this->_config->baseUrl() . '/oauth/connect?' . $queryString; + + return $url . '&signature=' . $this->computeSignature($url) . '&algorithm=SHA256'; + } + + 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..5a7de30 --- /dev/null +++ b/lib/Braintree/OAuthResult.php @@ -0,0 +1,36 @@ +_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/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..c0ee7d0 100644 --- a/lib/Braintree/PartnerMerchant.php +++ b/lib/Braintree/PartnerMerchant.php @@ -1,29 +1,24 @@ _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..7f09581 --- /dev/null +++ b/lib/Braintree/PayPalAccount.php @@ -0,0 +1,111 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @property-read string $customerId + * @property-read string $email + * @property-read string $token + * @property-read string $imageUrl + */ +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..1b0933e --- /dev/null +++ b/lib/Braintree/PayPalAccountGateway.php @@ -0,0 +1,180 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + */ +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..9be065f --- /dev/null +++ b/lib/Braintree/PaymentInstrumentType.php @@ -0,0 +1,13 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + */ +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) + { + return Configuration::gateway()->paymentMethod()->delete($token); + } +} +class_alias('Braintree\PaymentMethod', 'Braintree_PaymentMethod'); diff --git a/lib/Braintree/PaymentMethodGateway.php b/lib/Braintree/PaymentMethodGateway.php new file mode 100644 index 0000000..630d691 --- /dev/null +++ b/lib/Braintree/PaymentMethodGateway.php @@ -0,0 +1,300 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + */ +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); + 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['venmoAccount'])) { + return VenmoAccount::factory($response['venmoAccount']); + } else if (is_array($response)) { + return UnknownPaymentMethod::factory($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) + { + $this->_validateId($token); + $path = $this->_config->merchantPath() . '/payment_methods/any/' . $token; + $this->_http->delete($path); + return new Result\Successful(); + } + + public function grant($sharedPaymentMethodToken, $allowVaulting) + { + return $this->_doCreate( + '/payment_methods/grant', + [ + 'payment_method' => [ + 'shared_payment_method_token' => $sharedPaymentMethodToken, + 'allow_vaulting' => $allowVaulting + ] + ] + ); + } + + public function revoke($sharedPaymentMethodToken) + { + return $this->_doCreate( + '/payment_methods/revoke', + [ + 'payment_method' => [ + 'shared_payment_method_token' => $sharedPaymentMethodToken + ] + ] + ); + } + + private static function baseSignature() + { + $billingAddressSignature = AddressGateway::createSignature(); + $optionsSignature = [ + 'failOnDuplicatePaymentMethod', + 'makeDefault', + 'verificationMerchantAccountId', + 'verifyCard', + 'verificationAmount' + ]; + 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']); + 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; + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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['creditCard'])) { + return new Result\Successful( + CreditCard::factory($response['creditCard']), + 'paymentMethod' + ); + } else if (isset($response['paypalAccount'])) { + return new Result\Successful( + PayPalAccount::factory($response['paypalAccount']), + "paymentMethod" + ); + } else if (isset($response['coinbaseAccount'])) { + return new Result\Successful( + CoinbaseAccount::factory($response['coinbaseAccount']), + "paymentMethod" + ); + } else if (isset($response['applePayCard'])) { + return new Result\Successful( + ApplePayCard::factory($response['applePayCard']), + "paymentMethod" + ); + } else if (isset($response['androidPayCard'])) { + return new Result\Successful( + AndroidPayCard::factory($response['androidPayCard']), + "paymentMethod" + ); + } else if (isset($response['amexExpressCheckoutCard'])) { + return new Result\Successful( + AmexExpressCheckoutCard::factory($response['amexExpressCheckoutCard']), + "paymentMethod" + ); + } else if (isset($response['europeBankAccount'])) { + return new Result\Successful( + EuropeBankAccount::factory($response['europeBankAccount']), + "paymentMethod" + ); + } else if (isset($response['venmoAccount'])) { + return new Result\Successful( + VenmoAccount::factory($response['venmoAccount']), + "paymentMethod" + ); + } else if (isset($response['paymentMethodNonce'])) { + return new Result\Successful( + PaymentMethodNonce::factory($response['paymentMethodNonce']), + "paymentMethodNonce" + ); + } else if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else if (is_array($response)) { + return new Result\Successful( + UnknownPaymentMethod::factory($response), + "paymentMethod" + ); + } else { + throw new Exception\Unexpected( + 'Expected payment method 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..4e04efb --- /dev/null +++ b/lib/Braintree/PaymentMethodNonce.php @@ -0,0 +1,55 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + */ +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'])); + } + } +} +class_alias('Braintree\PaymentMethodNonce', 'Braintree_PaymentMethodNonce'); diff --git a/lib/Braintree/PaymentMethodNonceGateway.php b/lib/Braintree/PaymentMethodNonceGateway.php new file mode 100644 index 0000000..82dcfae --- /dev/null +++ b/lib/Braintree/PaymentMethodNonceGateway.php @@ -0,0 +1,67 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + */ +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/Plan.php b/lib/Braintree/Plan.php index b0fb3d0..a92c604 100644 --- a/lib/Braintree/Plan.php +++ b/lib/Braintree/Plan.php @@ -1,21 +1,8 @@ $response['plans']); - } else { - $plans = array("plan" => array()); - } - - return Braintree_Util::extractAttributeAsArray( - $plans, - 'plan' - ); - } +namespace Braintree; +class Plan extends Base +{ public static function factory($attributes) { $instance = new self(); @@ -28,28 +15,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/RangeNode.php b/lib/Braintree/RangeNode.php index fdc4307..622939a 100644 --- a/lib/Braintree/RangeNode.php +++ b/lib/Braintree/RangeNode.php @@ -1,38 +1,40 @@ 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..f1ab475 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,13 @@ * * @package Braintree * @subpackage Utility - * @copyright 2010 Braintree Payment Solutions + * @copyright 2015 Braintree, a division of PayPal, Inc. */ -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 +36,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 +62,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 +80,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 +113,7 @@ private function _getNextPage() { if (empty($this->_ids)) { - $this->_items = array(); + $this->_items = []; } else { @@ -128,21 +126,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..6a0eb16 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 +83,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..dd114e7 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 + * @copyright 2015 Braintree, a division of PayPal, Inc. * * @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 +54,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 +73,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..fe95835 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 + * @copyright 2015 Braintree, a division of PayPal, Inc. */ -class Braintree_Result_Successful extends Braintree_Instance +class Successful extends Instance { /** * @@ -42,28 +39,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 +73,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, $this->$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/RiskData.php b/lib/Braintree/RiskData.php new file mode 100644 index 0000000..971d805 --- /dev/null +++ b/lib/Braintree/RiskData.php @@ -0,0 +1,30 @@ +_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/SettlementBatchSummary.php b/lib/Braintree/SettlementBatchSummary.php index 98591a7..3a270a8 100644 --- a/lib/Braintree/SettlementBatchSummary.php +++ b/lib/Braintree/SettlementBatchSummary.php @@ -1,57 +1,13 @@ $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; +class SettlementBatchSummary extends Base +{ + /** + * + * @param array $attributes + * @return SettlementBatchSummary + */ public static function factory($attributes) { $instance = new self(); @@ -61,6 +17,7 @@ public static function factory($attributes) /** * @ignore + * @param array $attributes */ protected function _initialize($attributes) { @@ -71,4 +28,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..1b7eb94 100644 --- a/lib/Braintree/Subscription.php +++ b/lib/Braintree/Subscription.php @@ -1,4 +1,6 @@ $attributes)); - return self::_verifyGatewayResponse($response); - } + // Subscription Sources + const API = 'api'; + const CONTROL_PANEL = 'control_panel'; + const RECURRING = 'recurring'; /** * @ignore @@ -37,138 +37,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 +44,98 @@ 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(); + $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) + { + return Configuration::gateway()->subscription()->retryCharge($subscriptionId, $amount); + } + + 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..fda998a --- /dev/null +++ b/lib/Braintree/Subscription/StatusDetails.php @@ -0,0 +1,23 @@ +== More information == + * + * For more detailed information on Subscriptions, see {@link http://www.braintreepayments.com/gateway/subscription-api http://www.braintreepaymentsolutions.com/gateway/subscription-api} + * + * PHP Version 5 + * + * @package Braintree + * @copyright 2015 Braintree, a division of PayPal, Inc. + */ +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) + { + $transaction_params = ['type' => Transaction::SALE, + 'subscriptionId' => $subscriptionId]; + if (isset($amount)) { + $transaction_params['amount'] = $amount; + } + + $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']], + ], + 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']], + ], + 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..fc30622 100644 --- a/lib/Braintree/SubscriptionSearch.php +++ b/lib/Braintree/SubscriptionSearch.php @@ -1,64 +1,67 @@ '378734493671000', 'Discover' => '6011000990139424', 'MasterCard' => '5105105105105100', 'Visa' => '4000111111111115', - ); + ]; + public static $amexPayWithPoints = [ + 'Success' => "371260714673002", + 'IneligibleCard' => "378267515471109", + 'InsufficientPoints' => "371544868764018", + ]; public static function getAll() { @@ -74,3 +73,4 @@ public static function getAll() ); } } +class_alias('Braintree\Test\CreditCardNumbers', 'Braintree_Test_CreditCardNumbers'); diff --git a/lib/Braintree/Test/MerchantAccount.php b/lib/Braintree/Test/MerchantAccount.php index 85a7774..581eccd 100644 --- a/lib/Braintree/Test/MerchantAccount.php +++ b/lib/Braintree/Test/MerchantAccount.php @@ -1,12 +1,14 @@ 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..8a9e930 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..a04ec39 --- /dev/null +++ b/lib/Braintree/ThreeDSecureInfo.php @@ -0,0 +1,30 @@ +_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..60df695 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', @@ -144,7 +139,7 @@ * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions + * @copyright 2015 Braintree, a division of PayPal, Inc. * * * @property-read string $avsErrorResponseCode @@ -153,22 +148,30 @@ * @property-read string $cvvResponseCode * @property-read string $id transaction id * @property-read string $amount transaction amount - * @property-read object $billingDetails transaction billing address + * @property-read Braintree\Transaction\AddressDetails $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 Braintree\ApplePayCardDetails $applePayCardDetails transaction Apple Pay card info + * @property-read Braintree\AndroidPayCardDetails $androidPayCardDetails transaction Android Pay card info + * @property-read Braintree\AmexExpressCheckoutCardDetails $amexExpressCheckoutCardDetails transaction Amex Express Checkout card info + * @property-read Braintree\CreditCardDetails $creditCardDetails transaction credit card info + * @property-read Braintree\CoinbaseAccountDetails $coinbaseDetails transaction Coinbase account info + * @property-read Braintree\PayPalAccountDetails $paypalDetails transaction paypal account info + * @property-read Braintree\Transaction\CustomerDetails $customerDetails transaction customer info + * @property-read Braintree\VenmoAccount $venmoAccountDetails transaction Venmo Account info * @property-read array $customFields custom fields passed with the request * @property-read string $processorResponseCode gateway response code - * @property-read object $shippingDetails transaction shipping address + * @property-read string $additionalProcessorResponse raw response from processor + * @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 string $type transaction type * @property-read string $updatedAt transaction updated timestamp - * @property-read object $disbursementDetails populated when transaction is disbursed + * @property-read Braintree\Disbursement $disbursementDetails populated when transaction is disbursed + * @property-read Braintree\Dispute $disputes populated when transaction is disputed * */ -final class Braintree_Transaction extends Braintree +class Transaction extends Base { // Transaction Status const AUTHORIZATION_EXPIRED = 'authorization_expired'; @@ -182,6 +185,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 +210,97 @@ 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'; /** + * 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['amexExpressCheckoutCard'])) { + $this->_set('amexExpressCheckoutCardDetails', + new Transaction\AmexExpressCheckoutCardDetails( + $transactionAttribs['amexExpressCheckoutCard'] + ) + ); } - } - public static function fetch($query, $ids) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + if (isset($transactionAttribs['venmoAccount'])) { + $this->_set('venmoAccountDetails', + new Transaction\VenmoAccountDetails( + $transactionAttribs['venmoAccount'] + ) + ); } - $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); - } - - public static function releaseFromEscrow($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/release_from_escrow', - array() - ); - return self::_verifyGatewayResponse($response); - } - - public static function cancelRelease($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/cancel_release', - array() - ); - return self::_verifyGatewayResponse($response); - } - - - /** - * 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['creditCard'])) { $this->_set('creditCardDetails', - new Braintree_Transaction_CreditCardDetails( + new Transaction\CreditCardDetails( $transactionAttribs['creditCard'] ) ); } + if (isset($transactionAttribs['coinbaseAccount'])) { + $this->_set('coinbaseDetails', + new Transaction\CoinbaseDetails( + $transactionAttribs['coinbaseAccount'] + ) + ); + } + + if (isset($transactionAttribs['europeBankAccount'])) { + $this->_set('europeBankAccount', + new Transaction\EuropeBankAccountDetails( + $transactionAttribs['europeBankAccount'] + ) + ); + } + + 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 +308,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['billing'])) { $this->_set('billingDetails', - new Braintree_Transaction_AddressDetails( + new Transaction\AddressDetails( $transactionAttribs['billing'] ) ); @@ -548,7 +316,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['shipping'])) { $this->_set('shippingDetails', - new Braintree_Transaction_AddressDetails( + new Transaction\AddressDetails( $transactionAttribs['shipping'] ) ); @@ -556,7 +324,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['subscription'])) { $this->_set('subscriptionDetails', - new Braintree_Transaction_SubscriptionDetails( + new Transaction\SubscriptionDetails( $transactionAttribs['subscription'] ) ); @@ -564,7 +332,7 @@ protected function _initialize($transactionAttribs) if (isset($transactionAttribs['descriptor'])) { $this->_set('descriptor', - new Braintree_Descriptor( + new Descriptor( $transactionAttribs['descriptor'] ) ); @@ -572,34 +340,53 @@ 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); + + 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['facilitatorDetails'])) { + $this->_set('facilitatorDetails', FacilitatorDetails::factory($transactionAttribs['facilitatorDetails'])); + } } /** @@ -609,24 +396,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 +421,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 +433,130 @@ public function vaultCustomer() return null; } else { - return Braintree_Customer::find($customerId); + return Customer::find($customerId); } } + /** @return bool */ public function isDisbursed() { return $this->disbursementDetails->isValid(); } /** - * 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..d925721 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..e9ce5d4 --- /dev/null +++ b/lib/Braintree/Transaction/ApplePayCardDetails.php @@ -0,0 +1,41 @@ +== More information == + * + * For more detailed information on Transactions, see {@link http://www.braintreepayments.com/gateway/transaction-api http://www.braintreepaymentsolutions.com/gateway/transaction-api} + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + */ + +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 object + */ + 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', + 'sharedCustomerId', + 'sharedShippingAddressId', + 'sharedBillingAddressId', + 'shippingAddressId', + 'taxAmount', + 'taxExempt', + 'threeDSecureToken', + 'transactionSource', + 'type', + 'venmoSdkPaymentMethodCode', + ['riskData' => + ['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', + 'payeeEmail', + ['threeDSecure' => + ['required'] + ], + ['paypal' => + [ + 'payeeEmail', + 'customField', + 'description', + ['supplementaryData' => ['_anyKey_']], + ] + ], + ['amexRewards' => + [ + 'requestId', + 'points', + 'currencyAmount', + 'currencyIsoCode' + ] + ] + ], + ], + ['customFields' => ['_anyKey_']], + ['descriptor' => ['name', 'phone', 'url']], + ['paypalAccount' => ['payeeEmail']], + ['apple_pay_card' => ['number', 'cardholder_name', 'cryptogram', 'expiration_month', 'expiration_year']], + ['industry' => + ['industryType', + ['data' => + [ + 'folioNumber', + 'checkInDate', + 'checkOutDate', + 'travelPackage', + 'departureDate', + 'lodgingCheckInDate', + 'lodgingCheckOutDate', + 'lodgingName', + 'roomRate' + ] + ] + ] + ] + ]; + } + + 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 array + */ + 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 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 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]); + + return Util::extractattributeasarray( + $response['creditCardTransactions'], + 'transaction' + ); + } + + /** + * 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 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 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/TransactionSearch.php b/lib/Braintree/TransactionSearch.php index 487d781..1e85b2e 100644 --- a/lib/Braintree/TransactionSearch.php +++ b/lib/Braintree/TransactionSearch.php @@ -1,125 +1,130 @@ - * $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,9 @@ * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions + * @copyright 2015 Braintree, a division of PayPal, Inc. */ -class Braintree_TransparentRedirect +class TransparentRedirect { // Request Kinds const CREATE_TRANSACTION = 'create_transaction'; @@ -52,276 +45,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..9d4ec09 --- /dev/null +++ b/lib/Braintree/TransparentRedirectGateway.php @@ -0,0 +1,290 @@ +_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..a88a054 --- /dev/null +++ b/lib/Braintree/UnknownPaymentMethod.php @@ -0,0 +1,71 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @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/Util.php b/lib/Braintree/Util.php index b3e7fdd..03f01f2 100644 --- a/lib/Braintree/Util.php +++ b/lib/Braintree/Util.php @@ -1,35 +1,33 @@ success) { + return $resultObj->$resultObjName; + } else { + throw new Exception\ValidationsFailed(); + } + } + + /** + * removes the header from a classname + * + * @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\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', + ]; - $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', + '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,6 +197,7 @@ public static function buildClassName($name) * * @access public * @param string $string + * @param null|string $delimiter * @return string modified string */ public static function delimiterToCamelCase($string, $delimiter = '[\-\_]') @@ -166,20 +230,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; + } - return preg_replace_callback('/([A-Z])/', $callbacks[$delimiter], $string); + 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; + } + + public static function delimiterToUnderscoreArray($array) + { + $converted = []; + foreach ($array as $key => $value) { + $key = self::delimiterToUnderscore($key); + $converted[$key] = $value; + } + return $converted; } /** @@ -187,24 +291,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 +319,7 @@ public static function attributesToString($attributes) { } $printableAttribs[$key] = sprintf('%s', $pAttrib); } - return Braintree_Util::implodeAssociativeArray($printableAttribs); + return self::implodeAssociativeArray($printableAttribs); } /** @@ -234,7 +341,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 +352,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 +371,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 +413,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..acc0b1f --- /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 + * @copyright 2015 Braintree, a division of PayPal, Inc. + * + * @property-read string $createdAt + * @property-read string $default + * @property-read string $updatedAt + * @property-read string $customerId + * @property-read string $sourceDescription + * @property-read string $token + * @property-read string $imageUrl + * @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..b72b08d 100644 --- a/lib/Braintree/Version.php +++ b/lib/Braintree/Version.php @@ -1,23 +1,19 @@ _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['dispute'])) { + $this->_set('dispute', Dispute::factory($wrapperNode['dispute'])); + } + + if (isset($wrapperNode['accountUpdaterDailyReport'])) { + $this->_set('accountUpdaterDailyReport', AccountUpdaterDailyReport::factory($wrapperNode['accountUpdaterDailyReport'])); } 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/WebhookTesting.php b/lib/Braintree/WebhookTesting.php index f8d74da..9cbde0e 100644 --- a/lib/Braintree/WebhookTesting.php +++ b/lib/Braintree/WebhookTesting.php @@ -1,44 +1,64 @@ $signature, - 'payload' => $payload - ); + return [ + 'bt_signature' => $signature, + 'bt_payload' => $payload + ]; } private static function _sampleXml($kind, $id) { switch ($kind) { - case Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED: + case WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED: $subjectXml = self::_merchantAccountApprovedSampleXml($id); break; - case Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED: + case WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED: $subjectXml = self::_merchantAccountDeclinedSampleXml($id); break; - case Braintree_WebhookNotification::TRANSACTION_DISBURSED: + case WebhookNotification::TRANSACTION_DISBURSED: $subjectXml = self::_transactionDisbursedSampleXml($id); break; - case Braintree_WebhookNotification::DISBURSEMENT_EXCEPTION: + case WebhookNotification::DISBURSEMENT_EXCEPTION: $subjectXml = self::_disbursementExceptionSampleXml($id); break; - case Braintree_WebhookNotification::DISBURSEMENT: + case WebhookNotification::DISBURSEMENT: $subjectXml = self::_disbursementSampleXml($id); break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_CONNECTED: + case WebhookNotification::PARTNER_MERCHANT_CONNECTED: $subjectXml = self::_partnerMerchantConnectedSampleXml($id); break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_DISCONNECTED: + case WebhookNotification::PARTNER_MERCHANT_DISCONNECTED: $subjectXml = self::_partnerMerchantDisconnectedSampleXml($id); break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_DECLINED: + case WebhookNotification::PARTNER_MERCHANT_DECLINED: $subjectXml = self::_partnerMerchantDeclinedSampleXml($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::CHECK: + $subjectXml = self::_checkSampleXml(); + break; + case WebhookNotification::ACCOUNT_UPDATER_DAILY_REPORT: + $subjectXml = self::_accountUpdaterDailyReportSampleXml($id); + break; default: $subjectXml = self::_subscriptionSampleXml($id); break; @@ -159,6 +179,70 @@ private static function _disbursementSampleXml($id) "; } + private static function _disputeOpenedSampleXml($id) + { + return " + + 250.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 + USD + 2014-03-01 + 2014-03-21 + chargeback + lost + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + + "; + } + + private static function _disputeWonSampleXml($id) + { + return " + + 250.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 " @@ -174,34 +258,72 @@ private static function _subscriptionSampleXml($id) "; } + private static function _subscriptionChargedSuccessfullySampleXml($id) + { + return " + + {$id} + 2016-03-21 + 2017-03-31 + + + submitted_for_settlement + 49.99 + + + + + + + + "; + } + + private static function _checkSampleXml() + { + return " + true + "; + } + private static function _partnerMerchantConnectedSampleXml($id) { return " - - public_id - public_key - private_key - abc123 - cse_key - + + public_id + public_key + private_key + abc123 + cse_key + "; } private static function _partnerMerchantDisconnectedSampleXml($id) { return " - - abc123 - + + abc123 + "; } private static function _partnerMerchantDeclinedSampleXml($id) { return " - - abc123 - + + abc123 + + "; + } + + private static function _accountUpdaterDailyReportSampleXml($id) + { + return " + + 2016-01-14 + link-to-csv-report + "; } @@ -215,3 +337,4 @@ private static function _timestamp() return $timestamp; } } +class_alias('Braintree\WebhookTesting', 'Braintree_WebhookTesting'); diff --git a/lib/Braintree/Xml.php b/lib/Braintree/Xml.php index a6e5119..a84ed02 100644 --- a/lib/Braintree/Xml.php +++ b/lib/Braintree/Xml.php @@ -1,17 +1,14 @@ openMemory(); @@ -33,7 +41,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 +60,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 +72,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 +110,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 +150,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..61c6295 100644 --- a/lib/Braintree/Xml/Parser.php +++ b/lib/Braintree/Xml/Parser.php @@ -1,179 +1,142 @@ getName()); - $type = $iterator->attributes()->type; + $document = new DOMDocument('1.0', 'UTF-8'); + $document->loadXML($xml); - self::$_xmlRoot = $iterator->getName(); - self::$_responseType = $type; - - // 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 @@ + Date: Mon, 25 Jul 2016 17:30:44 -0600 Subject: [PATCH 02/19] Add gitignore file. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2acce55..b281d66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ # Created by .ignore support plugin (hsz.mobi) .idea/ +.DS_Store From f0f28c1fbba23871b5bffbd54f5f95c61762f2f6 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Mon, 25 Jul 2016 17:34:15 -0600 Subject: [PATCH 03/19] Increase version number and add documentation about subscriptions. --- README.md | 3 ++- gravity-forms-braintree.php | 2 +- readme.txt | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 991f90f..4c7da8e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Braintree Payments is a payment gateway provider owned by eBAY Inc, which allows * Quick and easy setup ## 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/gravity-forms-braintree.php b/gravity-forms-braintree.php index 45534e9..6b61e94 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.2.0 Author URI: http://plugify.io */ diff --git a/readme.txt b/readme.txt index 05daa4d..130f265 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: 4.5 +Stable tag: 1.2.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -61,6 +61,10 @@ No filters are currently available for this pre-release version == Changelog == += 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 From 44236cf16fc1df26569859184e161bc45073da99 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Mon, 25 Jul 2016 17:35:20 -0600 Subject: [PATCH 04/19] Update minimum Gravity Forms version. Update Braintree_Configuration to use new namespacing. Add initial create subscription functionality. --- lib/class.plugify-gform-braintree.php | 144 +++++++++++++++++++++++--- 1 file changed, 132 insertions(+), 12 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 5128597..1b98370 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -6,7 +6,7 @@ final class Plugify_GForm_Braintree extends GFPaymentAddOn { protected $_version = '1.0'; - protected $_min_gravityforms_version = '1.8.7.16'; + protected $_min_gravityforms_version = '2.0.3'; protected $_slug = 'gravity-forms-braintree'; protected $_path = 'gravity-forms-braintree/lib/class.plugify-gform-braintree.php'; protected $_full_path = __FILE__; @@ -107,10 +107,10 @@ public function authorize( $feed, $submission_data, $form, $entry ) { 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 if( $settings['settlement'] == 'Yes' ) { @@ -118,7 +118,7 @@ public function authorize( $feed, $submission_data, $form, $entry ) { } // Send transaction to Braintree - $result = Braintree_Transaction::sale( $args ); + $result = Braintree\Transaction::sale( $args ); // Update response to reflect successful payment if( $result->success == '1' ) { @@ -159,6 +159,132 @@ public function authorize( $feed, $submission_data, $form, $entry ) { } + /** + * 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. + * + * @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) + ]; + + // 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)) { + $result = Braintree\Customer::create($args); + + if (!empty($result)) { + $subscriptionResult = Braintree\Subscription::create([ + 'paymentMethodToken' => $result->customer->creditCards[0]->token, + 'planId' => $thePlan->id + ]); + + if( $subscriptionResult->success == true ) { + + $authorization['is_success'] = true; + $authorization['error_message'] = ''; + $authorization['subscription_id'] = $subscriptionResult->subscription->_attributes['id']; + $authorization['amount'] = $subscriptionResult->subscription->_attributes['price']; + + $authorization['captured_payment'] = [ + 'is_success' => true, + 'subscription_id' => $subscriptionResult->subscription->_attributes['id'], + 'amount' => $subscriptionResult->subscription->_attributes['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' ) ); + } + + return $authorization; + } + + return false; + } + /** * Create and display feed settings fields. * @@ -179,12 +305,6 @@ public function feed_settings_fields () { // 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 ); // Return sanitized settings From f6403ae376e04af733c8a32ae46efda09e016fc2 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Mon, 25 Jul 2016 18:05:19 -0600 Subject: [PATCH 05/19] Update subscription to search for existing customer before creating a new one. --- lib/class.plugify-gform-braintree.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 1b98370..e877678 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -249,11 +249,24 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { } if (!empty($thePlan)) { - $result = Braintree\Customer::create($args); + $collection = Braintree\Customer::search([ + Braintree\CustomerSearch::email()->is($args->email) + ]); + + if ($collection->maximumCount() > 0) { + foreach ($collection as $customer) { + $result = $customer; + } + } else { + $result = Braintree\Customer::create($args); + } if (!empty($result)) { + if (get_class($result) != 'Braintree\Customer') { + $result = $result->customer; + } $subscriptionResult = Braintree\Subscription::create([ - 'paymentMethodToken' => $result->customer->creditCards[0]->token, + 'paymentMethodToken' => $result->creditCards[0]->token, 'planId' => $thePlan->id ]); From 33a1ce29035b627e7eea7de5eaaa0d8e5cb5ae48 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Mon, 25 Jul 2016 23:43:03 -0600 Subject: [PATCH 06/19] Update subscription method to set the price if the submitted price does not match the plan price. --- lib/class.plugify-gform-braintree.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index e877678..1b7f6fc 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -265,10 +265,17 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { if (get_class($result) != 'Braintree\Customer') { $result = $result->customer; } - $subscriptionResult = Braintree\Subscription::create([ + + $subscription = [ 'paymentMethodToken' => $result->creditCards[0]->token, 'planId' => $thePlan->id - ]); + ]; + + 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 ) { @@ -292,17 +299,16 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { $authorization['error_message'] = apply_filters( 'gform_braintree_no_plan_message', __( 'No subscription plan found.', 'gravity-forms-braintree' ) ); } - return $authorization; } - return false; + return $authorization; } /** * Create and display feed settings fields. * * @since 1.0 - * @return void + * @return array */ public function feed_settings_fields () { @@ -329,7 +335,7 @@ public function feed_settings_fields () { * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed * * @since 1.0 - * @return void + * @return array */ public function plugin_settings_fields () { @@ -413,7 +419,7 @@ public function plugin_settings_fields () { * 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 + * @return boolean */ public function settings_are_valid ( $settings ) { @@ -435,7 +441,7 @@ public function settings_are_valid ( $settings ) { * Get plugin settings * * @since 1.0 - * @return void + * @return array|boolean */ public function get_plugin_settings () { From ba8f355a8585dc5c8f6640ac4758acdb1b2acf89 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Tue, 26 Jul 2016 00:57:20 -0600 Subject: [PATCH 07/19] Adjust getting the subscription and initial transacation ids and the price. --- lib/class.plugify-gform-braintree.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 1b7f6fc..110eed3 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -281,13 +281,14 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { $authorization['is_success'] = true; $authorization['error_message'] = ''; - $authorization['subscription_id'] = $subscriptionResult->subscription->_attributes['id']; - $authorization['amount'] = $subscriptionResult->subscription->_attributes['price']; + $authorization['subscription_id'] = $subscriptionResult->subscription->id; + $authorization['amount'] = $subscriptionResult->subscription->price; $authorization['captured_payment'] = [ 'is_success' => true, - 'subscription_id' => $subscriptionResult->subscription->_attributes['id'], - 'amount' => $subscriptionResult->subscription->_attributes['price'], + 'subscription_id' => $subscriptionResult->subscription->id, + 'transaction_id' => $subscriptionResult->subscription->transactions[0]->id, + 'amount' => $subscriptionResult->subscription->price, 'error_message' => '' ]; From 963a435e2eb6306e8af1d26a1804feead1a49f23 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Tue, 26 Jul 2016 09:46:37 -0600 Subject: [PATCH 08/19] Fix bug where it was not finding the customer by email address. --- lib/class.plugify-gform-braintree.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 110eed3..307a0d5 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -250,7 +250,7 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { if (!empty($thePlan)) { $collection = Braintree\Customer::search([ - Braintree\CustomerSearch::email()->is($args->email) + Braintree\CustomerSearch::email()->is($args['email']) ]); if ($collection->maximumCount() > 0) { From a5efcb338c3907cfd62661d4f680d1dd43f8d33c Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Wed, 27 Jul 2016 14:41:20 -0600 Subject: [PATCH 09/19] Wrap the creation of customer and subscription in a try catch. --- lib/class.plugify-gform-braintree.php | 128 +++++++++++++------------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 307a0d5..95bd4ae 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -199,7 +199,7 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { if( $settings = $this->get_plugin_settings() ) { // Sanitize card number, removing dashes and spaces - $card_number = str_replace( array( '-', ' ' ), '', $submission_data['card_number'] ); + $card_number = str_replace(array('-', ' '), '', $submission_data['card_number']); // Prepare Braintree payload $namePieces = explode(' ', $submission_data['card_name']); @@ -213,7 +213,7 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { 'postalCode' => $submission_data['zip'] ], 'number' => $card_number, - 'expirationDate' => sprintf( '%s/%s', $submission_data['card_expiration_date'][0], $submission_data['card_expiration_date'][1]), + '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'] ], @@ -222,84 +222,88 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { 'firstName' => implode(' ', $namePieces) ]; - // 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' ) ); - } + 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']); - if (!empty($thePlan)) { - $collection = Braintree\Customer::search([ - Braintree\CustomerSearch::email()->is($args['email']) - ]); + $plans = Braintree\Plan::all(); - if ($collection->maximumCount() > 0) { - foreach ($collection as $customer) { - $result = $customer; + // 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 { - $result = Braintree\Customer::create($args); + $authorization['error_message'] = apply_filters('gform_braintree_no_plans_failure_message', __('No subscription plans are available.', 'gravity-forms-braintree')); } - if (!empty($result)) { - if (get_class($result) != 'Braintree\Customer') { - $result = $result->customer; + if (!empty($thePlan)) { + $collection = Braintree\Customer::search([ + Braintree\CustomerSearch::email()->is($args['email']) + ]); + + if ($collection->maximumCount() > 0) { + foreach ($collection as $customer) { + $result = $customer; + } + } else { + $result = Braintree\Customer::create($args); } - $subscription = [ - 'paymentMethodToken' => $result->creditCards[0]->token, - 'planId' => $thePlan->id - ]; + if (!empty($result)) { + if (get_class($result) != 'Braintree\Customer') { + $result = $result->customer; + } - if ((float)$submission_data['payment_amount'] != (float)$plan->price) { - $subscription['price'] = (float)$submission_data['payment_amount']; - } + $subscription = [ + 'paymentMethodToken' => $result->creditCards[0]->token, + 'planId' => $thePlan->id + ]; - $subscriptionResult = Braintree\Subscription::create($subscription); + if ((float)$submission_data['payment_amount'] != (float)$plan->price) { + $subscription['price'] = (float)$submission_data['payment_amount']; + } - if( $subscriptionResult->success == true ) { + $subscriptionResult = Braintree\Subscription::create($subscription); - $authorization['is_success'] = true; - $authorization['error_message'] = ''; - $authorization['subscription_id'] = $subscriptionResult->subscription->id; - $authorization['amount'] = $subscriptionResult->subscription->price; + if ($subscriptionResult->success == true) { - $authorization['captured_payment'] = [ - 'is_success' => true, - 'subscription_id' => $subscriptionResult->subscription->id, - 'transaction_id' => $subscriptionResult->subscription->transactions[0]->id, - 'amount' => $subscriptionResult->subscription->price, - 'error_message' => '' - ]; + $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_customer_create_failure_message', __( 'Failed to create a customer.', 'gravity-forms-braintree' ) ); + $authorization['error_message'] = apply_filters('gform_braintree_no_plan_message', __('No subscription plan found.', 'gravity-forms-braintree')); } - } else { - $authorization['error_message'] = apply_filters( 'gform_braintree_no_plan_message', __( 'No subscription plan found.', 'gravity-forms-braintree' ) ); - } + } catch (Exception $e) { + // Do nothing with exception object, just fallback to generic failure + } } return $authorization; From a607a773bf5e31a80cbebc0da2f926cd0f86732b Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Wed, 3 Aug 2016 09:44:34 -0600 Subject: [PATCH 10/19] Adjust indentation. Fix return attributes in authorize method. Pass first name, last name and email in authorize method. --- lib/class.plugify-gform-braintree.php | 166 +++++++++++++------------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 95bd4ae..28f4c02 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -6,21 +6,21 @@ final class Plugify_GForm_Braintree extends GFPaymentAddOn { protected $_version = '1.0'; - protected $_min_gravityforms_version = '2.0.3'; - protected $_slug = 'gravity-forms-braintree'; - protected $_path = 'gravity-forms-braintree/lib/class.plugify-gform-braintree.php'; - protected $_full_path = __FILE__; - protected $_title = 'Braintree'; - protected $_short_title = 'Braintree'; - protected $_requires_credit_card = true; - protected $_supports_callbacks = false; - protected $_enable_rg_autoupgrade = true; + protected $_min_gravityforms_version = '2.0.3'; + protected $_slug = 'gravity-forms-braintree'; + protected $_path = 'gravity-forms-braintree/lib/class.plugify-gform-braintree.php'; + protected $_full_path = __FILE__; + protected $_title = 'Braintree'; + protected $_short_title = 'Braintree'; + protected $_requires_credit_card = true; + protected $_supports_callbacks = false; + protected $_enable_rg_autoupgrade = true; /** - * Class constructor. Send __construct call to parent - * @since 1.0 - * @return void - */ + * Class constructor. Send __construct call to parent + * @since 1.0 + * @return void + */ public function __construct () { // Build parent @@ -29,11 +29,11 @@ public function __construct () { } /** - * Override init_frontend to assign front end based filters and actions required for operation - * - * @since 1.0 - * @return void - */ + * Override init_frontend to assign front end based filters and actions required for operation + * + * @since 1.0 + * @return void + */ public function init_frontend () { // init_frontend on GFPaymentAddOn @@ -42,25 +42,25 @@ public function init_frontend () { } /** - * 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 - */ + * 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 +85,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,14 +97,17 @@ 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 { @@ -125,12 +131,12 @@ public function authorize( $feed, $submission_data, $form, $entry ) { $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,8 +146,8 @@ 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 ); } } @@ -310,11 +316,11 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { } /** - * Create and display feed settings fields. - * - * @since 1.0 - * @return array - */ + * Create and display feed settings fields. + * + * @since 1.0 + * @return array + */ public function feed_settings_fields () { // Get defaults from GFPaymentAddOn @@ -337,25 +343,25 @@ public function feed_settings_fields () { } /** - * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed - * - * @since 1.0 - * @return array - */ + * 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', @@ -370,8 +376,8 @@ public function plugin_settings_fields () { 'type' => 'text', 'class' => 'medium' ) - ) - ), + ) + ), array( 'title' => 'Payment Settings', 'fields' => array( @@ -415,17 +421,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 boolean - */ + * 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 ) ) { @@ -443,11 +449,11 @@ public function settings_are_valid ( $settings ) { } /** - * Get plugin settings - * - * @since 1.0 - * @return array|boolean - */ + * Get plugin settings + * + * @since 1.0 + * @return array|boolean + */ public function get_plugin_settings () { $settings = parent::get_plugin_settings(); From bbe304e97400cdb7f595c2b3dcb61a17cb298de3 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Sun, 2 Oct 2016 22:32:48 -0600 Subject: [PATCH 11/19] Add first bill date field for setting future bill dates. --- lib/class.plugify-gform-braintree.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 28f4c02..bc12b92 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -41,6 +41,14 @@ public function init_frontend () { } + 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 ); + + return $default_settings; + } + /** * 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 @@ -278,6 +286,11 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { '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']; } From 8110a573d1da90fc1cf80158c311fe5e2feeff7f Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Tue, 18 Oct 2016 11:33:27 -0600 Subject: [PATCH 12/19] Add billing info fields to add first billing date field. --- lib/class.plugify-gform-braintree.php | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index bc12b92..07fce09 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -41,14 +41,6 @@ public function init_frontend () { } - 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 ); - - return $default_settings; - } - /** * 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 @@ -339,9 +331,6 @@ 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 ); @@ -355,6 +344,20 @@ public function feed_settings_fields () { } + /** + * Update billing fields. + * + * @since 3.0.6 + * @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 ); + + return $default_settings; + } + /** * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed * From 04f1aaaa68685a86fa04a3d76ef825663fb72f18 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Sat, 26 Jan 2019 15:27:44 -0700 Subject: [PATCH 13/19] Add tax exempt option to feed settings. --- lib/class.plugify-gform-braintree.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 07fce09..93b072f 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -123,6 +123,10 @@ public function authorize( $feed, $submission_data, $form, $entry ) { $args['options']['submitForSettlement'] = 'true'; } + if ($feed['meta']['taxExempt'] == 1) { + $args['options']['taxExempt'] = 'true'; + } + // Send transaction to Braintree $result = Braintree\Transaction::sale( $args ); @@ -334,11 +338,26 @@ public function feed_settings_fields () { // Remove options $settings = $this->remove_field( 'options', $settings ); - // Remove the subscription option from transaction type dropdown $transaction_type = $this->get_field( 'transactionType', $settings ); $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' + ) + ) + ) + ); + $settings = $this->add_field_after( 'transactionType', $fields, $settings ); + // Return sanitized settings return $settings; From bf3bdaa1d08a1609d86624848277725f77beba9b Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Thu, 7 Mar 2019 11:19:25 -0700 Subject: [PATCH 14/19] Add logging for args and result. Fix comment. --- lib/class.plugify-gform-braintree.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 93b072f..ebd699a 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -118,7 +118,7 @@ public function authorize( $feed, $submission_data, $form, $entry ) { 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'; } @@ -126,9 +126,11 @@ public function authorize( $feed, $submission_data, $form, $entry ) { if ($feed['meta']['taxExempt'] == 1) { $args['options']['taxExempt'] = 'true'; } + GFCommon::log_debug('Braintree Transaction Args: ' . print_r( $args, true )); // Send transaction to Braintree $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' ) { From c771c4534df3896835986c1c6dcc222730d7858b Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Wed, 20 Mar 2019 22:07:24 -0600 Subject: [PATCH 15/19] Update to version 3.39.0 SDK. --- lib/Braintree.php | 17 +- lib/Braintree/AccountUpdaterDailyReport.php | 2 - lib/Braintree/AchMandate.php | 55 ++ lib/Braintree/AddOn.php | 14 + lib/Braintree/Address.php | 5 +- lib/Braintree/AddressGateway.php | 16 +- lib/Braintree/AmexExpressCheckoutCard.php | 17 +- lib/Braintree/AndroidPayCard.php | 8 +- lib/Braintree/ApplePayCard.php | 9 +- lib/Braintree/ApplePayGateway.php | 65 ++ lib/Braintree/ApplePayOptions.php | 28 + lib/Braintree/AuthorizationAdjustment.php | 35 ++ lib/Braintree/Base.php | 17 +- lib/Braintree/BinData.php | 41 ++ lib/Braintree/ClientToken.php | 2 +- lib/Braintree/ClientTokenGateway.php | 4 +- lib/Braintree/CoinbaseAccount.php | 2 - lib/Braintree/Collection.php | 1 - lib/Braintree/Configuration.php | 169 +++++- .../ConnectedMerchantPayPalStatusChanged.php | 37 ++ .../ConnectedMerchantStatusTransitioned.php | 37 ++ lib/Braintree/CredentialsParser.php | 1 - lib/Braintree/CreditCard.php | 33 +- lib/Braintree/CreditCardGateway.php | 35 +- lib/Braintree/CreditCardVerification.php | 24 + lib/Braintree/Customer.php | 71 ++- lib/Braintree/CustomerGateway.php | 62 +- lib/Braintree/Descriptor.php | 5 + lib/Braintree/Digest.php | 2 - lib/Braintree/Disbursement.php | 16 +- lib/Braintree/DisbursementDetails.php | 7 +- lib/Braintree/Discount.php | 14 + lib/Braintree/Dispute.php | 119 +++- lib/Braintree/Dispute/EvidenceDetails.php | 31 + .../Dispute/StatusHistoryDetails.php | 20 + lib/Braintree/Dispute/TransactionDetails.php | 2 - lib/Braintree/DisputeGateway.php | 274 +++++++++ lib/Braintree/DisputeSearch.php | 90 +++ lib/Braintree/DocumentUpload.php | 52 ++ lib/Braintree/DocumentUploadGateway.php | 81 +++ lib/Braintree/EndsWithNode.php | 23 + lib/Braintree/Error/Codes.php | 295 +++++++--- lib/Braintree/Error/ErrorCollection.php | 1 - lib/Braintree/Error/Validation.php | 3 +- .../Error/ValidationErrorCollection.php | 3 +- lib/Braintree/EuropeBankAccount.php | 1 - lib/Braintree/Exception.php | 1 - lib/Braintree/Exception/Authentication.php | 1 - lib/Braintree/Exception/Authorization.php | 1 - lib/Braintree/Exception/Connection.php | 17 + .../Exception/DownForMaintenance.php | 1 - lib/Braintree/Exception/ForgedQueryString.php | 1 - lib/Braintree/Exception/NotFound.php | 1 - lib/Braintree/Exception/SSLCaFileNotFound.php | 1 - lib/Braintree/Exception/SSLCertificate.php | 1 - lib/Braintree/Exception/ServerError.php | 1 - .../TestOperationPerformedInProduction.php | 1 - lib/Braintree/Exception/Timeout.php | 1 - lib/Braintree/Exception/TooManyRequests.php | 1 - lib/Braintree/Exception/Unexpected.php | 1 - lib/Braintree/Exception/UpgradeRequired.php | 1 - lib/Braintree/Exception/ValidationsFailed.php | 1 - lib/Braintree/FacilitatedDetails.php | 34 ++ lib/Braintree/FacilitatorDetails.php | 7 +- lib/Braintree/Gateway.php | 82 ++- .../GrantedPaymentInstrumentUpdate.php | 73 +++ lib/Braintree/GraphQL.php | 44 ++ lib/Braintree/Http.php | 127 +++- lib/Braintree/IbanBankAccount.php | 57 ++ lib/Braintree/IdealPayment.php | 92 +++ lib/Braintree/IdealPaymentGateway.php | 104 ++++ lib/Braintree/Instance.php | 1 - lib/Braintree/LocalPaymentCompleted.php | 68 +++ lib/Braintree/MasterpassCard.php | 141 +++++ lib/Braintree/MerchantAccount.php | 10 + lib/Braintree/MerchantAccountGateway.php | 28 + lib/Braintree/OAuthAccessRevocation.php | 32 + lib/Braintree/OAuthCredentials.php | 2 - lib/Braintree/OAuthGateway.php | 7 +- lib/Braintree/OAuthResult.php | 2 - lib/Braintree/PaginatedCollection.php | 120 ++++ lib/Braintree/PaginatedResult.php | 32 + lib/Braintree/PartnerMerchant.php | 1 - lib/Braintree/PayPalAccount.php | 10 +- lib/Braintree/PayPalAccountGateway.php | 2 - lib/Braintree/PaymentInstrumentType.php | 6 + lib/Braintree/PaymentMethod.php | 7 +- lib/Braintree/PaymentMethodGateway.php | 178 +++--- lib/Braintree/PaymentMethodNonce.php | 13 +- lib/Braintree/PaymentMethodNonceGateway.php | 3 - lib/Braintree/PaymentMethodParser.php | 58 ++ lib/Braintree/Plan.php | 17 + lib/Braintree/ProcessorResponseTypes.php | 15 + lib/Braintree/ResourceCollection.php | 1 - .../Result/CreditCardVerification.php | 5 +- lib/Braintree/Result/Error.php | 1 - lib/Braintree/Result/Successful.php | 3 +- .../Result/UsBankAccountVerification.php | 112 ++++ .../RevokedPaymentMethodMetadata.php | 53 ++ lib/Braintree/RiskData.php | 5 + lib/Braintree/SamsungPayCard.php | 138 +++++ lib/Braintree/SettlementBatchSummary.php | 3 + lib/Braintree/Subscription.php | 42 +- lib/Braintree/Subscription/StatusDetails.php | 5 +- lib/Braintree/SubscriptionGateway.php | 21 +- lib/Braintree/SubscriptionSearch.php | 5 + lib/Braintree/Test/CreditCardNumbers.php | 12 +- lib/Braintree/Test/MerchantAccount.php | 1 - lib/Braintree/Test/Nonces.php | 14 +- lib/Braintree/Test/Transaction.php | 2 - lib/Braintree/Test/TransactionAmounts.php | 6 +- lib/Braintree/Test/VenmoSdk.php | 1 - lib/Braintree/ThreeDSecureInfo.php | 5 + lib/Braintree/Transaction.php | 140 ++++- lib/Braintree/Transaction/AddressDetails.php | 1 - .../AmexExpressCheckoutCardDetails.php | 2 - .../Transaction/AndroidPayCardDetails.php | 2 - .../Transaction/ApplePayCardDetails.php | 2 - lib/Braintree/Transaction/CoinbaseDetails.php | 2 - .../Transaction/CreditCardDetails.php | 2 +- lib/Braintree/Transaction/CustomerDetails.php | 1 - .../Transaction/EuropeBankAccountDetails.php | 1 - .../Transaction/IdealPaymentDetails.php | 23 + lib/Braintree/Transaction/LineItem.php | 4 + .../Transaction/MasterpassCardDetails.php | 52 ++ lib/Braintree/Transaction/PayPalDetails.php | 18 +- .../Transaction/SamsungPayCardDetails.php | 52 ++ lib/Braintree/Transaction/StatusDetails.php | 11 +- .../Transaction/SubscriptionDetails.php | 5 +- .../Transaction/UsBankAccountDetails.php | 40 ++ .../Transaction/VenmoAccountDetails.php | 2 - .../Transaction/VisaCheckoutCardDetails.php | 52 ++ lib/Braintree/TransactionGateway.php | 93 ++- lib/Braintree/TransactionLineItem.php | 55 ++ lib/Braintree/TransactionLineItemGateway.php | 67 +++ lib/Braintree/TransactionSearch.php | 1 + lib/Braintree/TransparentRedirect.php | 1 - lib/Braintree/TransparentRedirectGateway.php | 1 - lib/Braintree/UnknownPaymentMethod.php | 2 - lib/Braintree/UsBankAccount.php | 117 ++++ lib/Braintree/UsBankAccountGateway.php | 106 ++++ lib/Braintree/UsBankAccountVerification.php | 102 ++++ .../UsBankAccountVerificationGateway.php | 129 ++++ .../UsBankAccountVerificationSearch.php | 64 ++ lib/Braintree/Util.php | 61 +- lib/Braintree/VenmoAccount.php | 10 +- lib/Braintree/Version.php | 4 +- lib/Braintree/VisaCheckoutCard.php | 154 +++++ lib/Braintree/WebhookNotification.php | 101 ++-- lib/Braintree/WebhookNotificationGateway.php | 77 +++ lib/Braintree/WebhookTesting.php | 333 +---------- lib/Braintree/WebhookTestingGateway.php | 553 ++++++++++++++++++ lib/Braintree/Xml.php | 2 - lib/Braintree/Xml/Generator.php | 4 - lib/Braintree/Xml/Parser.php | 2 - lib/ssl/api_braintreegateway_com.ca.crt | 50 ++ 156 files changed, 5349 insertions(+), 838 deletions(-) create mode 100644 lib/Braintree/AchMandate.php create mode 100644 lib/Braintree/ApplePayGateway.php create mode 100644 lib/Braintree/ApplePayOptions.php create mode 100644 lib/Braintree/AuthorizationAdjustment.php create mode 100644 lib/Braintree/BinData.php create mode 100644 lib/Braintree/ConnectedMerchantPayPalStatusChanged.php create mode 100644 lib/Braintree/ConnectedMerchantStatusTransitioned.php create mode 100644 lib/Braintree/Dispute/EvidenceDetails.php create mode 100644 lib/Braintree/Dispute/StatusHistoryDetails.php create mode 100644 lib/Braintree/DisputeGateway.php create mode 100644 lib/Braintree/DisputeSearch.php create mode 100644 lib/Braintree/DocumentUpload.php create mode 100644 lib/Braintree/DocumentUploadGateway.php create mode 100644 lib/Braintree/EndsWithNode.php create mode 100644 lib/Braintree/Exception/Connection.php create mode 100644 lib/Braintree/FacilitatedDetails.php create mode 100644 lib/Braintree/GrantedPaymentInstrumentUpdate.php create mode 100644 lib/Braintree/GraphQL.php create mode 100644 lib/Braintree/IbanBankAccount.php create mode 100644 lib/Braintree/IdealPayment.php create mode 100644 lib/Braintree/IdealPaymentGateway.php create mode 100644 lib/Braintree/LocalPaymentCompleted.php create mode 100644 lib/Braintree/MasterpassCard.php create mode 100644 lib/Braintree/OAuthAccessRevocation.php create mode 100644 lib/Braintree/PaginatedCollection.php create mode 100644 lib/Braintree/PaginatedResult.php create mode 100644 lib/Braintree/PaymentMethodParser.php create mode 100644 lib/Braintree/ProcessorResponseTypes.php create mode 100644 lib/Braintree/Result/UsBankAccountVerification.php create mode 100644 lib/Braintree/RevokedPaymentMethodMetadata.php create mode 100644 lib/Braintree/SamsungPayCard.php create mode 100644 lib/Braintree/Transaction/IdealPaymentDetails.php create mode 100644 lib/Braintree/Transaction/LineItem.php create mode 100644 lib/Braintree/Transaction/MasterpassCardDetails.php create mode 100644 lib/Braintree/Transaction/SamsungPayCardDetails.php create mode 100644 lib/Braintree/Transaction/UsBankAccountDetails.php create mode 100644 lib/Braintree/Transaction/VisaCheckoutCardDetails.php create mode 100644 lib/Braintree/TransactionLineItem.php create mode 100644 lib/Braintree/TransactionLineItemGateway.php create mode 100644 lib/Braintree/UsBankAccount.php create mode 100644 lib/Braintree/UsBankAccountGateway.php create mode 100644 lib/Braintree/UsBankAccountVerification.php create mode 100644 lib/Braintree/UsBankAccountVerificationGateway.php create mode 100644 lib/Braintree/UsBankAccountVerificationSearch.php create mode 100644 lib/Braintree/VisaCheckoutCard.php create mode 100644 lib/Braintree/WebhookNotificationGateway.php create mode 100644 lib/Braintree/WebhookTestingGateway.php diff --git a/lib/Braintree.php b/lib/Braintree.php index f6f1ca9..066107a 100644 --- a/lib/Braintree.php +++ b/lib/Braintree.php @@ -2,8 +2,6 @@ /** * Braintree PHP Library * Creates class_aliases for old class names replaced by PSR-4 Namespaces - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ require_once(__DIR__ . DIRECTORY_SEPARATOR . 'autoload.php'); @@ -12,14 +10,15 @@ throw new Braintree_Exception('PHP version >= 5.4.0 required'); } - -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.'); +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 index 802983b..226bdfe 100644 --- a/lib/Braintree/AccountUpdaterDailyReport.php +++ b/lib/Braintree/AccountUpdaterDailyReport.php @@ -6,11 +6,9 @@ * * * @package Braintree - * @copyright 2016 Braintree, a division of PayPal, Inc. * * @property-read string $reportUrl * @property-read date $reportDate - * @property-read date $receivedDate */ class AccountUpdaterDailyReport extends Base { 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 04c1705..3eb5612 100644 --- a/lib/Braintree/AddOn.php +++ b/lib/Braintree/AddOn.php @@ -1,6 +1,20 @@ _validateCustomerId($customerId); unset($attribs['customerId']); - return $this->_doCreate( - '/customers/' . $customerId . '/addresses', - ['address' => $attribs] - ); + try { + return $this->_doCreate( + '/customers/' . $customerId . '/addresses', + ['address' => $attribs] + ); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'Customer ' . $customerId . ' not found.' + ); + } } /** @@ -274,7 +279,6 @@ private function _doCreate($subPath, $params) $response = $this->_http->post($fullPath, $params); return $this->_verifyGatewayResponse($response); - } /** diff --git a/lib/Braintree/AmexExpressCheckoutCard.php b/lib/Braintree/AmexExpressCheckoutCard.php index a17d938..90d48f2 100644 --- a/lib/Braintree/AmexExpressCheckoutCard.php +++ b/lib/Braintree/AmexExpressCheckoutCard.php @@ -11,22 +11,21 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * - * @property-read string $createdAt - * @property-read string $default - * @property-read string $updatedAt - * @property-read string $customerId - * @property-read string $cardType * @property-read string $bin * @property-read string $cardMemberExpiryDate * @property-read string $cardMemberNumber * @property-read string $cardType - * @property-read string $sourceDescription - * @property-read string $token - * @property-read string $imageUrl + * @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 { diff --git a/lib/Braintree/AndroidPayCard.php b/lib/Braintree/AndroidPayCard.php index 22879cd..4d73c4d 100644 --- a/lib/Braintree/AndroidPayCard.php +++ b/lib/Braintree/AndroidPayCard.php @@ -11,13 +11,12 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $bin * @property-read string $cardType - * @property-read string $createdAt + * @property-read \DateTime $createdAt * @property-read string $customerId - * @property-read string $default + * @property-read boolean $default * @property-read string $expirationMonth * @property-read string $expirationYear * @property-read string $googleTransactionId @@ -26,8 +25,9 @@ * @property-read string $sourceCardLast4 * @property-read string $sourceCardType * @property-read string $sourceDescription + * @property-read \Braintree\Subscription[] $subscriptions * @property-read string $token - * @property-read string $updatedAt + * @property-read \DateTime $updatedAt * @property-read string $virtualCardLast4 * @property-read string $virtualCardType */ diff --git a/lib/Braintree/ApplePayCard.php b/lib/Braintree/ApplePayCard.php index c7b1dea..51f10fa 100644 --- a/lib/Braintree/ApplePayCard.php +++ b/lib/Braintree/ApplePayCard.php @@ -11,20 +11,23 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * + * @property-read string $bin * @property-read string $cardType - * @property-read string $createdAt + * @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 string $updatedAt + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read \DateTime $updatedAt */ class ApplePayCard extends Base { 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 index 1d661fb..8e027c7 100644 --- a/lib/Braintree/Base.php +++ b/lib/Braintree/Base.php @@ -1,6 +1,8 @@ _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 index 269bd98..f3d4884 100644 --- a/lib/Braintree/ClientToken.php +++ b/lib/Braintree/ClientToken.php @@ -11,7 +11,7 @@ class ClientToken /** * * @param array $params - * @return array + * @return string */ public static function generate($params=[]) { diff --git a/lib/Braintree/ClientTokenGateway.php b/lib/Braintree/ClientTokenGateway.php index cf72b28..b2fcbf5 100644 --- a/lib/Braintree/ClientTokenGateway.php +++ b/lib/Braintree/ClientTokenGateway.php @@ -53,7 +53,7 @@ public function generate($params=[]) * @ignore * @param var $url * @param array $params - * @return mixed + * @return string */ public function _doGenerate($subPath, $params) { @@ -86,7 +86,7 @@ public function generateWithCustomerIdSignature() return [ "version", "customerId", "proxyMerchantId", ["options" => ["makeDefault", "verifyCard", "failOnDuplicatePaymentMethod"]], - "merchantAccountId", "sepaMandateType", "sepaMandateAcceptanceLocation"]; + "merchantAccountId"]; } /** diff --git a/lib/Braintree/CoinbaseAccount.php b/lib/Braintree/CoinbaseAccount.php index 32fb583..a93fc1c 100644 --- a/lib/Braintree/CoinbaseAccount.php +++ b/lib/Braintree/CoinbaseAccount.php @@ -6,7 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,7 +16,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $customerId * @property-read string $token diff --git a/lib/Braintree/Collection.php b/lib/Braintree/Collection.php index e44b877..1cbec1d 100644 --- a/lib/Braintree/Collection.php +++ b/lib/Braintree/Collection.php @@ -17,7 +17,6 @@ * * @package Braintree * @subpackage Utility - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Collection implements Countable, IteratorAggregate, ArrayAccess diff --git a/lib/Braintree/Configuration.php b/lib/Braintree/Configuration.php index 8a40948..b72c30b 100644 --- a/lib/Braintree/Configuration.php +++ b/lib/Braintree/Configuration.php @@ -7,7 +7,6 @@ * * @package Braintree * @subpackage Utility - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Configuration @@ -24,13 +23,18 @@ class Configuration private $_proxyHost = null; private $_proxyPort = null; private $_proxyType = null; + private $_proxyUser = null; + private $_proxyPassword = null; private $_timeout = 60; + private $_sslVersion = null; + private $_acceptGzipEncoding = true; /** * Braintree API version to use * @access public */ - const API_VERSION = 4; + const API_VERSION = 5; + const GRAPHQL_API_VERSION = '2018-09-10'; public function __construct($attribs = []) { @@ -48,6 +52,12 @@ public function __construct($attribs = []) 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'])) { @@ -125,6 +135,22 @@ public static function timeout($value=null) self::$global->setTimeout($value); } + /** + * 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); + } + /** * Sets or gets the proxy host to use for connecting to Braintree * @@ -180,6 +206,49 @@ public static function isUsingProxy() return !empty($proxyHost) && !empty($proxyPort); } + public static function proxyUser($value = null) + { + if (empty($value)) { + return self::$global->getProxyUser(); + } + self::$global->setProxyUser($value); + } + + public static function proxyPassword($value = null) + { + 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); + } + + /** + * 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); + } + public static function assertGlobalHasAccessTokenOrKeys() { self::$global->assertHasAccessTokenOrKeys(); @@ -312,6 +381,26 @@ public function getProxyType() return $this->_proxyType; } + private function setProxyUser($value) + { + $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; @@ -322,6 +411,26 @@ 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; @@ -348,6 +457,18 @@ public function baseUrl() 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 * @@ -396,6 +517,21 @@ public function portNumber() 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 * @@ -437,6 +573,35 @@ public 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) { 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 index d61e1ae..034d973 100644 --- a/lib/Braintree/CredentialsParser.php +++ b/lib/Braintree/CredentialsParser.php @@ -7,7 +7,6 @@ * * @package Braintree * @subpackage Utility - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class CredentialsParser diff --git a/lib/Braintree/CreditCard.php b/lib/Braintree/CreditCard.php index 9adba03..a889545 100644 --- a/lib/Braintree/CreditCard.php +++ b/lib/Braintree/CreditCard.php @@ -7,27 +7,40 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. * - * @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 CreditCard extends Base { @@ -37,9 +50,11 @@ class CreditCard extends Base 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'; @@ -47,8 +62,8 @@ class CreditCard extends Base const UNKNOWN = 'Unknown'; // Credit card origination location - const INTERNATIONAL = "international"; - const US = "us"; + const INTERNATIONAL = "international"; + const US = "us"; const PREPAID_YES = 'Yes'; const PREPAID_NO = 'No'; @@ -76,6 +91,7 @@ class CreditCard extends Base const COUNTRY_OF_ISSUANCE_UNKNOWN = "Unknown"; const ISSUING_BANK_UNKNOWN = "Unknown"; + const PRODUCT_ID_UNKNOWN = "Unknown"; /* instance methods */ /** @@ -301,6 +317,7 @@ public static function allCardTypes() CreditCard::CHINA_UNION_PAY, CreditCard::DINERS_CLUB_INTERNATIONAL, CreditCard::DISCOVER, + CreditCard::ELO, CreditCard::JCB, CreditCard::LASER, CreditCard::MAESTRO, diff --git a/lib/Braintree/CreditCardGateway.php b/lib/Braintree/CreditCardGateway.php index b632797..5f8ce04 100644 --- a/lib/Braintree/CreditCardGateway.php +++ b/lib/Braintree/CreditCardGateway.php @@ -9,12 +9,11 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. */ class CreditCardGateway { @@ -382,23 +381,25 @@ public static function createSignature() public static function updateSignature() { - $signature = self::baseSignature(self::baseOptions()); + $options = self::baseOptions(); + $options[] = "failOnDuplicatePaymentMethod"; + $signature = self::baseSignature($options); - $updateExistingBillingSignature = [ - [ - 'options' => [ - 'updateExisting' - ] - ] - ]; + $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); - } - } + 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 $signature; } /** diff --git a/lib/Braintree/CreditCardVerification.php b/lib/Braintree/CreditCardVerification.php index ab9075c..d33bb47 100644 --- a/lib/Braintree/CreditCardVerification.php +++ b/lib/Braintree/CreditCardVerification.php @@ -1,6 +1,30 @@ == 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 2015 Braintree, a division of PayPal, Inc. * - * @property-read array $addresses - * @property-read array $paymentMethods + * @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 array $paypalAccounts - * @property-read array $applePayCards - * @property-read array $androidPayCards - * @property-read array $amexExpressCheckoutCards - * @property-read array $venmoAccounts - * @property-read array $coinbaseAccounts + * @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 Customer extends Base @@ -101,9 +104,9 @@ public static function createCustomerUrl() * @param string $id customer id * @return Customer */ - public static function find($id) + public static function find($id, $associationFilterId = null) { - return Configuration::gateway()->customer()->find($id); + return Configuration::gateway()->customer()->find($id, $associationFilterId); } /** @@ -296,6 +299,38 @@ protected function _initialize($customerAttribs) } $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, @@ -303,7 +338,11 @@ protected function _initialize($customerAttribs) $this->coinbaseAccounts, $this->androidPayCards, $this->amexExpressCheckoutCards, - $this->venmoAccounts + $this->venmoAccounts, + $this->visaCheckoutCards, + $this->masterpassCards, + $this->samsungPayCards, + $this->usBankAccounts )); } diff --git a/lib/Braintree/CustomerGateway.php b/lib/Braintree/CustomerGateway.php index 61a6ce0..f010082 100644 --- a/lib/Braintree/CustomerGateway.php +++ b/lib/Braintree/CustomerGateway.php @@ -9,11 +9,10 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. */ class CustomerGateway { @@ -81,7 +80,7 @@ public function fetch($query, $ids) * * @access public * @param array $attribs - * @return Braintree_Result_Successful|Braintree_Result_Error + * @return Result\Successful|Result\Error */ public function create($attribs = []) { @@ -144,19 +143,38 @@ public function createCustomerUrl() */ 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' => - ['customer_browser', 'customer_ip'] + ['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; } @@ -180,7 +198,26 @@ public static function updateSignature() '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; } @@ -190,14 +227,19 @@ public static function updateSignature() * * @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) + public function find($id, $associationFilterId = null) { $this->_validateId($id); try { - $path = $this->_config->merchantPath() . '/customers/' . $id; + $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) { @@ -295,7 +337,7 @@ public function saleNoValidate($customerId, $transactionAttribs) * * 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} + * 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 diff --git a/lib/Braintree/Descriptor.php b/lib/Braintree/Descriptor.php index d63383f..843ea4f 100644 --- a/lib/Braintree/Descriptor.php +++ b/lib/Braintree/Descriptor.php @@ -1,6 +1,11 @@ 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 7eb5928..6c5e182 100644 --- a/lib/Braintree/DisbursementDetails.php +++ b/lib/Braintree/DisbursementDetails.php @@ -7,14 +7,13 @@ * * * @package Braintree - * @copyright 2015 Braintree, a division of PayPal, Inc. * + * @property-read string $disbursementDate + * @property-read boolean $fundsHeld * @property-read string $settlementAmount - * @property-read string $settlementCurrencyIsoCode * @property-read string $settlementCurrencyExchangeRate - * @property-read string $fundsHeld + * @property-read string $settlementCurrencyIsoCode * @property-read string $success - * @property-read string $disbursementDate */ class DisbursementDetails extends Instance { diff --git a/lib/Braintree/Discount.php b/lib/Braintree/Discount.php index da780d0..efa3d72 100644 --- a/lib/Braintree/Discount.php +++ b/lib/Braintree/Discount.php @@ -1,6 +1,20 @@ _attributes = $disputeAttribs; if (isset($disputeAttribs['transaction'])) { - $this->_set('transactionDetails', + $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']) ); } @@ -78,5 +114,78 @@ public function __toString() 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/Error/Codes.php b/lib/Braintree/Error/Codes.php index 814145f..0d92fb0 100644 --- a/lib/Braintree/Error/Codes.php +++ b/lib/Braintree/Error/Codes.php @@ -14,7 +14,6 @@ * @package Braintree * @subpackage Errors * @category Validation - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Codes { @@ -85,57 +84,58 @@ class Codes const CLIENT_TOKEN_VERIFY_CARD_REQUIRES_CUSTOMER_ID = '92802'; const CLIENT_TOKEN_MERCHANT_ACCOUNT_DOES_NOT_EXIST = '92807'; - const CREDIT_CARD_BILLING_ADDRESS_CONFLICT = '91701'; - const CREDIT_CARD_BILLING_ADDRESS_FORMAT_IS_INVALID = '91744'; - const CREDIT_CARD_BILLING_ADDRESS_ID_IS_INVALID = '91702'; - const CREDIT_CARD_CANNOT_UPDATE_CARD_USING_PAYMENT_METHOD_NONCE = '91735'; - const CREDIT_CARD_CARDHOLDER_NAME_IS_TOO_LONG = '81723'; - const CREDIT_CARD_CREDIT_CARD_TYPE_IS_NOT_ACCEPTED = '81703'; - const CREDIT_CARD_CREDIT_CARD_TYPE_IS_NOT_ACCEPTED_BY_SUBSCRIPTION_MERCHANT_ACCOUNT = '81718'; - const CREDIT_CARD_CUSTOMER_ID_IS_INVALID = '91705'; - const CREDIT_CARD_CUSTOMER_ID_IS_REQUIRED = '91704'; - const CREDIT_CARD_CVV_IS_INVALID = '81707'; - const CREDIT_CARD_CVV_IS_REQUIRED = '81706'; - const CREDIT_CARD_CVV_VERIFICATION_FAILED = '81736'; - const CREDIT_CARD_DUPLICATE_CARD_EXISTS = '81724'; - const CREDIT_CARD_EXPIRATION_DATE_CONFLICT = '91708'; - const CREDIT_CARD_EXPIRATION_DATE_IS_INVALID = '81710'; - const CREDIT_CARD_EXPIRATION_DATE_IS_REQUIRED = '81709'; - const CREDIT_CARD_EXPIRATION_DATE_YEAR_IS_INVALID = '81711'; - const CREDIT_CARD_EXPIRATION_MONTH_IS_INVALID = '81712'; - const CREDIT_CARD_EXPIRATION_YEAR_IS_INVALID = '81713'; - const CREDIT_CARD_INVALID_PARAMS_FOR_CREDIT_CARD_UPDATE = '91745'; - const CREDIT_CARD_INVALID_VENMO_SDK_PAYMENT_METHOD_CODE = '91727'; - const CREDIT_CARD_NUMBER_INVALID_LENGTH = '81716'; - const CREDIT_CARD_NUMBER_IS_INVALID = '81715'; - const CREDIT_CARD_NUMBER_IS_PROHIBITED = '81750'; - const CREDIT_CARD_NUMBER_IS_REQUIRED = '81714'; - const CREDIT_CARD_NUMBER_LENGTH_IS_INVALID = '81716'; - const CREDIT_CARD_NUMBER_MUST_BE_TEST_NUMBER = '81717'; - const CREDIT_CARD_OPTIONS_UPDATE_EXISTING_TOKEN_IS_INVALID = '91723'; - const CREDIT_CARD_OPTIONS_UPDATE_EXISTING_TOKEN_NOT_ALLOWED = '91729'; - const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_CANNOT_BE_NEGATIVE = '91739'; - const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_FORMAT_IS_INVALID = '91740'; - const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_IS_TOO_LARGE = '91752'; - const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_NOT_SUPPORTED_BY_PROCESSOR = '91741'; - const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_ID_IS_INVALID = '91728'; - const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_IS_FORBIDDEN = '91743'; - const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_IS_SUSPENDED = '91742'; - const CREDIT_CARD_PAYMENT_METHOD_CONFLICT = '81725'; - const CREDIT_CARD_PAYMENT_METHOD_IS_NOT_A_CREDIT_CARD = '91738'; - const CREDIT_CARD_PAYMENT_METHOD_NONCE_CARD_TYPE_IS_NOT_ACCEPTED = '91734'; - const CREDIT_CARD_PAYMENT_METHOD_NONCE_CONSUMED = '91731'; - const CREDIT_CARD_PAYMENT_METHOD_NONCE_LOCKED = '91733'; - const CREDIT_CARD_PAYMENT_METHOD_NONCE_UNKNOWN = '91732'; - const CREDIT_CARD_POSTAL_CODE_VERIFICATION_FAILED = '81737'; - const CREDIT_CARD_TOKEN_FORMAT_IS_INVALID = '91718'; - const CREDIT_CARD_TOKEN_INVALID = '91718'; - const CREDIT_CARD_TOKEN_IS_IN_USE = '91719'; - const CREDIT_CARD_TOKEN_IS_NOT_ALLOWED = '91721'; - const CREDIT_CARD_TOKEN_IS_REQUIRED = '91722'; - const CREDIT_CARD_TOKEN_IS_TOO_LONG = '91720'; - const CREDIT_CARD_VENMO_SDK_PAYMENT_METHOD_CODE_CARD_TYPE_IS_NOT_ACCEPTED = '91726'; - const CREDIT_CARD_VERIFICATION_NOT_SUPPORTED_ON_THIS_MERCHANT_ACCOUNT = '91730'; + const CREDIT_CARD_BILLING_ADDRESS_CONFLICT = '91701'; + const CREDIT_CARD_BILLING_ADDRESS_FORMAT_IS_INVALID = '91744'; + const CREDIT_CARD_BILLING_ADDRESS_ID_IS_INVALID = '91702'; + const CREDIT_CARD_CANNOT_UPDATE_CARD_USING_PAYMENT_METHOD_NONCE = '91735'; + const CREDIT_CARD_CARDHOLDER_NAME_IS_TOO_LONG = '81723'; + const CREDIT_CARD_CREDIT_CARD_TYPE_IS_NOT_ACCEPTED = '81703'; + const CREDIT_CARD_CREDIT_CARD_TYPE_IS_NOT_ACCEPTED_BY_SUBSCRIPTION_MERCHANT_ACCOUNT = '81718'; + const CREDIT_CARD_CUSTOMER_ID_IS_INVALID = '91705'; + const CREDIT_CARD_CUSTOMER_ID_IS_REQUIRED = '91704'; + const CREDIT_CARD_CVV_IS_INVALID = '81707'; + const CREDIT_CARD_CVV_IS_REQUIRED = '81706'; + const CREDIT_CARD_CVV_VERIFICATION_FAILED = '81736'; + const CREDIT_CARD_DUPLICATE_CARD_EXISTS = '81724'; + const CREDIT_CARD_EXPIRATION_DATE_CONFLICT = '91708'; + const CREDIT_CARD_EXPIRATION_DATE_IS_INVALID = '81710'; + const CREDIT_CARD_EXPIRATION_DATE_IS_REQUIRED = '81709'; + const CREDIT_CARD_EXPIRATION_DATE_YEAR_IS_INVALID = '81711'; + const CREDIT_CARD_EXPIRATION_MONTH_IS_INVALID = '81712'; + const CREDIT_CARD_EXPIRATION_YEAR_IS_INVALID = '81713'; + const CREDIT_CARD_INVALID_PARAMS_FOR_CREDIT_CARD_UPDATE = '91745'; + const CREDIT_CARD_INVALID_VENMO_SDK_PAYMENT_METHOD_CODE = '91727'; + const CREDIT_CARD_NUMBER_INVALID_LENGTH = '81716'; + const CREDIT_CARD_NUMBER_IS_INVALID = '81715'; + const CREDIT_CARD_NUMBER_IS_PROHIBITED = '81750'; + const CREDIT_CARD_NUMBER_IS_REQUIRED = '81714'; + const CREDIT_CARD_NUMBER_LENGTH_IS_INVALID = '81716'; + const CREDIT_CARD_NUMBER_MUST_BE_TEST_NUMBER = '81717'; + const CREDIT_CARD_OPTIONS_UPDATE_EXISTING_TOKEN_IS_INVALID = '91723'; + const CREDIT_CARD_OPTIONS_UPDATE_EXISTING_TOKEN_NOT_ALLOWED = '91729'; + const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_CANNOT_BE_NEGATIVE = '91739'; + const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_FORMAT_IS_INVALID = '91740'; + const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_IS_TOO_LARGE = '91752'; + const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_NOT_SUPPORTED_BY_PROCESSOR = '91741'; + const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_ID_IS_INVALID = '91728'; + const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_IS_FORBIDDEN = '91743'; + const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_IS_SUSPENDED = '91742'; + const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_CANNOT_BE_SUB_MERCHANT_ACCOUNT = '91755'; + const CREDIT_CARD_PAYMENT_METHOD_CONFLICT = '81725'; + const CREDIT_CARD_PAYMENT_METHOD_IS_NOT_A_CREDIT_CARD = '91738'; + const CREDIT_CARD_PAYMENT_METHOD_NONCE_CARD_TYPE_IS_NOT_ACCEPTED = '91734'; + const CREDIT_CARD_PAYMENT_METHOD_NONCE_CONSUMED = '91731'; + const CREDIT_CARD_PAYMENT_METHOD_NONCE_LOCKED = '91733'; + const CREDIT_CARD_PAYMENT_METHOD_NONCE_UNKNOWN = '91732'; + const CREDIT_CARD_POSTAL_CODE_VERIFICATION_FAILED = '81737'; + const CREDIT_CARD_TOKEN_FORMAT_IS_INVALID = '91718'; + const CREDIT_CARD_TOKEN_INVALID = '91718'; + const CREDIT_CARD_TOKEN_IS_IN_USE = '91719'; + const CREDIT_CARD_TOKEN_IS_NOT_ALLOWED = '91721'; + const CREDIT_CARD_TOKEN_IS_REQUIRED = '91722'; + const CREDIT_CARD_TOKEN_IS_TOO_LONG = '91720'; + const CREDIT_CARD_VENMO_SDK_PAYMENT_METHOD_CODE_CARD_TYPE_IS_NOT_ACCEPTED = '91726'; + const CREDIT_CARD_VERIFICATION_NOT_SUPPORTED_ON_THIS_MERCHANT_ACCOUNT = '91730'; const CUSTOMER_COMPANY_IS_TOO_LONG = '81601'; const CUSTOMER_CUSTOM_FIELD_IS_INVALID = '91602'; @@ -166,19 +166,130 @@ class Codes const DESCRIPTOR_INTERNATIONAL_PHONE_FORMAT_IS_INVALID = '92205'; const DESCRIPTOR_URL_FORMAT_IS_INVALID = '92206'; - const INDUSTRY_DATA_INDUSTRY_TYPE_IS_INVALID = '93401'; - const INDUSTRY_DATA_LODGING_EMPTY_DATA = '93402'; - const INDUSTRY_DATA_LODGING_FOLIO_NUMBER_IS_INVALID = '93403'; - const INDUSTRY_DATA_LODGING_CHECK_IN_DATE_IS_INVALID = '93404'; - const INDUSTRY_DATA_LODGING_CHECK_OUT_DATE_IS_INVALID = '93405'; - const INDUSTRY_DATA_LODGING_CHECK_OUT_DATE_MUST_FOLLOW_CHECK_IN_DATE = '93406'; - const INDUSTRY_DATA_LODGING_UNKNOWN_DATA_FIELD = '93407'; - const INDUSTRY_DATA_TRAVEL_CRUISE_EMPTY_DATA = '93408'; - const INDUSTRY_DATA_TRAVEL_CRUISE_UNKNOWN_DATA_FIELD = '93409'; - const INDUSTRY_DATA_TRAVEL_CRUISE_TRAVEL_PACKAGE_IS_INVALID = '93410'; - const INDUSTRY_DATA_TRAVEL_CRUISE_DEPARTURE_DATE_IS_INVALID = '93411'; - const INDUSTRY_DATA_TRAVEL_CRUISE_LODGING_CHECK_IN_DATE_IS_INVALID = '93412'; - const INDUSTRY_DATA_TRAVEL_CRUISE_LODGING_CHECK_OUT_DATE_IS_INVALID = '93413'; + const DISPUTE_CAN_ONLY_ADD_EVIDENCE_TO_OPEN_DISPUTE = '95701'; + const DISPUTE_CAN_ONLY_REMOVE_EVIDENCE_FROM_OPEN_DISPUTE = '95702'; + const DISPUTE_CAN_ONLY_ADD_EVIDENCE_TO_DISPUTE = '95703'; + const DISPUTE_CAN_ONLY_ACCEPT_OPEN_DISPUTE = '95704'; + const DISPUTE_CAN_ONLY_FINALIZE_OPEN_DISPUTE = '95705'; + const DISPUTE_CAN_ONLY_CREATE_EVIDENCE_WITH_VALID_CATEGORY = '95706'; + + const DISPUTE_EVIDENCE_CONTENT_DATE_INVALID = '95707'; + const DISPUTE_EVIDENCE_CONTENT_TOO_LONG = '95708'; + const DISPUTE_EVIDENCE_CONTENT_ARN_TOO_LONG = '95709'; + const DISPUTE_EVIDENCE_CONTENT_PHONE_TOO_LONG = '95710'; + const DISPUTE_EVIDENCE_CATEGORY_TEXT_ONLY = '95711'; + const DISPUTE_EVIDENCE_CATEGORY_DOCUMENT_ONLY = '95712'; + const DISPUTE_EVIDENCE_CATEGORY_NOT_FOR_REASON_CODE = '95713'; + const DISPUTE_EVIDENCE_CATEGORY_DUPLICATE = '95713'; + const DISPUTE_EVIDENCE_CATEGORY_EMAIL_INVALID = '95713'; + + const DISPUTE_DIGITAL_GOODS_MISSING_EVIDENCE = '95720'; + const DISPUTE_DIGITAL_GOODS_MISSING_DOWNLOAD_DATE = '95721'; + const DISPUTE_PRIOR_NON_DISPUTED_TRANSACTION_MISSING_ARN = '95722'; + const DISPUTE_PRIOR_NON_DISPUTED_TRANSACTION_MISSING_DATE = '95723'; + const DISPUTE_RECURRING_TRANSACTION_MISSING_DATE = '95724'; + const DISPUTE_RECURRING_TRANSACTION_MISSING_ARN = '95725'; + const DISPUTE_VALID_EVIDENCE_REQUIRED_TO_FINALIZE = '95726'; + + const DOCUMENT_UPLOAD_KIND_IS_INVALID = '84901'; + const DOCUMENT_UPLOAD_FILE_IS_TOO_LARGE = '84902'; + const DOCUMENT_UPLOAD_FILE_TYPE_IS_INVALID = '84903'; + const DOCUMENT_UPLOAD_FILE_IS_MALFORMED_OR_ENCRYPTED = '84904'; + const DOCUMENT_UPLOAD_FILE_IS_TOO_LONG = '84905'; + + const FAILED_AUTH_ADJUSTMENT_ALLOW_RETRY = '95603'; + const FAILED_AUTH_ADJUSTMENT_HARD_DECLINE = '95602'; + const FINAL_AUTH_SUBMIT_FOR_SETTLEMENT_FOR_DIFFERENT_AMOUNT = '95601'; + + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_ARRIVAL_AIRPORT_CODE_IS_TOO_LONG = '96301'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_ARRIVAL_TIME_FORMAT_IS_INVALID = '96302'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_CARRIER_CODE_IS_TOO_LONG = '96303'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_CONJUNCTION_TICKET_IS_TOO_LONG = '96304'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_COUPON_NUMBER_IS_TOO_LONG = '96305'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_DEPARTURE_AIRPORT_CODE_IS_TOO_LONG = '96306'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_DEPARTURE_TIME_FORMAT_IS_INVALID = '96307'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_EXCHANGE_TICKET_IS_TOO_LONG = '96308'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_AMOUNT_CANNOT_BE_NEGATIVE = '96309'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_AMOUNT_FORMAT_IS_INVALID = '96310'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_AMOUNT_IS_TOO_LARGE = '96311'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_BASIS_CODE_IS_TOO_LONG = '96312'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FEE_AMOUNT_CANNOT_BE_NEGATIVE = '96313'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FEE_AMOUNT_FORMAT_IS_INVALID = '96314'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FEE_AMOUNT_IS_TOO_LARGE = '96315'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_SERVICE_CLASS_IS_TOO_LONG = '96316'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '96317'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TAX_AMOUNT_FORMAT_IS_INVALID = '96318'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TAX_AMOUNT_IS_TOO_LARGE = '96319'; + const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TICKET_NUMBER_IS_TOO_LONG = '96320'; + + const INDUSTRY_DATA_INDUSTRY_TYPE_IS_INVALID = '93401'; + const INDUSTRY_DATA_LODGING_EMPTY_DATA = '93402'; + const INDUSTRY_DATA_LODGING_FOLIO_NUMBER_IS_INVALID = '93403'; + const INDUSTRY_DATA_LODGING_CHECK_IN_DATE_IS_INVALID = '93404'; + const INDUSTRY_DATA_LODGING_CHECK_OUT_DATE_IS_INVALID = '93405'; + const INDUSTRY_DATA_LODGING_CHECK_OUT_DATE_MUST_FOLLOW_CHECK_IN_DATE = '93406'; + const INDUSTRY_DATA_LODGING_UNKNOWN_DATA_FIELD = '93407'; + const INDUSTRY_DATA_TRAVEL_CRUISE_EMPTY_DATA = '93408'; + const INDUSTRY_DATA_TRAVEL_CRUISE_UNKNOWN_DATA_FIELD = '93409'; + const INDUSTRY_DATA_TRAVEL_CRUISE_TRAVEL_PACKAGE_IS_INVALID = '93410'; + const INDUSTRY_DATA_TRAVEL_CRUISE_DEPARTURE_DATE_IS_INVALID = '93411'; + const INDUSTRY_DATA_TRAVEL_CRUISE_LODGING_CHECK_IN_DATE_IS_INVALID = '93412'; + const INDUSTRY_DATA_TRAVEL_CRUISE_LODGING_CHECK_OUT_DATE_IS_INVALID = '93413'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_EMPTY_DATA = '93414'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_UNKNOWN_DATA_FIELD = '93415'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_CUSTOMER_CODE_IS_TOO_LONG = '93416'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_FARE_AMOUNT_CANNOT_BE_NEGATIVE = '93417'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_FARE_AMOUNT_FORMAT_IS_INVALID = '93418'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_FARE_AMOUNT_IS_TOO_LARGE = '93419'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_FEE_AMOUNT_CANNOT_BE_NEGATIVE = '93420'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_FEE_AMOUNT_FORMAT_IS_INVALID = '93421'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_FEE_AMOUNT_IS_TOO_LARGE = '93422'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_ISSUED_DATE_FORMAT_IS_INVALID = '93423'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_ISSUING_CARRIER_CODE_IS_TOO_LONG = '93424'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_PASSENGER_MIDDLE_INITIAL_IS_TOO_LONG = '93425'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_RESTRICTED_TICKET_IS_REQUIRED = '93426'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '93427'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_TAX_AMOUNT_FORMAT_IS_INVALID = '93428'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_TAX_AMOUNT_IS_TOO_LARGE = '93429'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_TICKET_NUMBER_IS_TOO_LONG = '93430'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_LEGS_EXPECTED = '93431'; + const INDUSTRY_DATA_TRAVEL_FLIGHT_TOO_MANY_LEGS = '93432'; + + const TRANSACTION_LINE_ITEM_COMMODITY_CODE_IS_TOO_LONG = '95801'; + const TRANSACTION_LINE_ITEM_DESCRIPTION_IS_TOO_LONG = '95803'; + const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_FORMAT_IS_INVALID = '95804'; + const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_IS_TOO_LARGE = '95805'; + const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95806'; // Deprecated as the amount may be zero. Use TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_CANNOT_BE_ZERO. + const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_CANNOT_BE_NEGATIVE = '95806'; + const TRANSACTION_LINE_ITEM_KIND_IS_INVALID = '95807'; + const TRANSACTION_LINE_ITEM_KIND_IS_REQUIRED = '95808'; + const TRANSACTION_LINE_ITEM_NAME_IS_REQUIRED = '95822'; + const TRANSACTION_LINE_ITEM_NAME_IS_TOO_LONG = '95823'; + const TRANSACTION_LINE_ITEM_PRODUCT_CODE_IS_TOO_LONG = '95809'; + const TRANSACTION_LINE_ITEM_QUANTITY_FORMAT_IS_INVALID = '95810'; + const TRANSACTION_LINE_ITEM_QUANTITY_IS_REQUIRED = '95811'; + const TRANSACTION_LINE_ITEM_QUANTITY_IS_TOO_LARGE = '95812'; + const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_FORMAT_IS_INVALID = '95813'; + const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_IS_REQUIRED = '95814'; + const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_IS_TOO_LARGE = '95815'; + const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95816'; + const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_FORMAT_IS_INVALID = '95817'; + const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_IS_REQUIRED = '95818'; + const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_IS_TOO_LARGE = '95819'; + const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95820'; + const TRANSACTION_LINE_ITEM_UNIT_OF_MEASURE_IS_TOO_LONG = '95821'; + const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_FORMAT_IS_INVALID = '95824'; + const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_IS_TOO_LARGE = '95825'; + const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95826'; // Deprecated as the amount may be zero. Use TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_CANNOT_BE_ZERO. + const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '95826'; + const TRANSACTION_LINE_ITEM_TAX_AMOUNT_FORMAT_IS_INVALID = '95827'; + const TRANSACTION_LINE_ITEM_TAX_AMOUNT_IS_TOO_LARGE = '95828'; + const TRANSACTION_LINE_ITEM_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '95829'; + + const TRANSACTION_EXTERNAL_VAULT_STATUS_IS_INVALID = '915175'; + const TRANSACTION_EXTERNAL_VAULT_STATUS_WITH_PREVIOUS_NETWORK_TRANSACTION_ID_IS_INVALID = '915177'; + const TRANSACTION_EXTERNAL_VAULT_CARD_TYPE_IS_INVALID = '915178'; + const TRANSACTION_EXTERNAL_VAULT_PREVIOUS_NETWORK_TRANSACTION_ID_IS_INVALID = '915179'; const MERCHANT_COUNTRY_CANNOT_BE_BLANK = '83603'; const MERCHANT_COUNTRY_CODE_ALPHA2_IS_INVALID = '93607'; @@ -195,6 +306,12 @@ class Codes const MERCHANT_INCONSISTENT_COUNTRY = '93612'; const MERCHANT_ACCOUNT_PAYMENT_METHODS_ARE_INVALID = '93613'; const MERCHANT_PAYMENT_METHODS_ARE_NOT_ALLOWED = '93615'; + const MERCHANT_MERCHANT_ACCOUNT_EXISTS_FOR_CURRENCY = '93616'; + const MERCHANT_CURRENCY_IS_REQUIRED = '93617'; + const MERCHANT_CURRENCY_IS_INVALID = '93618'; + const MERCHANT_NO_MERCHANT_ACCOUNTS = '93619'; + const MERCHANT_MERCHANT_ACCOUNT_EXISTS_FOR_ID = '93620'; + const MERCHANT_MERCHANT_ACCOUNT_NOT_AUTH_ONBOARDED = '93621'; const MERCHANT_ACCOUNT_ID_FORMAT_IS_INVALID = '82603'; const MERCHANT_ACCOUNT_ID_IS_IN_USE = '82604'; @@ -303,6 +420,8 @@ class Codes const PAYMENT_METHOD_PAYMENT_METHOD_NONCE_UNKNOWN = '93108'; const PAYMENT_METHOD_PAYMENT_METHOD_NONCE_LOCKED = '93109'; const PAYMENT_METHOD_PAYMENT_METHOD_PARAMS_ARE_REQUIRED = '93101'; + const PAYMENT_METHOD_NO_LONGER_SUPPORTED = '93117'; + const PAYMENT_METHOD_OPTIONS_US_BANK_ACCOUNT_VERIFICATION_METHOD_IS_INVALID = '93121'; const PAYPAL_ACCOUNT_AUTH_EXPIRED = '92911'; const PAYPAL_ACCOUNT_CANNOT_HAVE_BOTH_ACCESS_TOKEN_AND_CONSENT_CODE = '82903'; @@ -407,6 +526,7 @@ class Codes const TRANSACTION_AMOUNT_CANNOT_BE_NEGATIVE = '81501'; const TRANSACTION_AMOUNT_DOES_NOT_MATCH3_D_SECURE_AMOUNT = '91585'; + const TRANSACTION_AMOUNT_DOES_NOT_MATCH_IDEAL_PAYMENT_AMOUNT = '915144'; const TRANSACTION_AMOUNT_FORMAT_IS_INVALID = '81503'; const TRANSACTION_AMOUNT_IS_INVALID = '81503'; const TRANSACTION_AMOUNT_IS_REQUIRED = '81502'; @@ -416,6 +536,7 @@ class Codes const TRANSACTION_CANNOT_BE_VOIDED = '91504'; const TRANSACTION_CANNOT_CANCEL_RELEASE = '91562'; const TRANSACTION_CANNOT_CLONE_CREDIT = '91543'; + const TRANSACTION_CANNOT_CLONE_MARKETPLACE_TRANSACTION = '915137'; const TRANSACTION_CANNOT_CLONE_TRANSACTION_WITH_PAYPAL_ACCOUNT = '91573'; const TRANSACTION_CANNOT_CLONE_TRANSACTION_WITH_VAULT_CREDIT_CARD = '91540'; const TRANSACTION_CANNOT_CLONE_UNSUCCESSFUL_TRANSACTION = '91542'; @@ -439,10 +560,26 @@ class Codes const TRANSACTION_CUSTOMER_ID_IS_INVALID = '91510'; const TRANSACTION_CUSTOM_FIELD_IS_INVALID = '91526'; const TRANSACTION_CUSTOM_FIELD_IS_TOO_LONG = '81527'; + const TRANSACTION_PAYMENT_INSTRUMENT_WITH_EXTERNAL_VAULT_IS_INVALID = '915176'; const TRANSACTION_HAS_ALREADY_BEEN_REFUNDED = '91512'; + const TRANSACTION_IDEAL_PAYMENT_NOT_COMPLETE = '815141'; + const TRANSACTION_IDEAL_PAYMENTS_CANNOT_BE_VAULTED = '915150'; + const TRANSACTION_LINE_ITEMS_EXPECTED = '915158'; + const TRANSACTION_TOO_MANY_LINE_ITEMS = '915157'; + const TRANSACTION_DISCOUNT_AMOUNT_FORMAT_IS_INVALID = '915159'; + const TRANSACTION_DISCOUNT_AMOUNT_CANNOT_BE_NEGATIVE = '915160'; + const TRANSACTION_DISCOUNT_AMOUNT_IS_TOO_LARGE = '915161'; + const TRANSACTION_SHIPPING_AMOUNT_FORMAT_IS_INVALID = '915162'; + const TRANSACTION_SHIPPING_AMOUNT_CANNOT_BE_NEGATIVE = '915163'; + const TRANSACTION_SHIPPING_AMOUNT_IS_TOO_LARGE = '915164'; + const TRANSACTION_SHIPS_FROM_POSTAL_CODE_IS_TOO_LONG = '915165'; + const TRANSACTION_SHIPS_FROM_POSTAL_CODE_IS_INVALID = '915166'; + const TRANSACTION_SHIPS_FROM_POSTAL_CODE_INVALID_CHARACTERS = '915167'; const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_MATCH3_D_SECURE_MERCHANT_ACCOUNT = '91584'; + const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_MATCH_IDEAL_PAYMENT_MERCHANT_ACCOUNT = '915143'; const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_SUPPORT_MOTO = '91558'; const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_SUPPORT_REFUNDS = '91547'; + const TRANSACTION_MERCHANT_ACCOUNT_ID_DOES_NOT_MATCH_SUBSCRIPTION = '915180'; const TRANSACTION_MERCHANT_ACCOUNT_ID_IS_INVALID = '91513'; const TRANSACTION_MERCHANT_ACCOUNT_IS_SUSPENDED = '91514'; const TRANSACTION_MERCHANT_ACCOUNT_NAME_IS_INVALID = '91513'; //Deprecated @@ -451,6 +588,8 @@ class Codes const TRANSACTION_OPTIONS_SUBMIT_FOR_SETTLEMENT_IS_REQUIRED_FOR_PAYPAL_UNILATERAL = '91582'; const TRANSACTION_OPTIONS_USE_BILLING_FOR_SHIPPING_DISABLED = '91572'; const TRANSACTION_OPTIONS_VAULT_IS_DISABLED = '91525'; + const TRANSACTION_ORDER_ID_DOES_NOT_MATCH_IDEAL_PAYMENT_ORDER_ID = '91503'; + const TRANSACTION_ORDER_ID_IS_REQUIRED_WITH_IDEAL_PAYMENT = '91502'; const TRANSACTION_ORDER_ID_IS_TOO_LONG = '91501'; const TRANSACTION_PAYMENT_INSTRUMENT_NOT_SUPPORTED_BY_MERCHANT_ACCOUNT = '91577'; const TRANSACTION_PAYMENT_INSTRUMENT_TYPE_IS_NOT_ACCEPTED = '915101'; @@ -508,13 +647,25 @@ class Codes const TRANSACTION_TYPE_IS_INVALID = '91523'; const TRANSACTION_TYPE_IS_REQUIRED = '91524'; const TRANSACTION_UNSUPPORTED_VOICE_AUTHORIZATION = '91539'; + const TRANSACTION_US_BANK_ACCOUNT_NONCE_MUST_BE_PLAID_VERIFIED = '915171'; + const TRANSACTION_US_BANK_ACCOUNT_NOT_VERIFIED = '915172'; + + const TRANSACTION_TRANSACTION_SOURCE_IS_INVALID = '915133'; + + const US_BANK_ACCOUNT_VERIFICATION_NOT_CONFIRMABLE = '96101'; + const US_BANK_ACCOUNT_VERIFICATION_MUST_BE_MICRO_TRANSFERS_VERIFICATION = '96102'; + const US_BANK_ACCOUNT_VERIFICATION_AMOUNTS_DO_NOT_MATCH = '96103'; + const US_BANK_ACCOUNT_VERIFICATION_TOO_MANY_CONFIRMATION_ATTEMPTS = '96104'; + const US_BANK_ACCOUNT_VERIFICATION_UNABLE_TO_CONFIRM_DEPOSIT_AMOUNTS = '96105'; + const US_BANK_ACCOUNT_VERIFICATION_INVALID_DEPOSIT_AMOUNTS = '96106'; - const VERIFICATION_OPTIONS_AMOUNT_CANNOT_BE_NEGATIVE = '94201'; - const VERIFICATION_OPTIONS_AMOUNT_FORMAT_IS_INVALID = '94202'; - const VERIFICATION_OPTIONS_AMOUNT_IS_TOO_LARGE = '94207'; - const VERIFICATION_OPTIONS_AMOUNT_NOT_SUPPORTED_BY_PROCESSOR = '94203'; - const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_ID_IS_INVALID = '94204'; - const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_IS_SUSPENDED = '94205'; - const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_IS_FORBIDDEN = '94206'; + const VERIFICATION_OPTIONS_AMOUNT_CANNOT_BE_NEGATIVE = '94201'; + const VERIFICATION_OPTIONS_AMOUNT_FORMAT_IS_INVALID = '94202'; + const VERIFICATION_OPTIONS_AMOUNT_IS_TOO_LARGE = '94207'; + const VERIFICATION_OPTIONS_AMOUNT_NOT_SUPPORTED_BY_PROCESSOR = '94203'; + const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_ID_IS_INVALID = '94204'; + const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_IS_SUSPENDED = '94205'; + const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_IS_FORBIDDEN = '94206'; + const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_CANNOT_BE_SUB_MERCHANT_ACCOUNT = '94208'; } class_alias('Braintree\Error\Codes', 'Braintree_Error_Codes'); diff --git a/lib/Braintree/Error/ErrorCollection.php b/lib/Braintree/Error/ErrorCollection.php index 6822214..79690e0 100644 --- a/lib/Braintree/Error/ErrorCollection.php +++ b/lib/Braintree/Error/ErrorCollection.php @@ -13,7 +13,6 @@ * @package Braintree * @subpackage Errors * @category Errors - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read object $errors */ diff --git a/lib/Braintree/Error/Validation.php b/lib/Braintree/Error/Validation.php index 7442f0c..79a4cc1 100644 --- a/lib/Braintree/Error/Validation.php +++ b/lib/Braintree/Error/Validation.php @@ -9,11 +9,10 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. * * @property-read string $attribute * @property-read string $code diff --git a/lib/Braintree/Error/ValidationErrorCollection.php b/lib/Braintree/Error/ValidationErrorCollection.php index 91de9aa..ff3683e 100644 --- a/lib/Braintree/Error/ValidationErrorCollection.php +++ b/lib/Braintree/Error/ValidationErrorCollection.php @@ -8,11 +8,10 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. * * @property-read array $errors * @property-read array $nested diff --git a/lib/Braintree/EuropeBankAccount.php b/lib/Braintree/EuropeBankAccount.php index cae9354..b661e70 100644 --- a/lib/Braintree/EuropeBankAccount.php +++ b/lib/Braintree/EuropeBankAccount.php @@ -11,7 +11,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $account-holder-name * @property-read string $bic diff --git a/lib/Braintree/Exception.php b/lib/Braintree/Exception.php index c3ad854..58d0805 100644 --- a/lib/Braintree/Exception.php +++ b/lib/Braintree/Exception.php @@ -6,7 +6,6 @@ * * @package Braintree * @subpackage Exception - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Exception extends \Exception { diff --git a/lib/Braintree/Exception/Authentication.php b/lib/Braintree/Exception/Authentication.php index 8c2304f..1f87746 100644 --- a/lib/Braintree/Exception/Authentication.php +++ b/lib/Braintree/Exception/Authentication.php @@ -9,7 +9,6 @@ * * @package Braintree * @subpackage Exception - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Authentication extends Exception { diff --git a/lib/Braintree/Exception/Authorization.php b/lib/Braintree/Exception/Authorization.php index 47ad3a4..d6d9b2a 100644 --- a/lib/Braintree/Exception/Authorization.php +++ b/lib/Braintree/Exception/Authorization.php @@ -11,7 +11,6 @@ * * @package Braintree * @subpackage Exception - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Authorization extends Exception { diff --git a/lib/Braintree/Exception/Connection.php b/lib/Braintree/Exception/Connection.php new file mode 100644 index 0000000..474cdbb --- /dev/null +++ b/lib/Braintree/Exception/Connection.php @@ -0,0 +1,17 @@ +_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 index f28e400..d84ce21 100644 --- a/lib/Braintree/FacilitatorDetails.php +++ b/lib/Braintree/FacilitatorDetails.php @@ -1,6 +1,11 @@ == 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 7936b65..616be51 100644 --- a/lib/Braintree/Http.php +++ b/lib/Braintree/Http.php @@ -1,11 +1,11 @@ _config = $config; } - public function delete($path) + public function delete($path, $params = null) { - $response = $this->_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']); } @@ -41,7 +44,22 @@ public function post($path, $params = null) { $response = $this->_doRequest('POST', $path, $this->_buildXml($params)); $responseCode = $response['status']; - if($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); + } else { + Util::throwStatusCodeException($responseCode); + } + } + + public function postMultipart($path, $params, $file) + { + $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 || $responseCode == 400) { return Xml::buildArrayFromXml($response['body']); } else { Util::throwStatusCodeException($responseCode); @@ -52,7 +70,7 @@ public function put($path, $params = null) { $response = $this->_doRequest('PUT', $path, $this->_buildXml($params)); $responseCode = $response['status']; - if($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { return Xml::buildArrayFromXml($response['body']); } else { Util::throwStatusCodeException($responseCode); @@ -68,7 +86,6 @@ private function _getHeaders() { return [ 'Accept: application/xml', - 'Content-Type: application/xml', ]; } @@ -96,22 +113,34 @@ public function useClientCredentials() $this->_useClientCredentials = true; } - private function _doRequest($httpVerb, $path, $requestBody = null) + private function _doRequest($httpVerb, $path, $requestBody = null, $file = null, $headers = null) { - return $this->_doUrlRequest($httpVerb, $this->_config->baseUrl() . $path, $requestBody); + return $this->_doUrlRequest($httpVerb, $this->_config->baseUrl() . $path, $requestBody, $file, $headers); } - public function _doUrlRequest($httpVerb, $url, $requestBody = null) + public function _doUrlRequest($httpVerb, $url, $requestBody = null, $file = null, $customHeaders = null) { $curl = curl_init(); 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'); - $headers = $this->_getHeaders($curl); - $headers[] = 'User-Agent: Braintree PHP Library ' . Version::get(); - $headers[] = 'X-ApiVersion: ' . Configuration::API_VERSION; + 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'])) { @@ -120,33 +149,42 @@ public function _doUrlRequest($httpVerb, $url, $requestBody = null) } else if (isset($authorization['token'])) { $headers[] = 'Authorization: Bearer ' . $authorization['token']; } - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - // curl_setopt($curl, CURLOPT_VERBOSE, true); if ($this->_config->sslOn()) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); 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()) { + 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)) { + 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(); @@ -155,12 +193,59 @@ public function _doUrlRequest($httpVerb, $url, $requestBody = null) curl_close($curl); if ($this->_config->sslOn()) { if ($httpStatus == 0) { - throw new 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; 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 2f8e46e..ba0d272 100644 --- a/lib/Braintree/Instance.php +++ b/lib/Braintree/Instance.php @@ -4,7 +4,6 @@ /** * Braintree Class Instance template * - * @copyright 2015 Braintree, a division of PayPal, Inc. * @abstract */ abstract class Instance 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/MerchantAccount.php b/lib/Braintree/MerchantAccount.php index 5bde533..0797e23 100644 --- a/lib/Braintree/MerchantAccount.php +++ b/lib/Braintree/MerchantAccount.php @@ -1,6 +1,16 @@ _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']; @@ -137,6 +162,9 @@ private function _doUpdate($subPath, $params) 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( 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 index d2f35f2..60de5fb 100644 --- a/lib/Braintree/OAuthCredentials.php +++ b/lib/Braintree/OAuthCredentials.php @@ -6,8 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. - * */ class OAuthCredentials extends Base { diff --git a/lib/Braintree/OAuthGateway.php b/lib/Braintree/OAuthGateway.php index 9cf892f..4e2d1b2 100644 --- a/lib/Braintree/OAuthGateway.php +++ b/lib/Braintree/OAuthGateway.php @@ -7,7 +7,6 @@ * Creates and manages Braintree Addresses * * @package Braintree - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class OAuthGateway { @@ -109,11 +108,13 @@ 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)); - $url = $this->_config->baseUrl() . '/oauth/connect?' . $queryString; - return $url . '&signature=' . $this->computeSignature($url) . '&algorithm=SHA256'; + return $this->_config->baseUrl() . '/oauth/connect?' . $queryString; } + /** + * @deprecated since version 3.26.1 + */ public function computeSignature($url) { $key = hash('sha256', $this->_config->getClientSecret(), true); diff --git a/lib/Braintree/OAuthResult.php b/lib/Braintree/OAuthResult.php index 5a7de30..9c3e4e6 100644 --- a/lib/Braintree/OAuthResult.php +++ b/lib/Braintree/OAuthResult.php @@ -6,8 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. - * */ class OAuthResult extends Base { 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/PartnerMerchant.php b/lib/Braintree/PartnerMerchant.php index c0ee7d0..d5ac152 100644 --- a/lib/Braintree/PartnerMerchant.php +++ b/lib/Braintree/PartnerMerchant.php @@ -8,7 +8,6 @@ * Creates an instance of PartnerMerchants * * @package Braintree - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $merchantPublicId * @property-read string $publicKey diff --git a/lib/Braintree/PayPalAccount.php b/lib/Braintree/PayPalAccount.php index 7f09581..7cfbf97 100644 --- a/lib/Braintree/PayPalAccount.php +++ b/lib/Braintree/PayPalAccount.php @@ -6,7 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,12 +16,17 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * + * @property-read string $billingAgreementId + * @property-read \DateTime $createdAt * @property-read string $customerId + * @property-read boolean $default * @property-read string $email - * @property-read string $token * @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 { diff --git a/lib/Braintree/PayPalAccountGateway.php b/lib/Braintree/PayPalAccountGateway.php index 1b0933e..9302a17 100644 --- a/lib/Braintree/PayPalAccountGateway.php +++ b/lib/Braintree/PayPalAccountGateway.php @@ -8,7 +8,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -19,7 +18,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class PayPalAccountGateway { diff --git a/lib/Braintree/PaymentInstrumentType.php b/lib/Braintree/PaymentInstrumentType.php index 9be065f..8218e42 100644 --- a/lib/Braintree/PaymentInstrumentType.php +++ b/lib/Braintree/PaymentInstrumentType.php @@ -7,7 +7,13 @@ class PaymentInstrumentType const COINBASE_ACCOUNT = 'coinbase_account'; const EUROPE_BANK_ACCOUNT = 'europe_bank_account'; const CREDIT_CARD = 'credit_card'; + const VISA_CHECKOUT_CARD = 'visa_checkout_card'; + const MASTERPASS_CARD = 'masterpass_card'; const APPLE_PAY_CARD = 'apple_pay_card'; const ANDROID_PAY_CARD = 'android_pay_card'; + const VENMO_ACCOUNT = 'venmo_account'; + const US_BANK_ACCOUNT = 'us_bank_account'; + const IDEAL_PAYMENT = 'ideal_payment'; + const SAMSUNG_PAY_CARD = 'samsung_pay_card'; } class_alias('Braintree\PaymentInstrumentType', 'Braintree_PaymentInstrumentType'); diff --git a/lib/Braintree/PaymentMethod.php b/lib/Braintree/PaymentMethod.php index ed2bfbe..f620a38 100644 --- a/lib/Braintree/PaymentMethod.php +++ b/lib/Braintree/PaymentMethod.php @@ -6,7 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,8 +16,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. - * */ class PaymentMethod extends Base { @@ -39,9 +36,9 @@ public static function update($token, $attribs) return Configuration::gateway()->paymentMethod()->update($token, $attribs); } - public static function delete($token) + public static function delete($token, $options=[]) { - return Configuration::gateway()->paymentMethod()->delete($token); + 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 index 630d691..df3dd58 100644 --- a/lib/Braintree/PaymentMethodGateway.php +++ b/lib/Braintree/PaymentMethodGateway.php @@ -8,7 +8,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -19,8 +18,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. - * */ class PaymentMethodGateway { @@ -56,25 +53,7 @@ public function find($token) try { $path = $this->_config->merchantPath() . '/payment_methods/any/' . $token; $response = $this->_http->get($path); - 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['venmoAccount'])) { - return VenmoAccount::factory($response['venmoAccount']); - } else if (is_array($response)) { - return UnknownPaymentMethod::factory($response); - } + return PaymentMethodParser::parsePaymentMethod($response); } catch (Exception\NotFound $e) { throw new Exception\NotFound( 'payment method with token ' . $token . ' not found' @@ -88,30 +67,35 @@ public function update($token, $attribs) return $this->_doUpdate('/payment_methods/any/' . $token, ['payment_method' => $attribs]); } - public function delete($token) + public function delete($token, $options=[]) { + Util::verifyKeys(self::deleteSignature(), $options); $this->_validateId($token); - $path = $this->_config->merchantPath() . '/payment_methods/any/' . $token; - $this->_http->delete($path); - return new Result\Successful(); + $queryString = ""; + if (!empty($options)) { + $queryString = "?" . http_build_query(Util::camelCaseToDelimiterArray($options, '_')); + } + return $this->_doDelete('/payment_methods/any/' . $token . $queryString); } - public function grant($sharedPaymentMethodToken, $allowVaulting) + public function grant($sharedPaymentMethodToken, $attribs=[]) { - return $this->_doCreate( + if (is_bool($attribs) === true) { + $attribs = ['allow_vaulting' => $attribs]; + } + $options = [ 'shared_payment_method_token' => $sharedPaymentMethodToken ]; + + return $this->_doGrant( '/payment_methods/grant', [ - 'payment_method' => [ - 'shared_payment_method_token' => $sharedPaymentMethodToken, - 'allow_vaulting' => $allowVaulting - ] + 'payment_method' => array_merge($attribs, $options) ] ); } public function revoke($sharedPaymentMethodToken) { - return $this->_doCreate( + return $this->_doRevoke( '/payment_methods/revoke', [ 'payment_method' => [ @@ -129,7 +113,25 @@ private static function baseSignature() 'makeDefault', 'verificationMerchantAccountId', 'verifyCard', - 'verificationAmount' + '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', @@ -149,7 +151,7 @@ private static function baseSignature() public static function createSignature() { - $signature = array_merge(self::baseSignature(), ['customerId']); + $signature = array_merge(self::baseSignature(), ['customerId', 'paypalRefreshToken', 'paypalVaultWithoutUpgrade']); return $signature; } @@ -170,6 +172,11 @@ public static function updateSignature() return $signature; } + private static function deleteSignature() + { + return ['revokeAllGrants']; + } + /** * sends the create request to the gateway * @@ -186,6 +193,22 @@ public function _doCreate($subPath, $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 * @@ -202,6 +225,21 @@ public function _doUpdate($subPath, $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 * @@ -217,61 +255,43 @@ public function _doUpdate($subPath, $params) */ private function _verifyGatewayResponse($response) { - if (isset($response['creditCard'])) { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else if (($response)) { return new Result\Successful( - CreditCard::factory($response['creditCard']), + PaymentMethodParser::parsePaymentMethod($response), 'paymentMethod' ); - } else if (isset($response['paypalAccount'])) { - return new Result\Successful( - PayPalAccount::factory($response['paypalAccount']), - "paymentMethod" - ); - } else if (isset($response['coinbaseAccount'])) { - return new Result\Successful( - CoinbaseAccount::factory($response['coinbaseAccount']), - "paymentMethod" - ); - } else if (isset($response['applePayCard'])) { - return new Result\Successful( - ApplePayCard::factory($response['applePayCard']), - "paymentMethod" - ); - } else if (isset($response['androidPayCard'])) { - return new Result\Successful( - AndroidPayCard::factory($response['androidPayCard']), - "paymentMethod" - ); - } else if (isset($response['amexExpressCheckoutCard'])) { - return new Result\Successful( - AmexExpressCheckoutCard::factory($response['amexExpressCheckoutCard']), - "paymentMethod" - ); - } else if (isset($response['europeBankAccount'])) { - return new Result\Successful( - EuropeBankAccount::factory($response['europeBankAccount']), - "paymentMethod" - ); - } else if (isset($response['venmoAccount'])) { - return new Result\Successful( - VenmoAccount::factory($response['venmoAccount']), - "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" + 'paymentMethodNonce' ); - } else if (isset($response['apiErrorResponse'])) { - return new Result\Error($response['apiErrorResponse']); - } else if (is_array($response)) { - return new Result\Successful( - UnknownPaymentMethod::factory($response), - "paymentMethod" + } 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 payment method or apiErrorResponse' + 'Expected success or apiErrorResponse' ); } } diff --git a/lib/Braintree/PaymentMethodNonce.php b/lib/Braintree/PaymentMethodNonce.php index 4e04efb..4501fa9 100644 --- a/lib/Braintree/PaymentMethodNonce.php +++ b/lib/Braintree/PaymentMethodNonce.php @@ -6,7 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,8 +16,12 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. - * + * + * @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 { @@ -50,6 +53,10 @@ protected function _initialize($nonceAttributes) 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 index 82dcfae..40ad8af 100644 --- a/lib/Braintree/PaymentMethodNonceGateway.php +++ b/lib/Braintree/PaymentMethodNonceGateway.php @@ -6,7 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,8 +16,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. - * */ class 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 a92c604..a65b15a 100644 --- a/lib/Braintree/Plan.php +++ b/lib/Braintree/Plan.php @@ -1,6 +1,23 @@ _returnObjectNames as $returnObjectName) { - array_push($objects, $this->$returnObjectName); + array_push($objects, $returnObjectName); } return __CLASS__ . '[' . implode(', ', $objects) . ']'; } 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 index 971d805..11552e1 100644 --- a/lib/Braintree/RiskData.php +++ b/lib/Braintree/RiskData.php @@ -1,6 +1,11 @@ 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 3a270a8..2906f20 100644 --- a/lib/Braintree/SettlementBatchSummary.php +++ b/lib/Braintree/SettlementBatchSummary.php @@ -1,6 +1,9 @@ == 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 2015 Braintree, a division of PayPal, Inc. + * + * @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 Subscription extends Base { @@ -64,6 +94,10 @@ protected function _initialize($attributes) $this->_set('descriptor', new Descriptor($attributes['descriptor'])); } + if (isset($attributes['description'])) { + $this->_set('description', $attributes['description']); + } + $statusHistory = []; if (isset($attributes['statusHistory'])) { foreach ($attributes['statusHistory'] AS $history) { @@ -128,9 +162,9 @@ public static function update($subscriptionId, $attributes) return Configuration::gateway()->subscription()->update($subscriptionId, $attributes); } - public static function retryCharge($subscriptionId, $amount = null) + public static function retryCharge($subscriptionId, $amount = null, $submitForSettlement = false) { - return Configuration::gateway()->subscription()->retryCharge($subscriptionId, $amount); + return Configuration::gateway()->subscription()->retryCharge($subscriptionId, $amount, $submitForSettlement); } public static function cancel($subscriptionId) diff --git a/lib/Braintree/Subscription/StatusDetails.php b/lib/Braintree/Subscription/StatusDetails.php index fda998a..9f588ce 100644 --- a/lib/Braintree/Subscription/StatusDetails.php +++ b/lib/Braintree/Subscription/StatusDetails.php @@ -8,12 +8,13 @@ * Creates an instance of StatusDetails, as part of a subscription response * * @package Braintree - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $price + * @property-read string $currencyIsoCode + * @property-read string $planId * @property-read string $balance * @property-read string $status - * @property-read string $timestamp + * @property-read \DateTime $timestamp * @property-read string $subscriptionSource * @property-read string $user */ diff --git a/lib/Braintree/SubscriptionGateway.php b/lib/Braintree/SubscriptionGateway.php index 8e72764..1d366c5 100644 --- a/lib/Braintree/SubscriptionGateway.php +++ b/lib/Braintree/SubscriptionGateway.php @@ -8,12 +8,11 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. */ class SubscriptionGateway { @@ -94,13 +93,16 @@ public function update($subscriptionId, $attributes) return $this->_verifyGatewayResponse($response); } - public function retryCharge($subscriptionId, $amount = null) + 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]); @@ -134,7 +136,11 @@ private static function _createSignature() 'trialDurationUnit', 'trialPeriod', ['descriptor' => ['name', 'phone', 'url']], - ['options' => ['doNotInheritAddOnsOrDiscounts', 'startImmediately']], + ['options' => [ + 'doNotInheritAddOnsOrDiscounts', + 'startImmediately', + ['paypal' => ['description']] + ]], ], self::_addOnDiscountSignature() ); @@ -147,7 +153,12 @@ private static function _updateSignature() 'merchantAccountId', 'numberOfBillingCycles', 'paymentMethodToken', 'planId', 'paymentMethodNonce', 'id', 'neverExpires', 'price', ['descriptor' => ['name', 'phone', 'url']], - ['options' => ['prorateCharges', 'replaceAllAddOnsAndDiscounts', 'revertSubscriptionOnProrationFailure']], + ['options' => [ + 'prorateCharges', + 'replaceAllAddOnsAndDiscounts', + 'revertSubscriptionOnProrationFailure', + ['paypal' => ['description']] + ]], ], self::_addOnDiscountSignature() ); diff --git a/lib/Braintree/SubscriptionSearch.php b/lib/Braintree/SubscriptionSearch.php index fc30622..ba2a5ef 100644 --- a/lib/Braintree/SubscriptionSearch.php +++ b/lib/Braintree/SubscriptionSearch.php @@ -63,5 +63,10 @@ public static function ids() { return new MultipleValueNode('ids'); } + + public static function createdAt() + { + return new RangeNode('created_at'); + } } class_alias('Braintree\SubscriptionSearch', 'Braintree_SubscriptionSearch'); diff --git a/lib/Braintree/Test/CreditCardNumbers.php b/lib/Braintree/Test/CreditCardNumbers.php index 30173d3..593a079 100644 --- a/lib/Braintree/Test/CreditCardNumbers.php +++ b/lib/Braintree/Test/CreditCardNumbers.php @@ -10,7 +10,6 @@ * * @package Braintree * @subpackage Test - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class CreditCardNumbers { @@ -25,6 +24,12 @@ class CreditCardNumbers '6011111111111117', '6011000990139424', ]; + + public static $elo = '5066991111111118'; + public static $eloCards = [ + '5066991111111118' + ]; + public static $JCBs = [ '3530111333300000', '3566002020360505', @@ -63,11 +68,16 @@ class CreditCardNumbers 'InsufficientPoints' => "371544868764018", ]; + public static $disputes = [ + 'Chargeback' => '4023898493988028', + ]; + public static function getAll() { return array_merge( self::$amExes, self::$discoverCards, + self::$eloCards, self::$masterCards, self::$visas ); diff --git a/lib/Braintree/Test/MerchantAccount.php b/lib/Braintree/Test/MerchantAccount.php index 581eccd..cc00c67 100644 --- a/lib/Braintree/Test/MerchantAccount.php +++ b/lib/Braintree/Test/MerchantAccount.php @@ -6,7 +6,6 @@ * * @package Braintree * @subpackage Test - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class MerchantAccount { diff --git a/lib/Braintree/Test/Nonces.php b/lib/Braintree/Test/Nonces.php index 3e73d68..906f582 100644 --- a/lib/Braintree/Test/Nonces.php +++ b/lib/Braintree/Test/Nonces.php @@ -6,7 +6,6 @@ * * @package Braintree * @subpackage Test - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,7 +16,6 @@ * * @package Braintree * @subpackage Test - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Nonces { @@ -66,5 +64,17 @@ class Nonces public static $sepa = "fake-sepa-bank-account-nonce"; public static $gatewayRejectedFraud = "fake-gateway-rejected-fraud-nonce"; public static $venmoAccount = "fake-venmo-account-nonce"; + public static $visaCheckoutAmEx = "fake-visa-checkout-amex-nonce"; + public static $visaCheckoutDiscover = "fake-visa-checkout-discover-nonce"; + public static $visaCheckoutMasterCard = "fake-visa-checkout-mastercard-nonce"; + public static $visaCheckoutVisa = "fake-visa-checkout-visa-nonce"; + public static $masterpassAmEx = "fake-masterpass-amex-nonce"; + public static $masterpassDiscover = "fake-masterpass-discover-nonce"; + public static $masterpassMasterCard = "fake-masterpass-mastercard-nonce"; + public static $masterpassVisa = "fake-masterpass-visa-nonce"; + public static $samsungPayAmEx = "tokensam_fake_american_express"; + public static $samsungPayDiscover = "tokensam_fake_discover"; + public static $samsungPayMasterCard = "tokensam_fake_mastercard"; + public static $samsungPayVisa = "tokensam_fake_visa"; } class_alias('Braintree\Test\Nonces', 'Braintree_Test_Nonces'); diff --git a/lib/Braintree/Test/Transaction.php b/lib/Braintree/Test/Transaction.php index cf2bcc0..59c26ae 100644 --- a/lib/Braintree/Test/Transaction.php +++ b/lib/Braintree/Test/Transaction.php @@ -8,8 +8,6 @@ * * The constants in this class can be used to create transactions with * the desired status in the sandbox environment. - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Transaction { diff --git a/lib/Braintree/Test/TransactionAmounts.php b/lib/Braintree/Test/TransactionAmounts.php index 8a9e930..9258d72 100644 --- a/lib/Braintree/Test/TransactionAmounts.php +++ b/lib/Braintree/Test/TransactionAmounts.php @@ -9,11 +9,11 @@ * * @package Braintree * @subpackage Test - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class TransactionAmounts { - public static $authorize = '1000.00'; - public static $decline = '2000.00'; + public static $authorize = '1000.00'; + public static $decline = '2000.00'; + public static $hardDecline = '2015.00'; } class_alias('Braintree\Test\TransactionAmounts', 'Braintree_Test_TransactionAmounts'); diff --git a/lib/Braintree/Test/VenmoSdk.php b/lib/Braintree/Test/VenmoSdk.php index dc5311f..34b8fc2 100644 --- a/lib/Braintree/Test/VenmoSdk.php +++ b/lib/Braintree/Test/VenmoSdk.php @@ -6,7 +6,6 @@ * * @package Braintree * @subpackage Test - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class VenmoSdk { diff --git a/lib/Braintree/ThreeDSecureInfo.php b/lib/Braintree/ThreeDSecureInfo.php index a04ec39..f7abae2 100644 --- a/lib/Braintree/ThreeDSecureInfo.php +++ b/lib/Braintree/ThreeDSecureInfo.php @@ -1,6 +1,11 @@ == 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 2015 Braintree, a division of PayPal, Inc. * * + * @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 Braintree\Transaction\AddressDetails $billingDetails transaction billing address - * @property-read string $createdAt transaction created timestamp - * @property-read Braintree\ApplePayCardDetails $applePayCardDetails transaction Apple Pay card info - * @property-read Braintree\AndroidPayCardDetails $androidPayCardDetails transaction Android Pay card info - * @property-read Braintree\AmexExpressCheckoutCardDetails $amexExpressCheckoutCardDetails transaction Amex Express Checkout card info - * @property-read Braintree\CreditCardDetails $creditCardDetails transaction credit card info - * @property-read Braintree\CoinbaseAccountDetails $coinbaseDetails transaction Coinbase account info - * @property-read Braintree\PayPalAccountDetails $paypalDetails transaction paypal account info - * @property-read Braintree\Transaction\CustomerDetails $customerDetails transaction customer info - * @property-read Braintree\VenmoAccount $venmoAccountDetails transaction Venmo Account 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 string $additionalProcessorResponse raw response from processor - * @property-read Braintree\Transaction\AddressDetails $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 Braintree\Disbursement $disbursementDetails populated when transaction is disbursed - * @property-read Braintree\Dispute $disputes populated when transaction is disputed + * @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 * */ @@ -221,6 +261,7 @@ class Transaction extends Base // 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 @@ -250,6 +291,30 @@ protected function _initialize($transactionAttribs) ); } + if (isset($transactionAttribs['masterpassCard'])) { + $this->_set('masterpassCardDetails', + new Transaction\MasterpassCardDetails( + $transactionAttribs['masterpassCard'] + ) + ); + } + + if (isset($transactionAttribs['visaCheckoutCard'])) { + $this->_set('visaCheckoutCardDetails', + new Transaction\VisaCheckoutCardDetails( + $transactionAttribs['visaCheckoutCard'] + ) + ); + } + + if (isset($transactionAttribs['samsungPayCard'])) { + $this->_set('samsungPayCardDetails', + new Transaction\SamsungPayCardDetails( + $transactionAttribs['samsungPayCard'] + ) + ); + } + if (isset($transactionAttribs['amexExpressCheckoutCard'])) { $this->_set('amexExpressCheckoutCardDetails', new Transaction\AmexExpressCheckoutCardDetails( @@ -282,10 +347,18 @@ protected function _initialize($transactionAttribs) ); } - if (isset($transactionAttribs['europeBankAccount'])) { - $this->_set('europeBankAccount', - new Transaction\EuropeBankAccountDetails( - $transactionAttribs['europeBankAccount'] + if (isset($transactionAttribs['usBankAccount'])) { + $this->_set('usBankAccount', + new Transaction\UsBankAccountDetails( + $transactionAttribs['usBankAccount'] + ) + ); + } + + if (isset($transactionAttribs['idealPayment'])) { + $this->_set('idealPayment', + new Transaction\IdealPaymentDetails( + $transactionAttribs['idealPayment'] ) ); } @@ -378,12 +451,24 @@ protected function _initialize($transactionAttribs) } $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'])); } @@ -437,11 +522,16 @@ public function vaultCustomer() } } - /** @return bool */ + /** @return boolean */ public function isDisbursed() { return $this->disbursementDetails->isValid(); } + /** @return line items */ + public function lineItems() { + return Configuration::gateway()->transactionLineItem()->findAll($this->id); + } + /** * factory method: returns an instance of Transaction * to the requesting method, with populated properties diff --git a/lib/Braintree/Transaction/AddressDetails.php b/lib/Braintree/Transaction/AddressDetails.php index d925721..597a208 100644 --- a/lib/Braintree/Transaction/AddressDetails.php +++ b/lib/Braintree/Transaction/AddressDetails.php @@ -9,7 +9,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $firstName * @property-read string $lastName diff --git a/lib/Braintree/Transaction/AmexExpressCheckoutCardDetails.php b/lib/Braintree/Transaction/AmexExpressCheckoutCardDetails.php index fbc9c3f..a5a29ac 100644 --- a/lib/Braintree/Transaction/AmexExpressCheckoutCardDetails.php +++ b/lib/Braintree/Transaction/AmexExpressCheckoutCardDetails.php @@ -7,7 +7,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -16,7 +15,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $cardType * @property-read string $bin diff --git a/lib/Braintree/Transaction/AndroidPayCardDetails.php b/lib/Braintree/Transaction/AndroidPayCardDetails.php index 6e5eeb4..a3a52c5 100644 --- a/lib/Braintree/Transaction/AndroidPayCardDetails.php +++ b/lib/Braintree/Transaction/AndroidPayCardDetails.php @@ -8,7 +8,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,7 +16,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $bin * @property-read string $default diff --git a/lib/Braintree/Transaction/ApplePayCardDetails.php b/lib/Braintree/Transaction/ApplePayCardDetails.php index e9ce5d4..f0a2be4 100644 --- a/lib/Braintree/Transaction/ApplePayCardDetails.php +++ b/lib/Braintree/Transaction/ApplePayCardDetails.php @@ -8,7 +8,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,7 +16,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $cardType * @property-read string $paymentInstrumentName diff --git a/lib/Braintree/Transaction/CoinbaseDetails.php b/lib/Braintree/Transaction/CoinbaseDetails.php index e177f2e..60c1526 100644 --- a/lib/Braintree/Transaction/CoinbaseDetails.php +++ b/lib/Braintree/Transaction/CoinbaseDetails.php @@ -8,7 +8,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,7 +16,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $token * @property-read string $userId diff --git a/lib/Braintree/Transaction/CreditCardDetails.php b/lib/Braintree/Transaction/CreditCardDetails.php index 7886b17..26b7a34 100644 --- a/lib/Braintree/Transaction/CreditCardDetails.php +++ b/lib/Braintree/Transaction/CreditCardDetails.php @@ -9,10 +9,10 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $bin * @property-read string $cardType + * @property-read string $cardholderName * @property-read string $expirationDate * @property-read string $expirationMonth * @property-read string $expirationYear diff --git a/lib/Braintree/Transaction/CustomerDetails.php b/lib/Braintree/Transaction/CustomerDetails.php index 45e5f11..8aa6913 100644 --- a/lib/Braintree/Transaction/CustomerDetails.php +++ b/lib/Braintree/Transaction/CustomerDetails.php @@ -9,7 +9,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $company * @property-read string $email diff --git a/lib/Braintree/Transaction/EuropeBankAccountDetails.php b/lib/Braintree/Transaction/EuropeBankAccountDetails.php index ee60b24..fc783a7 100644 --- a/lib/Braintree/Transaction/EuropeBankAccountDetails.php +++ b/lib/Braintree/Transaction/EuropeBankAccountDetails.php @@ -9,7 +9,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $accountHolderName * @property-read string $bic diff --git a/lib/Braintree/Transaction/IdealPaymentDetails.php b/lib/Braintree/Transaction/IdealPaymentDetails.php new file mode 100644 index 0000000..08688e5 --- /dev/null +++ b/lib/Braintree/Transaction/IdealPaymentDetails.php @@ -0,0 +1,23 @@ +_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 index 36e263e..4c13a42 100644 --- a/lib/Braintree/Transaction/PayPalDetails.php +++ b/lib/Braintree/Transaction/PayPalDetails.php @@ -8,7 +8,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,16 +16,25 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * + * @property-read string $authorizationId + * @property-read string $captureId + * @property-read string $customField + * @property-read string $description + * @property-read string $imageUrl * @property-read string $payerEmail + * @property-read string $payerFirstName + * @property-read string $payerId + * @property-read string $payerLastName + * @property-read string $payerStatus * @property-read string $paymentId - * @property-read string $authorizationId + * @property-read string $refundId + * @property-read string $sellerProtectionStatus + * @property-read string $taxId + * @property-read string $taxIdType * @property-read string $token - * @property-read string $imageUrl * @property-read string $transactionFeeAmount * @property-read string $transactionFeeCurrencyIsoCode - * @property-read string $description */ class PayPalDetails extends Instance { diff --git a/lib/Braintree/Transaction/SamsungPayCardDetails.php b/lib/Braintree/Transaction/SamsungPayCardDetails.php new file mode 100644 index 0000000..b01b38c --- /dev/null +++ b/lib/Braintree/Transaction/SamsungPayCardDetails.php @@ -0,0 +1,52 @@ +_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 d9cc86c..b8e1f8f 100644 --- a/lib/Braintree/Transaction/StatusDetails.php +++ b/lib/Braintree/Transaction/StatusDetails.php @@ -8,13 +8,12 @@ * Creates an instance of StatusDetails, as part of a transaction response * * @package Braintree - * @copyright 2015 Braintree, a division of PayPal, Inc. * - * @property-read string $amount - * @property-read string $status - * @property-read string $timestamp - * @property-read string $transactionSource - * @property-read string $user + * @property-read string $amount + * @property-read string $status + * @property-read \DateTime $timestamp + * @property-read string $transactionSource + * @property-read string $user */ class StatusDetails extends Instance { diff --git a/lib/Braintree/Transaction/SubscriptionDetails.php b/lib/Braintree/Transaction/SubscriptionDetails.php index ad3bd7c..2748182 100644 --- a/lib/Braintree/Transaction/SubscriptionDetails.php +++ b/lib/Braintree/Transaction/SubscriptionDetails.php @@ -9,10 +9,9 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * - * @property-read string $billing_period_start_date - * @property-read string $billing_period_end_date + * @property-read string $billingPeriodStartDate + * @property-read string $billingPeriodEndDate */ class SubscriptionDetails extends Instance { diff --git a/lib/Braintree/Transaction/UsBankAccountDetails.php b/lib/Braintree/Transaction/UsBankAccountDetails.php new file mode 100644 index 0000000..72998f3 --- /dev/null +++ b/lib/Braintree/Transaction/UsBankAccountDetails.php @@ -0,0 +1,40 @@ +achMandate = $achMandate; + } +} +class_alias('Braintree\Transaction\UsBankAccountDetails', 'Braintree_Transaction_UsBankAccountDetails'); diff --git a/lib/Braintree/Transaction/VenmoAccountDetails.php b/lib/Braintree/Transaction/VenmoAccountDetails.php index 5bc77f5..b48baed 100644 --- a/lib/Braintree/Transaction/VenmoAccountDetails.php +++ b/lib/Braintree/Transaction/VenmoAccountDetails.php @@ -7,7 +7,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -16,7 +15,6 @@ * * @package Braintree * @subpackage Transaction - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $sourceDescription * @property-read string $token diff --git a/lib/Braintree/Transaction/VisaCheckoutCardDetails.php b/lib/Braintree/Transaction/VisaCheckoutCardDetails.php new file mode 100644 index 0000000..723b374 --- /dev/null +++ b/lib/Braintree/Transaction/VisaCheckoutCardDetails.php @@ -0,0 +1,52 @@ +_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 index d6bf2aa..4f5e32f 100644 --- a/lib/Braintree/TransactionGateway.php +++ b/lib/Braintree/TransactionGateway.php @@ -10,11 +10,10 @@ * * == 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 2015 Braintree, a division of PayPal, Inc. */ class TransactionGateway @@ -41,7 +40,7 @@ public function cloneTransaction($transactionId, $attribs) * @ignore * @access private * @param array $attribs - * @return object + * @return Result\Successful|Result\Error */ private function create($attribs) { @@ -120,6 +119,7 @@ public static function createSignature() 'recurring', 'serviceFeeAmount', 'sharedPaymentMethodToken', + 'sharedPaymentMethodNonce', 'sharedCustomerId', 'sharedShippingAddressId', 'sharedBillingAddressId', @@ -130,8 +130,11 @@ public static function createSignature() 'transactionSource', 'type', 'venmoSdkPaymentMethodCode', + 'shippingAmount', + 'discountAmount', + 'shipsFromPostalCode', ['riskData' => - ['customer_browser', 'customer_ip'] + ['customerBrowser', 'customerIp', 'customer_browser', 'customer_ip'] ], ['creditCard' => ['token', 'cardholderName', 'cvv', 'expirationDate', 'expirationMonth', 'expirationYear', 'number'], @@ -170,12 +173,21 @@ public static function createSignature() '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', @@ -189,13 +201,23 @@ public static function createSignature() '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' => ['payeeEmail']], - ['apple_pay_card' => ['number', 'cardholder_name', 'cryptogram', 'expiration_month', 'expiration_year']], + ['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' => @@ -208,10 +230,49 @@ public static function createSignature() 'lodgingCheckInDate', 'lodgingCheckOutDate', 'lodgingName', - 'roomRate' + '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'], ] ]; } @@ -276,7 +337,7 @@ public function find($id) /** * new sale * @param array $attribs - * @return array + * @return Result\Successful|Result\Error */ public function sale($attribs) { @@ -301,7 +362,7 @@ public function saleNoValidate($attribs) * * 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} + * 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 @@ -340,10 +401,14 @@ public function fetch($query, $ids) $path = $this->_config->merchantPath() . '/transactions/advanced_search'; $response = $this->_http->post($path, ['search' => $criteria]); - return Util::extractattributeasarray( - $response['creditCardTransactions'], - 'transaction' - ); + if (array_key_exists('creditCardTransactions', $response)) { + return Util::extractattributeasarray( + $response['creditCardTransactions'], + 'transaction' + ); + } else { + throw new Exception\DownForMaintenance(); + } } /** @@ -459,7 +524,7 @@ public function refund($transactionId, $amount_or_options = null) * @ignore * @param var $subPath * @param array $params - * @return mixed + * @return Result\Successful|Result\Error */ public function _doCreate($subPath, $params) { 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 1e85b2e..5271b0b 100644 --- a/lib/Braintree/TransactionSearch.php +++ b/lib/Braintree/TransactionSearch.php @@ -77,6 +77,7 @@ public static function creditCardCardType() CreditCard::CHINA_UNION_PAY, CreditCard::DINERS_CLUB_INTERNATIONAL, CreditCard::DISCOVER, + CreditCard::ELO, CreditCard::JCB, CreditCard::LASER, CreditCard::MAESTRO, diff --git a/lib/Braintree/TransparentRedirect.php b/lib/Braintree/TransparentRedirect.php index 84a1d03..30f274d 100644 --- a/lib/Braintree/TransparentRedirect.php +++ b/lib/Braintree/TransparentRedirect.php @@ -34,7 +34,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class TransparentRedirect { diff --git a/lib/Braintree/TransparentRedirectGateway.php b/lib/Braintree/TransparentRedirectGateway.php index 9d4ec09..3308af0 100644 --- a/lib/Braintree/TransparentRedirectGateway.php +++ b/lib/Braintree/TransparentRedirectGateway.php @@ -11,7 +11,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class TransparentRedirectGateway { diff --git a/lib/Braintree/UnknownPaymentMethod.php b/lib/Braintree/UnknownPaymentMethod.php index a88a054..74e7289 100644 --- a/lib/Braintree/UnknownPaymentMethod.php +++ b/lib/Braintree/UnknownPaymentMethod.php @@ -6,7 +6,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** @@ -17,7 +16,6 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * * @property-read string $token * @property-read string $imageUrl 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 @@ + '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', @@ -163,6 +209,8 @@ public static function cleanClassName($name) 'Braintree_PayPalAccount' => 'paypalAccount', 'Braintree\PayPalAccountGateway' => 'paypalAccount', 'Braintree_PayPalAccountGateway' => 'paypalAccount', + 'Braintree\UsBankAccountVerification' => 'usBankAccountVerification', + 'Braintree_UsBankAccountVerification' => 'usBankAccountVerification', ]; return $classNamesToResponseKeys[$name]; @@ -178,6 +226,8 @@ public static function buildClassName($name) $responseKeysToClassNames = [ 'creditCard' => 'Braintree\CreditCard', 'customer' => 'Braintree\Customer', + 'dispute' => 'Braintree\Dispute', + 'documentUpload' => 'Braintree\DocumentUpload', 'subscription' => 'Braintree\Subscription', 'transaction' => 'Braintree\Transaction', 'verification' => 'Braintree\CreditCardVerification', @@ -202,12 +252,11 @@ public static function buildClassName($name) */ 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); diff --git a/lib/Braintree/VenmoAccount.php b/lib/Braintree/VenmoAccount.php index acc0b1f..91f0da3 100644 --- a/lib/Braintree/VenmoAccount.php +++ b/lib/Braintree/VenmoAccount.php @@ -11,15 +11,15 @@ * * @package Braintree * @category Resources - * @copyright 2015 Braintree, a division of PayPal, Inc. * - * @property-read string $createdAt - * @property-read string $default - * @property-read string $updatedAt + * @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 string $imageUrl + * @property-read \DateTime $updatedAt * @property-read string $username * @property-read string $venmoUserId */ diff --git a/lib/Braintree/Version.php b/lib/Braintree/Version.php index b72b08d..35eb484 100644 --- a/lib/Braintree/Version.php +++ b/lib/Braintree/Version.php @@ -4,8 +4,6 @@ /** * Braintree Library Version * stores version information about the Braintree library - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Version { @@ -13,7 +11,7 @@ class Version * class constants */ const MAJOR = 3; - const MINOR = 15; + const MINOR = 39; const TINY = 0; /** diff --git a/lib/Braintree/VisaCheckoutCard.php b/lib/Braintree/VisaCheckoutCard.php new file mode 100644 index 0000000..5184a88 --- /dev/null +++ b/lib/Braintree/VisaCheckoutCard.php @@ -0,0 +1,154 @@ +== 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 e76f344..c669dc8 100644 --- a/lib/Braintree/WebhookNotification.php +++ b/lib/Braintree/WebhookNotification.php @@ -13,6 +13,8 @@ class WebhookNotification extends Base const SUB_MERCHANT_ACCOUNT_APPROVED = 'sub_merchant_account_approved'; const SUB_MERCHANT_ACCOUNT_DECLINED = 'sub_merchant_account_declined'; const TRANSACTION_DISBURSED = 'transaction_disbursed'; + const TRANSACTION_SETTLED = 'transaction_settled'; + const TRANSACTION_SETTLEMENT_DECLINED = 'transaction_settlement_declined'; const DISBURSEMENT_EXCEPTION = 'disbursement_exception'; const DISBURSEMENT = 'disbursement'; const DISPUTE_OPENED = 'dispute_opened'; @@ -21,32 +23,23 @@ class WebhookNotification extends Base const PARTNER_MERCHANT_CONNECTED = 'partner_merchant_connected'; const PARTNER_MERCHANT_DISCONNECTED = 'partner_merchant_disconnected'; const PARTNER_MERCHANT_DECLINED = 'partner_merchant_declined'; + const OAUTH_ACCESS_REVOKED = 'oauth_access_revoked'; const CHECK = 'check'; const ACCOUNT_UPDATER_DAILY_REPORT = 'account_updater_daily_report'; - - public static function parse($signature, $payload) - { - if (preg_match("/[^A-Za-z0-9+=\/\n]/", $payload) === 1) { - throw new Exception\InvalidSignature("payload contains illegal characters"); - } - - Configuration::assertGlobalHasAccessTokenOrKeys(); - self::_validateSignature($signature, $payload); - - $xml = base64_decode($payload); - $attributes = Xml::buildArrayFromXml($xml); - return self::factory($attributes['notification']); + const CONNECTED_MERCHANT_STATUS_TRANSITIONED = 'connected_merchant_status_transitioned'; + const CONNECTED_MERCHANT_PAYPAL_STATUS_CHANGED = 'connected_merchant_paypal_status_changed'; + const IDEAL_PAYMENT_COMPLETE = 'ideal_payment_complete'; + const IDEAL_PAYMENT_FAILED = 'ideal_payment_failed'; + const GRANTED_PAYMENT_INSTRUMENT_UPDATE = 'granted_payment_instrument_update'; + const GRANTED_PAYMENT_METHOD_REVOKED = 'granted_payment_method_revoked'; + const LOCAL_PAYMENT_COMPLETED = "local_payment_completed"; + + public static function parse($signature, $payload) { + return Configuration::gateway()->webhookNotification()->parse($signature, $payload); } - public static function verify($challenge) - { - if (!preg_match('/^[a-f0-9]{20,32}$/', $challenge)) { - throw new Exception\InvalidChallenge("challenge contains non-hex characters"); - } - Configuration::assertGlobalHasAccessTokenOrKeys(); - $publicKey = Configuration::publicKey(); - $digest = Digest::hexDigestSha1(Configuration::privateKey(), $challenge); - return "{$publicKey}|{$digest}"; + public static function verify($challenge) { + return Configuration::gateway()->webhookNotification()->verify($challenge); } public static function factory($attributes) @@ -56,42 +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] == Configuration::publicKey()) { - return $components[1]; - } - } - - return null; - } - - private static function _payloadMatches($signature, $payload) - { - $payloadSignature = Digest::hexDigestSha1(Configuration::privateKey(), $payload); - return Digest::secureCompare($signature, $payloadSignature); - } - - private static 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"); - } - } - 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 { @@ -118,6 +83,18 @@ protected function _initialize($attributes) $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'])); } @@ -126,6 +103,22 @@ protected function _initialize($attributes) $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 Error\ValidationErrorCollection($wrapperNode['errors'])); $this->_set('message', $wrapperNode['message']); 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 9cbde0e..350591c 100644 --- a/lib/Braintree/WebhookTesting.php +++ b/lib/Braintree/WebhookTesting.php @@ -3,338 +3,9 @@ class WebhookTesting { - public static function sampleNotification($kind, $id) + public static function sampleNotification($kind, $id, $sourceMerchantId = null) { - $payload = base64_encode(self::_sampleXml($kind, $id)) . "\n"; - $signature = Configuration::publicKey() . "|" . Digest::hexDigestSha1(Configuration::privateKey(), $payload); - - return [ - 'bt_signature' => $signature, - 'bt_payload' => $payload - ]; - } - - private static function _sampleXml($kind, $id) - { - 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::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::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::CHECK: - $subjectXml = self::_checkSampleXml(); - break; - case WebhookNotification::ACCOUNT_UPDATER_DAILY_REPORT: - $subjectXml = self::_accountUpdaterDailyReportSampleXml($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 _disputeOpenedSampleXml($id) - { - return " - - 250.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 - USD - 2014-03-01 - 2014-03-21 - chargeback - lost - fraud - ${id} - - ${id} - 250.00 - - 2014-03-21 - - "; - } - - private static function _disputeWonSampleXml($id) - { - return " - - 250.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 - - - submitted_for_settlement - 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 _accountUpdaterDailyReportSampleXml($id) - { - return " - - 2016-01-14 - link-to-csv-report - - "; - } - - 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; + 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 a84ed02..e938c40 100644 --- a/lib/Braintree/Xml.php +++ b/lib/Braintree/Xml.php @@ -5,8 +5,6 @@ * Braintree Xml parser and generator * PHP version 5 * superclass for Braintree XML parsing and generation - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Xml { diff --git a/lib/Braintree/Xml/Generator.php b/lib/Braintree/Xml/Generator.php index c3c33a2..a8dc3d6 100644 --- a/lib/Braintree/Xml/Generator.php +++ b/lib/Braintree/Xml/Generator.php @@ -8,15 +8,11 @@ /** * PHP version 5 - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ /** * Generates XML output from arrays using PHP's * built-in XMLWriter - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Generator { diff --git a/lib/Braintree/Xml/Parser.php b/lib/Braintree/Xml/Parser.php index 61c6295..1c0d05e 100644 --- a/lib/Braintree/Xml/Parser.php +++ b/lib/Braintree/Xml/Parser.php @@ -10,8 +10,6 @@ /** * Braintree XML Parser - * - * @copyright 2015 Braintree, a division of PayPal, Inc. */ class Parser { diff --git a/lib/ssl/api_braintreegateway_com.ca.crt b/lib/ssl/api_braintreegateway_com.ca.crt index 2d90f19..7dcf4af 100644 --- a/lib/ssl/api_braintreegateway_com.ca.crt +++ b/lib/ssl/api_braintreegateway_com.ca.crt @@ -189,3 +189,53 @@ PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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----- From 9a8b82cdaa5ec7c444efc70c4cdcb9a4020be137 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Wed, 20 Mar 2019 22:11:53 -0600 Subject: [PATCH 16/19] Update docs for SDK 3.39.0 update. --- gravity-forms-braintree.php | 2 +- readme.txt | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gravity-forms-braintree.php b/gravity-forms-braintree.php index 6b61e94..dbca71f 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.2.0 +Version: 1.3.0 Author URI: http://plugify.io */ diff --git a/readme.txt b/readme.txt index 130f265..3ac6829 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=hello Tags: credit card,braintree,gravity form,payment Requires at least: 3.8 Tested up to: 4.5 -Stable tag: 1.2.0 +Stable tag: 1.3.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -61,6 +61,9 @@ No filters are currently available for this pre-release version == Changelog == += 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. From ce85a2cc80ea1e6b3e8e57a7e3da3831a2bc4126 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Wed, 20 Mar 2019 23:03:56 -0600 Subject: [PATCH 17/19] Add exception message to debug log. Fix issue with where taxExempt should be attached. --- gravity-forms-braintree.php | 2 +- lib/class.plugify-gform-braintree.php | 5 +++-- readme.txt | 7 +++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/gravity-forms-braintree.php b/gravity-forms-braintree.php index dbca71f..3db17d6 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.3.0 +Version: 1.3.1 Author URI: http://plugify.io */ diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index ebd699a..25f168a 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -124,7 +124,7 @@ public function authorize( $feed, $submission_data, $form, $entry ) { } if ($feed['meta']['taxExempt'] == 1) { - $args['options']['taxExempt'] = 'true'; + $args['taxExempt'] = 'true'; } GFCommon::log_debug('Braintree Transaction Args: ' . print_r( $args, true )); @@ -160,7 +160,8 @@ public function authorize( $feed, $submission_data, $form, $entry ) { } 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; diff --git a/readme.txt b/readme.txt index 3ac6829..bc1c461 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: 4.5 -Stable tag: 1.3.0 +Tested up to: 5.1.1 +Stable tag: 1.3.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -61,6 +61,9 @@ No filters are currently available for this pre-release version == Changelog == += 1.3.1 = +* Fix bug with tax exempt flag. + = 1.3.0 = * Update Braintree SDK files to 3.39.0 version. From e0fcb01d6ad633d9af5ad518f109b959393cea96 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Thu, 21 Mar 2019 15:58:41 -0600 Subject: [PATCH 18/19] Update to add Advanced Fraud Tools to the plugin setup. --- assets/js/braintree-data-processing.js | 28 ++++++++ lib/class.plugify-gform-braintree.php | 90 +++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 assets/js/braintree-data-processing.js diff --git a/assets/js/braintree-data-processing.js b/assets/js/braintree-data-processing.js new file mode 100644 index 0000000..c45d41c --- /dev/null +++ b/assets/js/braintree-data-processing.js @@ -0,0 +1,28 @@ +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(".ginput_card_expiration_year").parents('form').find('input[type=hidden]').first(); + + if (deviceDataInput == null) { + deviceDataInput = document.createElement('input'); + deviceDataInput.name = 'device_data'; + deviceDataInput.type = 'hidden'; + form.appendChild(deviceDataInput); + } + + deviceDataInput.val(dataCollectorInstance.deviceData); + }); + }); +}); diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 25f168a..7146d50 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -4,7 +4,7 @@ final class Plugify_GForm_Braintree extends GFPaymentAddOn { - protected $_version = '1.0'; + protected $_version = '1.3.2'; protected $_min_gravityforms_version = '2.0.3'; protected $_slug = 'gravity-forms-braintree'; @@ -41,6 +41,51 @@ public function init_frontend () { } + public function scripts() { + $scripts = []; + + if( $settings = $this->get_plugin_settings() ) { + //$enableAFT = $this->get_setting('enableAFT'); + //GFCommon::log_debug('Braintree setting: ' . print_r($enableAFT, true)); + //if ($enableAFT == 1) {/* check that enableAFT is 1 */ + $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' => [], + 'strings' => [ + 'bt_magic' => $settings['tokenization-key'] + ], + '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 @@ -126,6 +171,10 @@ public function authorize( $feed, $submission_data, $form, $entry ) { 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 @@ -272,6 +321,10 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { $result = $customer; } } else { + if ($feed['meta']['enableAFT'] == 1 && !empty($submission_data['device_data'])) { + $args['deviceData'] = $submission_data['device_data']; + } + $result = Braintree\Customer::create($args); } @@ -320,7 +373,8 @@ public function subscribe( $feed, $submission_data, $form, $entry ) { } } 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 )); } } @@ -357,7 +411,19 @@ public function feed_settings_fields () { '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 ); @@ -376,10 +442,21 @@ 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; } + 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; + } + /** * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed * @@ -413,6 +490,13 @@ 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' ) ) ), From a7242c47bffc210cd3e392def56afda0f07240b9 Mon Sep 17 00:00:00 2001 From: Mike Burrows Date: Thu, 21 Mar 2019 18:02:27 -0600 Subject: [PATCH 19/19] Updates to documentation and work in progress for getting field name into JS. --- README.md | 2 + assets/js/braintree-data-processing.js | 7 +- gravity-forms-braintree.php | 2 +- lib/class.plugify-gform-braintree.php | 96 ++++++++++++++++---------- readme.txt | 5 +- 5 files changed, 72 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4c7da8e..c83af9c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ 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 diff --git a/assets/js/braintree-data-processing.js b/assets/js/braintree-data-processing.js index c45d41c..ad8a9da 100644 --- a/assets/js/braintree-data-processing.js +++ b/assets/js/braintree-data-processing.js @@ -1,3 +1,8 @@ +/** + * Used for Advanced Fraud Tools integration. + * + * @since 1.4.0 + */ jQuery( function($) { braintree.client.create({ authorization: braintree_data_processing_strings.bt_magic @@ -13,7 +18,7 @@ jQuery( function($) { // Handle error in data collector creation return; } - var deviceDataInput = jQuery(".ginput_card_expiration_year").parents('form').find('input[type=hidden]').first(); + var deviceDataInput = jQuery("[name=" + braintree_data_processing_strings.bt_field + "]"); if (deviceDataInput == null) { deviceDataInput = document.createElement('input'); diff --git a/gravity-forms-braintree.php b/gravity-forms-braintree.php index 3db17d6..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.3.1 +Version: 1.4.0 Author URI: http://plugify.io */ diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 7146d50..88befe9 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -4,7 +4,7 @@ final class Plugify_GForm_Braintree extends GFPaymentAddOn { - protected $_version = '1.3.2'; + protected $_version = '1.4.0'; protected $_min_gravityforms_version = '2.0.3'; protected $_slug = 'gravity-forms-braintree'; @@ -41,46 +41,49 @@ public function init_frontend () { } + /** + * Enqueue scripts. + * + * @since 1.4.0 + * @return array + */ public function scripts() { $scripts = []; if( $settings = $this->get_plugin_settings() ) { - //$enableAFT = $this->get_setting('enableAFT'); - //GFCommon::log_debug('Braintree setting: ' . print_r($enableAFT, true)); - //if ($enableAFT == 1) {/* check that enableAFT is 1 */ - $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' => [], - 'strings' => [ - 'bt_magic' => $settings['tokenization-key'] - ], - 'enqueue' => [ - [$this, 'aft_enabled'] - ] + $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); @@ -230,6 +233,7 @@ public function authorize( $feed, $submission_data, $form, $entry ) { * @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, @@ -435,7 +439,7 @@ public function feed_settings_fields () { /** * Update billing fields. * - * @since 3.0.6 + * @since 1.2.1 * @return array */ public function billing_info_fields() { @@ -447,6 +451,13 @@ public function billing_info_fields() { 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']); @@ -457,6 +468,17 @@ public function aft_enabled( $form ) { 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 * diff --git a/readme.txt b/readme.txt index bc1c461..2c6ff23 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=hello Tags: credit card,braintree,gravity form,payment Requires at least: 3.8 Tested up to: 5.1.1 -Stable tag: 1.3.1 +Stable tag: 1.4.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -61,6 +61,9 @@ 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.