CreditCardGateway.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <?php
  2. namespace Braintree;
  3. use InvalidArgumentException;
  4. /**
  5. * Braintree CreditCardGateway module
  6. * Creates and manages Braintree CreditCards
  7. *
  8. * <b>== More information ==</b>
  9. *
  10. * 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}<br />
  11. * 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}
  12. *
  13. * @package Braintree
  14. * @category Resources
  15. */
  16. class CreditCardGateway
  17. {
  18. private $_gateway;
  19. private $_config;
  20. private $_http;
  21. public function __construct($gateway)
  22. {
  23. $this->_gateway = $gateway;
  24. $this->_config = $gateway->config;
  25. $this->_config->assertHasAccessTokenOrKeys();
  26. $this->_http = new Http($gateway->config);
  27. }
  28. public function create($attribs)
  29. {
  30. Util::verifyKeys(self::createSignature(), $attribs);
  31. return $this->_doCreate('/payment_methods', ['credit_card' => $attribs]);
  32. }
  33. /**
  34. * attempts the create operation assuming all data will validate
  35. * returns a CreditCard object instead of a Result
  36. *
  37. * @access public
  38. * @param array $attribs
  39. * @return CreditCard
  40. * @throws Exception\ValidationError
  41. */
  42. public function createNoValidate($attribs)
  43. {
  44. $result = $this->create($attribs);
  45. return Util::returnObjectOrThrowException(__CLASS__, $result);
  46. }
  47. /**
  48. * returns a ResourceCollection of expired credit cards
  49. * @return ResourceCollection
  50. */
  51. public function expired()
  52. {
  53. $path = $this->_config->merchantPath() . '/payment_methods/all/expired_ids';
  54. $response = $this->_http->post($path);
  55. $pager = [
  56. 'object' => $this,
  57. 'method' => 'fetchExpired',
  58. 'methodArgs' => []
  59. ];
  60. return new ResourceCollection($response, $pager);
  61. }
  62. public function fetchExpired($ids)
  63. {
  64. $path = $this->_config->merchantPath() . "/payment_methods/all/expired";
  65. $response = $this->_http->post($path, ['search' => ['ids' => $ids]]);
  66. return Util::extractattributeasarray(
  67. $response['paymentMethods'],
  68. 'creditCard'
  69. );
  70. }
  71. /**
  72. * returns a ResourceCollection of credit cards expiring between start/end
  73. *
  74. * @return ResourceCollection
  75. */
  76. public function expiringBetween($startDate, $endDate)
  77. {
  78. $queryPath = $this->_config->merchantPath() . '/payment_methods/all/expiring_ids?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate);
  79. $response = $this->_http->post($queryPath);
  80. $pager = [
  81. 'object' => $this,
  82. 'method' => 'fetchExpiring',
  83. 'methodArgs' => [$startDate, $endDate]
  84. ];
  85. return new ResourceCollection($response, $pager);
  86. }
  87. public function fetchExpiring($startDate, $endDate, $ids)
  88. {
  89. $queryPath = $this->_config->merchantPath() . '/payment_methods/all/expiring?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate);
  90. $response = $this->_http->post($queryPath, ['search' => ['ids' => $ids]]);
  91. return Util::extractAttributeAsArray(
  92. $response['paymentMethods'],
  93. 'creditCard'
  94. );
  95. }
  96. /**
  97. * find a creditcard by token
  98. *
  99. * @access public
  100. * @param string $token credit card unique id
  101. * @return CreditCard
  102. * @throws Exception\NotFound
  103. */
  104. public function find($token)
  105. {
  106. $this->_validateId($token);
  107. try {
  108. $path = $this->_config->merchantPath() . '/payment_methods/credit_card/' . $token;
  109. $response = $this->_http->get($path);
  110. return CreditCard::factory($response['creditCard']);
  111. } catch (Exception\NotFound $e) {
  112. throw new Exception\NotFound(
  113. 'credit card with token ' . $token . ' not found'
  114. );
  115. }
  116. }
  117. /**
  118. * Convert a payment method nonce to a credit card
  119. *
  120. * @access public
  121. * @param string $nonce payment method nonce
  122. * @return CreditCard
  123. * @throws Exception\NotFound
  124. */
  125. public function fromNonce($nonce)
  126. {
  127. $this->_validateId($nonce, "nonce");
  128. try {
  129. $path = $this->_config->merchantPath() . '/payment_methods/from_nonce/' . $nonce;
  130. $response = $this->_http->get($path);
  131. return CreditCard::factory($response['creditCard']);
  132. } catch (Exception\NotFound $e) {
  133. throw new Exception\NotFound(
  134. 'credit card with nonce ' . $nonce . ' locked, consumed or not found'
  135. );
  136. }
  137. }
  138. /**
  139. * create a credit on the card for the passed transaction
  140. *
  141. * @access public
  142. * @param array $attribs
  143. * @return Result\Successful|Result\Error
  144. */
  145. public function credit($token, $transactionAttribs)
  146. {
  147. $this->_validateId($token);
  148. return Transaction::credit(
  149. array_merge(
  150. $transactionAttribs,
  151. ['paymentMethodToken' => $token]
  152. )
  153. );
  154. }
  155. /**
  156. * create a credit on this card, assuming validations will pass
  157. *
  158. * returns a Transaction object on success
  159. *
  160. * @access public
  161. * @param array $attribs
  162. * @return Transaction
  163. * @throws Exception\ValidationError
  164. */
  165. public function creditNoValidate($token, $transactionAttribs)
  166. {
  167. $result = $this->credit($token, $transactionAttribs);
  168. return Util::returnObjectOrThrowException('Braintree\Transaction', $result);
  169. }
  170. /**
  171. * create a new sale for the current card
  172. *
  173. * @param string $token
  174. * @param array $transactionAttribs
  175. * @return Result\Successful|Result\Error
  176. * @see Transaction::sale()
  177. */
  178. public function sale($token, $transactionAttribs)
  179. {
  180. $this->_validateId($token);
  181. return Transaction::sale(
  182. array_merge(
  183. $transactionAttribs,
  184. ['paymentMethodToken' => $token]
  185. )
  186. );
  187. }
  188. /**
  189. * create a new sale using this card, assuming validations will pass
  190. *
  191. * returns a Transaction object on success
  192. *
  193. * @access public
  194. * @param array $transactionAttribs
  195. * @param string $token
  196. * @return Transaction
  197. * @throws Exception\ValidationsFailed
  198. * @see Transaction::sale()
  199. */
  200. public function saleNoValidate($token, $transactionAttribs)
  201. {
  202. $result = $this->sale($token, $transactionAttribs);
  203. return Util::returnObjectOrThrowException('Braintree\Transaction', $result);
  204. }
  205. /**
  206. * updates the creditcard record
  207. *
  208. * if calling this method in context, $token
  209. * is the 2nd attribute. $token is not sent in object context.
  210. *
  211. * @access public
  212. * @param array $attributes
  213. * @param string $token (optional)
  214. * @return Result\Successful|Result\Error
  215. */
  216. public function update($token, $attributes)
  217. {
  218. Util::verifyKeys(self::updateSignature(), $attributes);
  219. $this->_validateId($token);
  220. return $this->_doUpdate('put', '/payment_methods/credit_card/' . $token, ['creditCard' => $attributes]);
  221. }
  222. /**
  223. * update a creditcard record, assuming validations will pass
  224. *
  225. * if calling this method in context, $token
  226. * is the 2nd attribute. $token is not sent in object context.
  227. * returns a CreditCard object on success
  228. *
  229. * @access public
  230. * @param array $attributes
  231. * @param string $token
  232. * @return CreditCard
  233. * @throws Exception\ValidationsFailed
  234. */
  235. public function updateNoValidate($token, $attributes)
  236. {
  237. $result = $this->update($token, $attributes);
  238. return Util::returnObjectOrThrowException(__CLASS__, $result);
  239. }
  240. public function delete($token)
  241. {
  242. $this->_validateId($token);
  243. $path = $this->_config->merchantPath() . '/payment_methods/credit_card/' . $token;
  244. $this->_http->delete($path);
  245. return new Result\Successful();
  246. }
  247. private static function baseOptions()
  248. {
  249. return ['makeDefault', 'verificationMerchantAccountId', 'verifyCard', 'verificationAmount', 'verificationAccountType', 'venmoSdkSession'];
  250. }
  251. private static function baseSignature($options)
  252. {
  253. return [
  254. 'billingAddressId', 'cardholderName', 'cvv', 'number', 'deviceSessionId',
  255. 'expirationDate', 'expirationMonth', 'expirationYear', 'token', 'venmoSdkPaymentMethodCode',
  256. 'deviceData', 'fraudMerchantId', 'paymentMethodNonce',
  257. ['options' => $options],
  258. [
  259. 'billingAddress' => self::billingAddressSignature()
  260. ],
  261. ];
  262. }
  263. public static function billingAddressSignature()
  264. {
  265. return [
  266. 'firstName',
  267. 'lastName',
  268. 'company',
  269. 'countryCodeAlpha2',
  270. 'countryCodeAlpha3',
  271. 'countryCodeNumeric',
  272. 'countryName',
  273. 'extendedAddress',
  274. 'locality',
  275. 'region',
  276. 'postalCode',
  277. 'streetAddress'
  278. ];
  279. }
  280. public static function createSignature()
  281. {
  282. $options = self::baseOptions();
  283. $options[] = "failOnDuplicatePaymentMethod";
  284. $signature = self::baseSignature($options);
  285. $signature[] = 'customerId';
  286. return $signature;
  287. }
  288. public static function updateSignature()
  289. {
  290. $options = self::baseOptions();
  291. $options[] = "failOnDuplicatePaymentMethod";
  292. $signature = self::baseSignature($options);
  293. $updateExistingBillingSignature = [
  294. [
  295. 'options' => [
  296. 'updateExisting'
  297. ]
  298. ]
  299. ];
  300. foreach($signature AS $key => $value) {
  301. if(is_array($value) and array_key_exists('billingAddress', $value)) {
  302. $signature[$key]['billingAddress'] = array_merge_recursive($value['billingAddress'], $updateExistingBillingSignature);
  303. }
  304. }
  305. return $signature;
  306. }
  307. /**
  308. * sends the create request to the gateway
  309. *
  310. * @ignore
  311. * @param string $subPath
  312. * @param array $params
  313. * @return mixed
  314. */
  315. public function _doCreate($subPath, $params)
  316. {
  317. $fullPath = $this->_config->merchantPath() . $subPath;
  318. $response = $this->_http->post($fullPath, $params);
  319. return $this->_verifyGatewayResponse($response);
  320. }
  321. /**
  322. * verifies that a valid credit card identifier is being used
  323. * @ignore
  324. * @param string $identifier
  325. * @param Optional $string $identifierType type of identifier supplied, default "token"
  326. * @throws InvalidArgumentException
  327. */
  328. private function _validateId($identifier = null, $identifierType = "token")
  329. {
  330. if (empty($identifier)) {
  331. throw new InvalidArgumentException(
  332. 'expected credit card id to be set'
  333. );
  334. }
  335. if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) {
  336. throw new InvalidArgumentException(
  337. $identifier . ' is an invalid credit card ' . $identifierType . '.'
  338. );
  339. }
  340. }
  341. /**
  342. * sends the update request to the gateway
  343. *
  344. * @ignore
  345. * @param string $url
  346. * @param array $params
  347. * @return mixed
  348. */
  349. private function _doUpdate($httpVerb, $subPath, $params)
  350. {
  351. $fullPath = $this->_config->merchantPath() . $subPath;
  352. $response = $this->_http->$httpVerb($fullPath, $params);
  353. return $this->_verifyGatewayResponse($response);
  354. }
  355. /**
  356. * generic method for validating incoming gateway responses
  357. *
  358. * creates a new CreditCard object and encapsulates
  359. * it inside a Result\Successful object, or
  360. * encapsulates a Errors object inside a Result\Error
  361. * alternatively, throws an Unexpected exception if the response is invalid
  362. *
  363. * @ignore
  364. * @param array $response gateway response values
  365. * @return Result\Successful|Result\Error
  366. * @throws Exception\Unexpected
  367. */
  368. private function _verifyGatewayResponse($response)
  369. {
  370. if (isset($response['creditCard'])) {
  371. // return a populated instance of Address
  372. return new Result\Successful(
  373. CreditCard::factory($response['creditCard'])
  374. );
  375. } elseif (isset($response['apiErrorResponse'])) {
  376. return new Result\Error($response['apiErrorResponse']);
  377. } else {
  378. throw new Exception\Unexpected(
  379. "Expected address or apiErrorResponse"
  380. );
  381. }
  382. }
  383. }
  384. class_alias('Braintree\CreditCardGateway', 'Braintree_CreditCardGateway');