Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

braintree php client library 1.0.0

  • Loading branch information...
commit f2d8d7cfb2b8b88d4006de93cc982b9222f4b87d 0 parents
@braintreeps braintreeps authored
Showing with 7,931 additions and 0 deletions.
  1. +22 −0 LICENSE
  2. +37 −0 README.md
  3. +14 −0 Rakefile
  4. +101 −0 lib/Braintree.php
  5. +384 −0 lib/Braintree/Address.php
  6. +159 −0 lib/Braintree/Collection.php
  7. +364 −0 lib/Braintree/Configuration.php
  8. +535 −0 lib/Braintree/CreditCard.php
  9. +545 −0 lib/Braintree/Customer.php
  10. +62 −0 lib/Braintree/Digest.php
  11. +117 −0 lib/Braintree/Error/Codes.php
  12. +73 −0 lib/Braintree/Error/ErrorCollection.php
  13. +59 −0 lib/Braintree/Error/Validation.php
  14. +97 −0 lib/Braintree/Error/ValidationErrorCollection.php
  15. +25 −0 lib/Braintree/Exception.php
  16. +21 −0 lib/Braintree/Exception/Authentication.php
  17. +23 −0 lib/Braintree/Exception/Authorization.php
  18. +21 −0 lib/Braintree/Exception/Configuration.php
  19. +20 −0 lib/Braintree/Exception/DownForMaintenance.php
  20. +23 −0 lib/Braintree/Exception/ForgedQueryString.php
  21. +20 −0 lib/Braintree/Exception/NotFound.php
  22. +20 −0 lib/Braintree/Exception/SSLCertificate.php
  23. +20 −0 lib/Braintree/Exception/ServerError.php
  24. +21 −0 lib/Braintree/Exception/Unexpected.php
  25. +21 −0 lib/Braintree/Exception/ValidationsFailed.php
  26. +156 −0 lib/Braintree/Http.php
  27. +72 −0 lib/Braintree/Instance.php
  28. +264 −0 lib/Braintree/PagedCollection.php
  29. +63 −0 lib/Braintree/Result/CreditCardVerification.php
  30. +95 −0 lib/Braintree/Result/Error.php
  31. +78 −0 lib/Braintree/Result/Successful.php
  32. +53 −0 lib/Braintree/SSLExpirationCheck.php
  33. +76 −0 lib/Braintree/Test/CreditCardNumbers.php
  34. +24 −0 lib/Braintree/Test/TransactionAmounts.php
  35. +701 −0 lib/Braintree/Transaction.php
  36. +32 −0 lib/Braintree/Transaction/AddressDetails.php
  37. +40 −0 lib/Braintree/Transaction/CreditCardDetails.php
  38. +30 −0 lib/Braintree/Transaction/CustomerDetails.php
  39. +26 −0 lib/Braintree/Transaction/StatusDetails.php
  40. +289 −0 lib/Braintree/TransparentRedirect.php
  41. +252 −0 lib/Braintree/Util.php
  42. +43 −0 lib/Braintree/Version.php
  43. +48 −0 lib/Braintree/Xml.php
  44. +145 −0 lib/Braintree/Xml/Generator.php
  45. +185 −0 lib/Braintree/Xml/Parser.php
  46. +44 −0 lib/ssl/securetrust_ca.crt
  47. +18 −0 lib/ssl/valicert_ca.crt
  48. +56 −0 tests/TestHelper.php
  49. +225 −0 tests/integration/AddressTest.php
  50. +27 −0 tests/integration/AllTests.php
  51. +465 −0 tests/integration/CreditCardTest.php
  52. +541 −0 tests/integration/CustomerTest.php
  53. +319 −0 tests/integration/TransactionTest.php
  54. +56 −0 tests/unit/AddressTest.php
  55. +40 −0 tests/unit/AllTests.php
  56. +24 −0 tests/unit/BraintreeTest.php
  57. +192 −0 tests/unit/ConfigurationTest.php
  58. +46 −0 tests/unit/CreditCardTest.php
  59. +13 −0 tests/unit/CustomerTest.php
  60. +23 −0 tests/unit/DigestTest.php
  61. +9 −0 tests/unit/HttpTest.php
  62. +19 −0 tests/unit/TransactionTest.php
  63. +149 −0 tests/unit/UtilTest.php
  64. +189 −0 tests/unit/Xml_ParserTest.php
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010 Braintree Payment Solutions
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
37 README.md
@@ -0,0 +1,37 @@
+# Braintree PHP Client Library
+
+The Braintree PHP library provides integration access to the Braintree Gateway.
+
+## Dependencies
+
+* Zend Framework
+
+## Quick Start Example
+
+ <?php
+
+ // ensure Zend_Framework is in your load path
+ require_once 'PATH_TO_BRAINTREE/lib/Braintree.php';
+
+ // validates and sets config statically
+ Braintree_Configuration::environment('sandbox');
+ Braintree_Configuration::merchantId('the_merchant_id');
+ Braintree_Configuration::publicKey('the_public_key');
+ Braintree_Configuration::privateKey('the_private_key');
+
+ $transaction = Braintree_Transaction::saleNoValidate(array(
+ 'amount' => '100.00',
+ 'creditCard' => array(
+ 'number' => '5105105105105100',
+ 'expirationDate' => '05/12',
+ )
+ ));
+
+ print 'Transaction ID: ' . $transaction->id;
+ print 'Status: ' . $transaction->status;
+ ?>
+
+## License
+
+See the LICENSE file.
+
14 Rakefile
@@ -0,0 +1,14 @@
+task :default => %w[test:unit test:integration]
+
+namespace :test do
+ desc "run unit tests"
+ task :unit do
+ sh "phpunit tests/unit/AllTests.php"
+ end
+
+ desc "run integration tests"
+ task :integration do
+ sh "phpunit tests/integration/AllTests.php"
+ end
+end
+
101 lib/Braintree.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Braintree base class and initialization
+ *
+ * PHP version 5
+ *
+ * @package Braintree
+ * @copyright 2010 Braintree Payment Solutions
+ */
+
+
+/*
+ * append the include path
+*/
+set_include_path(get_include_path() .
+ PATH_SEPARATOR . realpath(dirname(__FILE__))
+ );
+
+/**
+ * set up autoloader
+ */
+require_once 'Zend/Loader/Autoloader.php';
+$loader = Zend_Loader_Autoloader::getInstance();
+$loader->registerNamespace('Braintree_');
+
+
+/**
+ * Braintree PHP Library
+ *
+ * Provides methods to child classes. This class cannot be instantiated.
+ *
+ * @package Braintree
+ * @copyright 2010 Braintree Payment Solutions
+ */
+abstract class Braintree
+{
+ /**
+ * @ignore
+ * don't permit an explicit call of the constructor!
+ * (like $t = new Braintree_Transaction())
+ */
+ protected function __construct()
+ {
+ }
+ /**
+ * @ignore
+ * don't permit cloning the instances (like $x = clone $v)
+ */
+ protected function __clone()
+ {
+ }
+
+ public function __get($name)
+ {
+ trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE);
+ return null;
+ }
+
+ /**
+ *
+ * @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();
+ }
+ }
+}
+
+/**
+ * requirements and version checks
+ */
+// check PHP version
+if (version_compare(PHP_VERSION, '5.2.1', '<')) {
+ throw new Braintree_Exception('PHP version >= 5.2.1 required');
+}
+
+// check for extensions
+$requiredExtensions = array('xmlwriter', 'SimpleXML', 'openssl', 'dom', 'hash');
+foreach ($requiredExtensions AS $ext) {
+ if (!extension_loaded($ext)) {
+ throw new Braintree_Exception(
+ 'The Braintree library requires the ' . $ext . ' extension.'
+ );
+ }
+}
+
+
+//
+//
+// check ssl certificate
+Braintree_SSLExpirationCheck::checkDates();
+
+
384 lib/Braintree/Address.php
@@ -0,0 +1,384 @@
+<?php
+/**
+ * Braintree Address module
+ *
+ * PHP Version 5
+ *
+ * @package Braintree
+ * @copyright 2010 Braintree Payment Solutions
+ */
+/**
+ * Creates and manages Braintree Addresses
+ *
+ * An Address belongs to a Customer. It can be associated to a
+ * CreditCard as the billing address. It can also be used
+ * as the shipping address when creating a Transaction.
+ *
+ * @package Braintree
+ * @copyright 2010 Braintree Payment Solutions
+ *
+ * @property-read string $company
+ * @property-read string $countryName
+ * @property-read string $createdAt
+ * @property-read string $customerId
+ * @property-read string $extendedAddress
+ * @property-read string $firstName
+ * @property-read string $id
+ * @property-read string $lastName
+ * @property-read string $locality
+ * @property-read string $postalCode
+ * @property-read string $region
+ * @property-read string $streetAddress
+ * @property-read string $updatedAt
+ */
+class Braintree_Address extends Braintree
+{
+
+ /* public class methods */
+ /**
+ *
+ * @access public
+ * @param array $attribs
+ * @return object Result, either Successful or Error
+ */
+ public static function create($attribs)
+ {
+ Braintree_Util::verifyKeys(self::createSignature(), $attribs);
+ $customerId = isset($attribs['customerId']) ?
+ $attribs['customerId'] :
+ null;
+
+ self::_validateCustomerId($customerId);
+ unset($attribs['customerId']);
+ return self::_doCreate(
+ '/customers/' . $customerId . '/addresses',
+ array('address' => $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 <b>addressId</b> that is associated
+ * to the given <b>customerOrId</b>.
+ * 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);
+
+ 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
+ *
+ * @param object $other address to compare against
+ * @return boolean
+ */
+ public function isEqual($other)
+ {
+ return !is_a($other, __CLASS__) ?
+ 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', '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();
+
+ }
+
+ /**
+ * returns private/nonexistent instance properties
+ * @ignore
+ * @access public
+ * @param string $name property name
+ * @return mixed contents of instance properties
+ */
+ public function __get($name)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ return $this->_attributes[$name];
+ }
+ else {
+ return parent::__get($name);
+ }
+ }
+ /**
+ * create a printable representation of the object as:
+ * ClassName[property=value, property=value]
+ * @return var
+ */
+ public function __toString()
+ {
+ $objOutput = Braintree_Util::implodeAssociativeArray($this->_attributes);
+ return __CLASS__ . '[' . $objOutput . ']';
+ }
+ /* private class properties */
+ /**
+ * @access protected
+ * @var array registry of customer data
+ */
+ private $_attributes = array(
+ 'company' => '',
+ 'countryName' => '',
+ 'customerId' => '',
+ 'extendedAddress' => '',
+ 'firstName' => '',
+ 'id' => '',
+ 'lastName' => '',
+ 'locality' => '',
+ 'postalCode' => '',
+ 'region' => '',
+ 'streetAddress' => '',
+ 'updatedAt' => '',
+ );
+
+ /**
+ * sets instance properties from an array of values
+ *
+ * @access protected
+ * @param array $addressAttribs array of address data
+ * @return none
+ */
+ protected function _initialize($addressAttribs)
+ {
+ // set the attributes
+ $this->_attributes = array_merge($this->_attributes, $addressAttribs);
+
+ }
+
+ /**
+ * verifies that a valid address id is being used
+ * @param string $id address id
+ * @throws InvalidArgumentException
+ */
+ private static function _validateId($id = null)
+ {
+ if (empty($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
+ * @param string $id customer id
+ * @throws InvalidArgumentException
+ */
+ private static function _validateCustomerId($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.'
+ );
+ }
+
+ }
+
+ /**
+ * determines if a string id or Customer object was passed
+ *
+ * @param mixed $customerOrId
+ *
+ * @return string customerId
+ *
+ */
+ private static function _determineCustomerId($customerOrId)
+ {
+ $customerId = is_a('Braintree_Customer', $customerOrId) ?
+ $customerOrId->id :
+ $customerOrId;
+ self::_validateCustomerId($customerId);
+ return $customerId;
+
+ }
+
+ /* private class methods */
+ /**
+ * sends the create request to the gateway
+ *
+ * @param string $url
+ * @param array $params
+ * @return mixed
+ */
+ private static function _doCreate($url, $params)
+ {
+ $response = Braintree_Http::post($url, $params);
+
+ return self::_verifyGatewayResponse($response);
+
+ }
+
+ /**
+ * 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.
+ *
+ * @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['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"
+ );
+ }
+
+ }
+
+ /**
+ * factory method: returns an instance of Braintree_Address
+ * to the requesting method, with populated properties
+ *
+ * @return object instance of Braintree_Address
+ */
+ public static function factory($attributes)
+ {
+ $instance = new self();
+ $instance->_initialize($attributes);
+ return $instance;
+
+ }
+}
159 lib/Braintree/Collection.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * Braintree Generic collection
+ *
+ * PHP Version 5
+ *
+ * @package Braintree
+ * @subpackage Utility
+ * @copyright 2010 Braintree Payment Solutions
+ */
+
+/**
+ * Generic Collection class
+ *
+ * Based on Generic Collection class from:
+ * {@link http://codeutopia.net/code/library/CU/Collection.php}
+ *
+ * @package Braintree
+ * @subpackage Utility
+ */
+class Braintree_Collection implements Countable, IteratorAggregate, ArrayAccess
+{
+ /**
+ *
+ * @var array $_collection collection storage
+ */
+ protected $_collection = array();
+
+ /**
+ * Add a value into the collection
+ * @param string $value
+ */
+ public function add($value)
+ {
+ $this->_collection[] = $value;
+ }
+
+ /**
+ * Set index's value
+ * @param integer $index
+ * @param mixed $value
+ * @throws OutOfRangeException
+ */
+ public function set($index, $value)
+ {
+ if($index >= $this->count())
+ throw new OutOfRangeException('Index out of range');
+
+ $this->_collection[$index] = $value;
+ }
+
+ /**
+ * Remove a value from the collection
+ * @param integer $index index to remove
+ * @throws OutOfRangeException if index is out of range
+ */
+ public function remove($index)
+ {
+ if($index >= $this->count())
+ throw new OutOfRangeException('Index out of range');
+
+ array_splice($this->_collection, $index, 1);
+ }
+
+ /**
+ * Return value at index
+ * @param integer $index
+ * @return mixed
+ * @throws OutOfRangeException
+ */
+ public function get($index)
+ {
+ if($index >= $this->count())
+ throw new OutOfRangeException('Index out of range');
+
+ return $this->_collection[$index];
+ }
+
+ /**
+ * Determine if index exists
+ * @param integer $index
+ * @return boolean
+ */
+ public function exists($index)
+ {
+ if($index >= $this->count())
+ return false;
+
+ return true;
+ }
+ /**
+ * Return count of items in collection
+ * Implements countable
+ * @return integer
+ */
+ public function count()
+ {
+ return count($this->_collection);
+ }
+
+
+ /**
+ * Return an iterator
+ * Implements IteratorAggregate
+ * @return ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_collection);
+ }
+
+ /**
+ * Set offset to value
+ * Implements ArrayAccess
+ * @see set
+ * @param integer $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+
+ /**
+ * Unset offset
+ * Implements ArrayAccess
+ * @see remove
+ * @param integer $offset
+ */
+ public function offsetUnset($offset)
+ {
+ $this->remove($offset);
+ }
+
+ /**
+ * get an offset's value
+ * Implements ArrayAccess
+ * @see get
+ * @param integer $offset
+ * @return mixed
+ */
+ public function offsetGet($offset)
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * Determine if offset exists
+ * Implements ArrayAccess
+ * @see exists
+ * @param integer $offset
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return $this->exists($offset);
+ }
+
+}
364 lib/Braintree/Configuration.php
@@ -0,0 +1,364 @@
+<?php
+/**
+ *
+ * Configuration registry
+ *
+ * @package Braintree
+ * @subpackage Utility
+ * @copyright 2010 Braintree Payment Solutions
+ */
+
+/**
+ * acts as a registry for config data.
+ *
+ *
+ * @package Braintree
+ * @subpackage Utility
+ *
+ * */
+
+class Braintree_Configuration extends Braintree
+{
+ /**
+ * Braintree API version to use
+ * @access public
+ */
+ const API_VERSION = 1;
+
+ /**
+ * @var array array of config properties
+ * @access protected
+ * @static
+ */
+ private static $_cache = array(
+ 'environment' => '',
+ 'merchantId' => '',
+ 'publicKey' => '',
+ 'privateKey' => '',
+ );
+ /**
+ *
+ * @access protected
+ * @static
+ * @var array valid environments, used for validation
+ */
+ private static $_validEnvironments = array(
+ 'development',
+ 'sandbox',
+ 'production',
+ 'qa',
+ );
+
+ /**
+ * resets configuration to default
+ * @access public
+ * @static
+ */
+ public static function reset()
+ {
+ self::$_cache = array (
+ 'environment' => '',
+ 'merchantId' => '',
+ 'publicKey' => '',
+ 'privateKey' => '',
+ );
+ }
+
+ /**
+ * performs sanity checks when config settings are being set
+ *
+ * @access protected
+ * @param string $key name of config setting
+ * @param string $value value to set
+ * @throws InvalidArgumentException
+ * @throws Braintree_Exception_Configuration
+ * @static
+ * @return boolean
+ */
+ private static function validate($key=null, $value=null)
+ {
+ if (empty($key) && empty($value)) {
+ throw new InvalidArgumentException('nothing to validate');
+ }
+
+ if ($key === 'environment' &&
+ !in_array($value, self::$_validEnvironments) ) {
+ throw new Braintree_Exception_Configuration('"' .
+ $value . '" is not a valid environment.');
+ }
+
+ if (!isset(self::$_cache[$key])) {
+ throw new Braintree_Exception_Configuration($key .
+ ' is not a valid configuration setting.');
+ }
+
+ if (empty($value)) {
+ throw new InvalidArgumentException($key . ' cannot be empty.');
+ }
+
+ return true;
+ }
+ /**
+ *
+ * sets private config registry values, after validation
+ *
+ * @access protected
+ * @static
+ * @param string $key config item to be set
+ * @param string $value value to assign
+ *
+ */
+ private static function set($key, $value)
+ {
+ // this method will raise an exception on invalid data
+ self::validate($key, $value);
+ // set the value in the cache
+ self::$_cache[$key] = $value;
+
+ }
+
+ /**
+ *
+ * gets private config registry values
+ *
+ * @access protected
+ * @static
+ * @param string $key config item to retrieve
+ * @return string value of the retrieved item
+ * @throws Braintree_Exception_Configuration
+ */
+ private static function get($key)
+ {
+ // 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 (array_key_exists($key, self::$_cache)) {
+ return self::$_cache[$key];
+ }
+
+ // return null by default to prevent __set from overloading
+ return null;
+ }
+
+
+ /**
+ * sets value of named property if passed, otherwise
+ * returns current value
+ * @access protected
+ * @static
+ * @param string $name name of property to set/get
+ * @param mixed $value value to set, defaults to null
+ * @return mixed
+ */
+ private static function setOrGet($name, $value = null)
+ {
+ if (!empty($value) && is_array($value)) {
+ $value = $value[0];
+ }
+ if (!empty($value)) {
+ self::set($name, $value);
+ } else {
+ return self::get($name);
+ }
+ return true;
+ }
+ /**#@+
+ * sets or returns the property after validation
+ * @access public
+ * @static
+ * @param string $value pass a string to set, empty to get
+ * @return mixed returns true on set
+ */
+ public static function environment($value = null)
+ {
+ return self::setOrGet(__FUNCTION__, $value);
+ }
+
+ public static function merchantId($value = null)
+ {
+ return self::setOrGet(__FUNCTION__, $value);
+ }
+
+ public static function publicKey($value = null)
+ {
+ return self::setOrGet(__FUNCTION__, $value);
+ }
+
+ public static function privateKey($value = null)
+ {
+ return self::setOrGet(__FUNCTION__, $value);
+ }
+ /**#@-*/
+
+ /**
+ * returns the full merchant URL based on config values
+ *
+ * @access public
+ * @static
+ * @param none
+ * @return string merchant URL
+ */
+ public static function merchantUrl()
+ {
+ return self::baseUrl() .
+ self::merchantPath();
+ }
+
+ /**
+ * returns the base braintree gateway URL based on config values
+ *
+ * @access public
+ * @static
+ * @param none
+ * @return string braintree gateway URL
+ */
+ public static function baseUrl()
+ {
+ return self::protocol() . '://' .
+ self::serverName() . ':' .
+ self::portNumber();
+ }
+
+ /**
+ * sets the merchant path based on merchant ID
+ *
+ * @access protected
+ * @static
+ * @param none
+ * @return string merchant path uri
+ */
+ public static function merchantPath()
+ {
+ return '/merchants/'.self::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 = DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR .
+ 'ssl' . DIRECTORY_SEPARATOR;
+
+ switch(self::environment()) {
+ case 'production':
+ $caPath = realpath(
+ dirname(__FILE__) .
+ $sslPath . 'securetrust_ca.crt'
+ );
+ break;
+ case 'qa':
+ case 'sandbox':
+ default:
+ $caPath = realpath(
+ dirname(__FILE__) .
+ $sslPath . 'valicert_ca.crt'
+ );
+ break;
+ }
+ return $caPath;
+ }
+
+ /**
+ * returns the port number depending on environment
+ *
+ * @access public
+ * @static
+ * @param none
+ * @return int portnumber
+ */
+ public static function portNumber()
+ {
+ return self::sslOn() ? 443 : 3000;
+ }
+
+ /**
+ * returns http protocol depending on environment
+ *
+ * @access public
+ * @static
+ * @param none
+ * @return string http || https
+ */
+ public static function protocol()
+ {
+ return self::sslOn() ? 'https' : 'http';
+ }
+
+ /**
+ * returns gateway server name depending on environment
+ *
+ * @access public
+ * @static
+ * @param none
+ * @return string server domain name
+ */
+ public static function serverName()
+ {
+ switch(self::environment()) {
+ case 'production':
+ $serverName = 'www.braintreegateway.com';
+ break;
+ case 'qa':
+ $serverName = 'qa.braintreegateway.com';
+ break;
+ case 'sandbox':
+ $serverName = 'sandbox.braintreegateway.com';
+ break;
+ case 'development':
+ default:
+ $serverName = 'localhost';
+ 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()
+ {
+ switch(self::environment()) {
+ case 'development':
+ $ssl = false;
+ break;
+ case 'production':
+ case 'qa':
+ case 'sandbox':
+ default:
+ $ssl = true;
+ break;
+ }
+
+ return $ssl;
+ }
+
+ /**
+ * log message to default logger
+ *
+ * @param string $message
+ *
+ */
+ public static function logMessage($message)
+ {
+ error_log('[Braintree] ' . $message);
+ }
+
+}
535 lib/Braintree/CreditCard.php
@@ -0,0 +1,535 @@
+<?php
+/**
+ * Braintree CreditCard module
+ *
+ * @package Braintree
+ * @category Resources
+ * @copyright 2010 Braintree Payment Solutions
+ */
+
+/**
+ * Creates and manages Braintree CreditCards
+ *
+ *
+ * @package Braintree
+ * @category Resources
+ * @copyright 2010 Braintree Payment Solutions
+ *
+ * @property-read string $billingAddress
+ * @property-read string $bin
+ * @property-read string $cardType
+ * @property-read string $cardholderName
+ * @property-read string $createdAt
+ * @property-read string $customerId
+ * @property-read string $expirationDate
+ * @property-read string $expirationMonth
+ * @property-read string $expirationYear
+ * @property-read string $last4
+ * @property-read string $maskedNumber
+ * @property-read string $token
+ * @property-read string $updatedAt
+ */
+class Braintree_CreditCard extends Braintree
+{
+ public static function create($attribs)
+ {
+ Braintree_Util::verifyKeys(self::createSignature(), $attribs);
+ return self::_doCreate('/payment_methods', array('credit_card' => $attribs));
+ }
+
+ /**
+ * attempts the create operation assuming all data will validate
+ * returns a Braintree_CreditCard 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);
+ }
+ /**
+ * create a customer from a TransparentRedirect operation
+ *
+ * @access public
+ * @param array $attribs
+ * @return object
+ */
+ public static function createFromTransparentRedirect($queryString)
+ {
+ $params = Braintree_TransparentRedirect::parseAndValidateQueryString(
+ $queryString
+ );
+ return self::_doCreate(
+ '/payment_methods/all/confirm_transparent_redirect_request',
+ array('id' => $params['id'])
+ );
+ }
+
+ /**
+ *
+ * @access public
+ * @param none
+ * @return string
+ */
+ public static function createCreditCardUrl()
+ {
+ return Braintree_Configuration::merchantUrl() .
+ '/payment_methods/all/create_via_transparent_redirect_request';
+ }
+
+ /**
+ * returns a PagedCollection of expired credit cards
+ * @return object PagedCollection
+ */
+ public static function expired($options = null)
+ {
+ $page = isset($options['page']) ? $options['page'] : 1;
+ $queryPath = '/payment_methods/all/expired?page=' . $page;
+ $response = Braintree_Http::get($queryPath);
+ $attributes = $response['paymentMethods'];
+ $attributes['items'] = Braintree_Util::extractAttributeAsArray(
+ $attributes,
+ 'creditCard'
+ );
+ $pager = array(
+ 'className' => __CLASS__,
+ 'classMethod' => __METHOD__,
+ 'methodArgs' => array()
+ );
+
+ return new Braintree_PagedCollection($attributes, $pager);
+ }
+ /**
+ * returns a PagedCollection of credit cards expiring between start/end
+ *
+ * @return object PagedCollection
+ */
+ public static function expiringBetween($startDate, $endDate, $options = null)
+ {
+ $page = isset($options['page']) ? $options['page'] : 1;
+ $queryPath = '/payment_methods/all/expiring?page=' . $page .
+ '&start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate);
+ $response = Braintree_Http::get($queryPath);
+ $attributes = $response['paymentMethods'];
+ $attributes['items'] = Braintree_Util::extractAttributeAsArray(
+ $attributes,
+ 'creditCard'
+ );
+ $pager = array(
+ 'className' => __CLASS__,
+ 'classMethod' => __METHOD__,
+ 'methodArgs' => array($startDate, $endDate)
+ );
+
+ return new Braintree_PagedCollection($attributes, $pager);
+
+ }
+ /**
+ * 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'
+ );
+ }
+
+ }
+
+ /**
+ * 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)
+ {
+ self::_validateId($token);
+ return Braintree_Transaction::credit(
+ array_merge(
+ $transactionAttribs,
+ array('paymentMethodToken' => $token)
+ )
+ );
+ }
+
+ /**
+ * create a credit on this card, assuming validations will pass
+ *
+ * returns a Braintree_Transaction object on success
+ *
+ * @access public
+ * @param array $attribs
+ * @return object Braintree_Transaction
+ * @throws Braintree_Exception_ValidationError
+ */
+ public static function creditNoValidate($token, $transactionAttribs)
+ {
+ $result = self::credit($token, $transactionAttribs);
+ return self::returnObjectOrThrowException('Transaction', $result);
+ }
+
+ /**
+ * 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()
+ */
+ public static function sale($token, $transactionAttribs)
+ {
+ self::_validateId($token);
+ return Braintree_Transaction::sale(
+ array_merge(
+ $transactionAttribs,
+ array('paymentMethodToken' => $token)
+ )
+ );
+ }
+
+ /**
+ * create a new sale using this card, assuming validations will pass
+ *
+ * returns a Braintree_Transaction object on success
+ *
+ * @access public
+ * @param array $transactionAttribs
+ * @param string $token
+ * @return object Braintree_Transaction
+ * @throws Braintree_Exception_ValidationsFailed
+ * @see Braintree_Transaction::sale()
+ */
+ public static function saleNoValidate($token, $transactionAttribs)
+ {
+ $result = self::sale($token, $transactionAttribs);
+ return self::returnObjectOrThrowException('Transaction', $result);
+ }
+
+ /**
+ * updates the creditcard record
+ *
+ * if calling this method in static context, $token
+ * is the 2nd attribute. $token is not sent in object context.
+ *
+ * @access public
+ * @param array $attributes
+ * @param string $token (optional)
+ * @return object Braintree_Result_Successful or Braintree_Result_Error
+ */
+ public static function update($token, $attributes)
+ {
+ Braintree_Util::verifyKeys(self::updateSignature(), $attributes);
+ self::_validateId($token);
+ return self::_doUpdate('put', '/payment_methods/' . $token, array('creditCard' => $attributes));
+ }
+
+ /**
+ * 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)
+ {
+ $result = self::update($token, $attributes);
+ return self::returnObjectOrThrowException(__CLASS__, $result);
+ }
+ /**
+ *
+ * @access public
+ * @param none
+ * @return string
+ */
+ public static function updateCreditCardUrl()
+ {
+ return Braintree_Configuration::merchantUrl() .
+ '/payment_methods/all/update_via_transparent_redirect_request';
+ }
+
+ /**
+ * update a customer from a TransparentRedirect operation
+ *
+ * @access public
+ * @param array $attribs
+ * @return object
+ */
+ public static function updateFromTransparentRedirect($queryString)
+ {
+ $params = Braintree_TransparentRedirect::parseAndValidateQueryString(
+ $queryString
+ );
+ return self::_doUpdate(
+ 'post',
+ '/payment_methods/all/confirm_transparent_redirect_request',
+ array('id' => $params['id'])
+ );
+ }
+
+ /* instance methods */
+ /**
+ * returns false if default is null or false
+ *
+ * @return boolean
+ */
+ public function isDefault()
+ {
+ if ($this->default) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * checks whether the card is expired based on the current date
+ *
+ * @return boolean
+ */
+ public function isExpired()
+ {
+ if ($this->expirationYear == date('Y')) {
+ return ($this->expirationMonth < date('m'));
+ }
+ return ($this->expirationYear < date('Y'));
+ }
+
+ public static function delete($token)
+ {
+ self::_validateId($token);
+ Braintree_Http::delete('/payment_methods/' . $token);
+ return new Braintree_Result_Successful();
+ }
+
+ /**
+ * sets instance properties from an array of values
+ *
+ * @access protected
+ * @param array $creditCardAttribs array of creditcard data
+ * @return none
+ */
+ protected function _initialize($creditCardAttribs)
+ {
+ // set the attributes
+ $this->_attributes = array_merge($this->_attributes, $creditCardAttribs);
+
+ // map each address into its own object
+ $billingAddress = isset($creditCardAttribs['billingAddress']) ?
+ Braintree_Address::factory($creditCardAttribs['billingAddress']) :
+ null;
+
+ $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 Braintree_CreditCard,
+ * or is a Braintree_CreditCard with a different id
+ *
+ * @param object $otherCreditCard customer to compare against
+ * @return boolean
+ */
+ public function isEqual($otherCreditCard)
+ {
+ return !is_a('Braintree_CreditCard', $otherCreditCard) ?
+ false :
+ $this->token === $otherCreditCard->token;
+ }
+
+ public static function createSignature()
+ {
+ return array(
+ 'customerId', 'cardholderName', 'cvv', 'number',
+ 'expirationDate', 'expirationMonth', 'expirationYear', 'token',
+ array('options' => array('verifyCard')),
+ array(
+ 'billingAddress' => array(
+ 'firstName',
+ 'lastName',
+ 'company',
+ 'countryName',
+ 'extendedAddress',
+ 'locality',
+ 'region',
+ 'postalCode',
+ 'streetAddress',
+ ),
+ ),
+ );
+ }
+ public static function updateSignature()
+ {
+ // return all but the first element of create signature
+ $signature = self::createSignature();
+ return array_slice($signature, 1);
+ }
+
+ /**
+ * returns private/nonexistent instance properties
+ * @ignore
+ * @access public
+ * @param string $name property name
+ * @return mixed contents of instance properties
+ */
+ public function __get($name)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ return $this->_attributes[$name];
+ }
+ else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * create a printable representation of the object as:
+ * ClassName[property=value, property=value]
+ * @return string
+ */
+ public function __toString()
+ {
+ $objOutput = Braintree_Util::implodeAssociativeArray($this->_attributes);
+ return __CLASS__ . '[' . $objOutput . ']';
+ }
+ /* private class properties */
+
+ /**
+ * @access protected
+ * @var array registry of customer data
+ */
+ private $_attributes = array(
+ 'billingAddress' => '',
+ 'bin' => '',
+ 'cardType' => '',
+ 'cardholderName' => '',
+ 'createdAt' => '',
+ 'customerId' => '',
+ 'expirationMonth' => '',
+ 'expirationYear' => '',
+ 'last4' => '',
+ 'token' => '',
+ 'updatedAt' => '',
+ );
+
+ /**
+ * verifies that a valid credit card token is being used
+ * @param string $token
+ * @throws InvalidArgumentException
+ */
+ private static function _validateId($token = null)
+ {
+ if (empty($token)) {
+ throw new InvalidArgumentException(
+ 'expected address id to be set'
+ );
+ }
+ if (!preg_match('/^[0-9A-Za-z_-]+$/', $token)) {
+ throw new InvalidArgumentException(
+ $token . ' is an invalid address id.'
+ );
+ }
+ }
+ /**
+ * sets private properties
+ * this function is private so values are read only
+ * @access protected
+ * @param string $key
+ * @param mixed $value
+ */
+ private function _set($key, $value)
+ {
+ $this->_attributes[$key] = $value;
+ }
+
+ /* private class methods */
+
+ /**
+ * sends the create request to the gateway
+ *
+ * @param string $url
+ * @param array $params
+ * @return mixed
+ */
+ private static function _doCreate($url, $params)
+ {
+ $response = Braintree_Http::post($url, $params);
+
+ return self::_verifyGatewayResponse($response);
+ }
+
+ /**
+ * sends the update request to the gateway
+ *
+ * @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_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.
+ *
+ * @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['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"
+ );
+ }
+ }
+
+ /**
+ * factory method: returns an instance of Braintree_CreditCard
+ * to the requesting method, with populated properties
+ *
+ * @return object instance of Braintree_CreditCard
+ */
+ public static function factory($attributes)
+ {
+ $instance = new self();
+ $instance->_initialize($attributes);
+ return $instance;
+ }
+}
545 lib/Braintree/Customer.php
@@ -0,0 +1,545 @@
+<?php
+/**
+ * Braintree Customer module
+ *
+ * @package Braintree
+ * @category Resources
+ * @copyright 2010 Braintree Payment Solutions
+ */
+
+/**
+ * Creates and manages Customers
+ *
+ * @package Braintree
+ * @category Resources
+ * @copyright 2010 Braintree Payment Solutions
+ *
+ * @property-read array $addresses
+ * @property-read string $company
+ * @property-read string $createdAt
+ * @property-read array $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 string $phone
+ * @property-read string $updatedAt
+ * @property-read string $website
+ */
+class Braintree_Customer extends Braintree
+{
+ /* public class methods */
+
+ /**
+ *
+ */
+ public static function all($options = null)
+ {
+ $page = isset($options['page']) ? $options['page'] : 1;
+ $response = Braintree_Http::get('/customers?page=' . $page);
+ $attributes = $response['customers'];
+ $attributes['items'] = Braintree_Util::extractAttributeAsArray(
+ $attributes,
+ 'customer'
+ );
+ unset($attributes['customer']);
+ $pager = array(
+ 'className' => __CLASS__,
+ 'classMethod' => __FUNCTION__,
+ 'methodArgs' => array());
+
+ return new Braintree_PagedCollection($attributes, $pager);
+ }
+ /**
+ * Creates a customer using the given +attributes+. If <tt>:id</tt> is not passed,
+ * the gateway will generate it.
+ *
+ * <code>
+ * $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';
+ * }
+ * </code>
+ *
+ * @access public
+ * @param array $attribs
+ * @return object Result, either Successful or Error
+ */
+ public static function create($attribs = array())
+ {
+ Braintree_Util::verifyKeys(self::createSignature(), $attribs);
+ return self::_doCreate('/customers', array('customer' => $attribs));
+ }
+
+ /**
+ * attempts the create operation assuming all data will validate
+ * returns a Braintree_Customer object instead of a Result
+ *
+ * @access public
+ * @param array $attribs
+ * @return object
+ * @throws Braintree_Exception_ValidationError
+ */
+ public static function createNoValidate($attribs = array())
+ {
+ $result = self::create($attribs);
+ return self::returnObjectOrThrowException(__CLASS__, $result);
+ }
+ /**
+ * create a customer from a TransparentRedirect operation
+ *
+ * @access public
+ * @param array $attribs
+ * @return object
+ */
+ public static function createFromTransparentRedirect($queryString)
+ {
+ $params = Braintree_TransparentRedirect::parseAndValidateQueryString(
+ $queryString
+ );
+ return self::_doCreate(
+ '/customers/all/confirm_transparent_redirect_request',
+ array('id' => $params['id'])
+ );
+ }
+
+ /**
+ *
+ * @access public
+ * @param none
+ * @return string
+ */
+ public static function createCustomerUrl()
+ {
+ return Braintree_Configuration::merchantUrl() .
+ '/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 = Braintree_CreditCard::createSignature();
+ unset($creditCardSignature['customerId']);
+ $signature = array(
+ 'id', 'company', 'email', 'fax', 'firstName',
+ 'lastName', 'phone', 'website',
+ array('creditCard' => $creditCardSignature),
+ array('customFields' => array('_anyKey_')),
+ );
+ return $signature;
+ }
+
+ /**
+ * creates a full array signature of a valid update request
+ * @return array update request format
+ */
+ public static function updateSignature()
+ {
+ $signature = array(
+ 'id', 'company', 'email', 'fax', 'firstName',
+ 'lastName', 'phone', 'website',
+ array('customFields' => array('_anyKey_')),
+ );
+ return $signature;
+ }
+
+
+ /**
+ * find a customer by id
+ *
+ * @access public
+ * @param string id customer Id
+ * @return object Braintree_Customer
+ * @throws Braintree_Exception_NotFound
+ */
+ 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'
+ );
+ }
+
+ }
+
+ /**
+ * credit a customer for the passed transaction
+ *
+ * @access public
+ * @param array $attribs
+ * @return object Braintree_Result_Successful or Braintree_Result_Error
+ */
+ public static function credit($customerId, $transactionAttribs)
+ {
+ self::_validateId($customerId);
+ return Braintree_Transaction::credit(
+ array_merge($transactionAttribs,
+ array('customerId' => $customerId)
+ )
+ );
+ }
+
+ /**
+ * 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
+ */
+ public static function creditNoValidate($customerId, $transactionAttribs)
+ {
+ $result = self::credit($customerId, $transactionAttribs);
+ return self::returnObjectOrThrowException('Braintree_Transaction', $result);
+ }
+
+ /**
+ * delete a customer by id
+ *
+ * @param string $customerId
+ */
+ public static function delete($customerId)
+ {
+ self::_validateId($customerId);
+ Braintree_Http::delete('/customers/' . $customerId);
+ return new Braintree_Result_Successful();
+ }
+
+ /**
+ * create a new sale for a customer
+ *
+ * @param string $customerId
+ * @param array $transactionAttribs
+ * @return object Braintree_Result_Successful or Braintree_Result_Error
+ * @see Braintree_Transaction::sale()
+ */
+ public static function sale($customerId, $transactionAttribs)
+ {
+ self::_validateId($customerId);
+ return Braintree_Transaction::sale(
+ array_merge($transactionAttribs,
+ array('customerId' => $customerId)
+ )
+ );
+ }
+
+ /**
+ * create a new sale for a customer, assuming validations will pass
+ *
+ * returns a Braintree_Transaction object on success
+ * @access public
+ * @param string $customerId
+ * @param array $transactionAttribs
+ * @return object Braintree_Transaction
+ * @throws Braintree_Exception_ValidationsFailed
+ * @see Braintree_Transaction::sale()
+ */
+ public static function saleNoValidate($customerId, $transactionAttribs)
+ {
+ $result = self::sale($customerId, $transactionAttribs);
+ return self::returnObjectOrThrowException('Braintree_Transaction', $result);
+ }
+
+ /**
+ * 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 array $attributes
+ * @param string $customerId (optional)
+ * @return object Braintree_Result_Successful or Braintree_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)
+ );
+ }
+
+ /**
+ * 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
+ * @param array $attributes
+ * @param string $customerId
+ * @return object Braintree_Customer
+ * @throws Braintree_Exception_ValidationsFailed
+ */
+ public static function updateNoValidate($customerId, $attributes)
+ {
+ $result = self::update($customerId, $attributes);
+ return self::returnObjectOrThrowException(__CLASS__, $result);
+ }
+ /**
+ *
+ * @access public
+ * @param none
+ * @return string
+ */
+ public static function updateCustomerUrl()
+ {
+ return Braintree_Configuration::merchantUrl() .
+ '/customers/all/update_via_transparent_redirect_request';
+ }
+
+ /**
+ * update a customer from a TransparentRedirect operation
+ *
+ * @access public
+ * @param array $attribs
+ * @return object
+ */
+ public static function updateFromTransparentRedirect($queryString)
+ {
+ $params = Braintree_TransparentRedirect::parseAndValidateQueryString(
+ $queryString
+ );
+ return self::_doUpdate(
+ 'post',
+ '/customers/all/confirm_transparent_redirect_request',
+ array('id' => $params['id'])
+ );
+ }
+
+ /* instance methods */
+
+ /**
+ * sets instance properties from an array of values
+ *
+ * @access protected
+ * @param array $customerAttribs array of customer data
+ * @return none
+ */
+ protected function _initialize($customerAttribs)
+ {
+ // set the attributes
+ $this->_attributes = array_merge($this->_attributes, $customerAttribs);
+
+ // map each address into its own object
+ $addressArray = null;
+ if (isset($customerAttribs['addresses'])) {
+
+ foreach ($customerAttribs['addresses'] AS $address) {
+ $addressArray[] = Braintree_Address::factory($address);
+ }
+ }
+ $this->_set('addresses', $addressArray);
+
+ // map each creditcard into its own object
+ $ccArray = null;
+ if (isset($customerAttribs['creditCards'])) {
+ foreach ($customerAttribs['creditCards'] AS $creditCard) {
+ $ccArray[] = Braintree_CreditCard::factory($creditCard);
+ }
+ }
+ $this->_set('creditCards', $ccArray);
+
+ }
+
+ /**
+ * returns private/nonexistent instance properties
+ * @ignore
+ * @access public
+ * @param string $name property name
+ * @return mixed contents of instance properties
+ */
+ public function __get($name)
+ {
+ if (array_key_exists($name, $this->_attributes)) {
+ return $this->_attributes[$name];
+ }
+ else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * returns a string representation of the customer
+ * @return string
+ */
+ public function __toString()
+ {
+ foreach ($this->_attributes AS $key => $value) {
+ if (is_array($value)) {
+ foreach ($value AS $obj) {
+ $pAttrib .= sprintf('%s', $obj);
+ }
+ } else {
+ $pAttrib = $value;
+ }
+ $printableAttribs[$key] = sprintf('%s', $pAttrib);
+ }
+ return __CLASS__ . '[' .
+ Braintree_Util::implodeAssociativeArray($printableAttribs) .']';
+ }
+
+ /**
+ * returns false if comparing object is not a Braintree_Customer,
+ * or is a Braintree_Customer with a different id
+ *
+ * @param object $otherCust customer to compare against
+ * @return boolean
+ */
+ public function isEqual($otherCust)
+ {
+ return !is_a('Braintree_Customer', $otherCust) ?
+ false :
+ $this->id === $otherCust->id;
+ }
+
+ /* private class properties */
+
+ /**
+ * @access protected
+ * @var array registry of customer data
+ */
+ private $_attributes = array(
+ 'addresses' => '',
+ 'company' => '',
+ 'creditCards' => '',
+ 'email' => '',
+ 'fax' => '',
+ 'firstName' => '',
+ 'id' => '',
+ 'lastName' => '',
+ 'phone' => '',
+ 'createdAt' => '',
+ 'updatedAt' => '',
+ 'website' => '',
+ );
+
+ /**
+ * sets private properties
+ * this function is private so values are read only
+ * @access protected
+ * @param string $key
+ * @param mixed $value
+ */
+ private function _set($key, $value)
+ {
+ $this->_attributes[$key] = $value;
+ }
+
+ /**
+ * verifies that a valid customer id is being used
+ * @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 create request to the gateway
+ *
+ * @param string $url
+ * @param array $params
+ * @return mixed
+ */
+ private static function _doCreate($url, $params)
+ {
+ $response = Braintree_Http::post($url, $params);
+
+ return self::_verifyGatewayResponse($response);
+ }
+ /**
+ * sends the update request to the gateway
+ *
+ * @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.
+ *
+ * @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
+ * to the requesting method, with populated properties
+ *
+ * @return object instance of Braintree_Customer
+ */
+ public static function factory($attributes)
+ {
+ $instance = new self();
+ $instance->_initialize($attributes);
+ return $instance;
+ }
+
+}
62 lib/Braintree/Digest.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Digest encryption module
+ *
+ * @package Braintree
+ * @subpackage Utility
+ * @copyright 2010 Braintree Payment Solutions
+ */
+
+/**
+ * Digest creates an HMAC-SHA1 hash for encrypting messages
+ *
+ * @package Braintree
+ * @subpackage Utility
+ * @copyright 2010 Braintree Payment Solutions
+ */
+class Braintree_Digest
+{
+ /**
+ * public interface