TransactionGateway.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <?php
  2. namespace Braintree;
  3. use InvalidArgumentException;
  4. /**
  5. * Braintree TransactionGateway processor
  6. * Creates and manages transactions
  7. *
  8. *
  9. * <b>== More information ==</b>
  10. *
  11. * For more detailed information on Transactions, see {@link https://developers.braintreepayments.com/reference/response/transaction/php https://developers.braintreepayments.com/reference/response/transaction/php}
  12. *
  13. * @package Braintree
  14. * @category Resources
  15. */
  16. class TransactionGateway
  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 cloneTransaction($transactionId, $attribs)
  29. {
  30. Util::verifyKeys(self::cloneSignature(), $attribs);
  31. return $this->_doCreate('/transactions/' . $transactionId . '/clone', ['transactionClone' => $attribs]);
  32. }
  33. /**
  34. * @ignore
  35. * @access private
  36. * @param array $attribs
  37. * @return Result\Successful|Result\Error
  38. */
  39. private function create($attribs)
  40. {
  41. Util::verifyKeys(self::createSignature(), $attribs);
  42. return $this->_doCreate('/transactions', ['transaction' => $attribs]);
  43. }
  44. /**
  45. * @ignore
  46. * @access private
  47. * @param array $attribs
  48. * @return object
  49. * @throws Exception\ValidationError
  50. */
  51. private function createNoValidate($attribs)
  52. {
  53. $result = $this->create($attribs);
  54. return Util::returnObjectOrThrowException(__CLASS__, $result);
  55. }
  56. /**
  57. *
  58. * @deprecated since version 2.3.0
  59. * @access public
  60. * @param array $attribs
  61. * @return object
  62. */
  63. public function createFromTransparentRedirect($queryString)
  64. {
  65. trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE);
  66. $params = TransparentRedirect::parseAndValidateQueryString(
  67. $queryString
  68. );
  69. return $this->_doCreate(
  70. '/transactions/all/confirm_transparent_redirect_request',
  71. ['id' => $params['id']]
  72. );
  73. }
  74. /**
  75. *
  76. * @deprecated since version 2.3.0
  77. * @access public
  78. * @param none
  79. * @return string
  80. */
  81. public function createTransactionUrl()
  82. {
  83. trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE);
  84. return $this->_config->baseUrl() . $this->_config->merchantPath() .
  85. '/transactions/all/create_via_transparent_redirect_request';
  86. }
  87. public static function cloneSignature()
  88. {
  89. return ['amount', 'channel', ['options' => ['submitForSettlement']]];
  90. }
  91. /**
  92. * creates a full array signature of a valid gateway request
  93. * @return array gateway request signature format
  94. */
  95. public static function createSignature()
  96. {
  97. return [
  98. 'amount',
  99. 'billingAddressId',
  100. 'channel',
  101. 'customerId',
  102. 'deviceData',
  103. 'deviceSessionId',
  104. 'fraudMerchantId',
  105. 'merchantAccountId',
  106. 'orderId',
  107. 'paymentMethodNonce',
  108. 'paymentMethodToken',
  109. 'purchaseOrderNumber',
  110. 'recurring',
  111. 'serviceFeeAmount',
  112. 'sharedPaymentMethodToken',
  113. 'sharedPaymentMethodNonce',
  114. 'sharedCustomerId',
  115. 'sharedShippingAddressId',
  116. 'sharedBillingAddressId',
  117. 'shippingAddressId',
  118. 'taxAmount',
  119. 'taxExempt',
  120. 'threeDSecureToken',
  121. 'transactionSource',
  122. 'type',
  123. 'venmoSdkPaymentMethodCode',
  124. 'shippingAmount',
  125. 'discountAmount',
  126. 'shipsFromPostalCode',
  127. ['riskData' =>
  128. ['customerBrowser', 'customerIp', 'customer_browser', 'customer_ip']
  129. ],
  130. ['creditCard' =>
  131. ['token', 'cardholderName', 'cvv', 'expirationDate', 'expirationMonth', 'expirationYear', 'number'],
  132. ],
  133. ['customer' =>
  134. [
  135. 'id', 'company', 'email', 'fax', 'firstName',
  136. 'lastName', 'phone', 'website'],
  137. ],
  138. ['billing' =>
  139. [
  140. 'firstName', 'lastName', 'company', 'countryName',
  141. 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric',
  142. 'extendedAddress', 'locality', 'postalCode', 'region',
  143. 'streetAddress'],
  144. ],
  145. ['shipping' =>
  146. [
  147. 'firstName', 'lastName', 'company', 'countryName',
  148. 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric',
  149. 'extendedAddress', 'locality', 'postalCode', 'region',
  150. 'streetAddress'],
  151. ],
  152. ['threeDSecurePassThru' =>
  153. [
  154. 'eciFlag',
  155. 'cavv',
  156. 'xid'],
  157. ],
  158. ['options' =>
  159. [
  160. 'holdInEscrow',
  161. 'storeInVault',
  162. 'storeInVaultOnSuccess',
  163. 'submitForSettlement',
  164. 'addBillingAddressToPaymentMethod',
  165. 'venmoSdkSession',
  166. 'storeShippingAddressInVault',
  167. 'payeeId',
  168. 'payeeEmail',
  169. 'skipAdvancedFraudChecking',
  170. 'skipAvs',
  171. 'skipCvv',
  172. ['threeDSecure' =>
  173. ['required']
  174. ],
  175. # TODO: Snake case version included for backwards compatiblity. Remove in the next major version
  176. ['three_d_secure' =>
  177. ['required']
  178. ],
  179. ['paypal' =>
  180. [
  181. 'payeeId',
  182. 'payeeEmail',
  183. 'customField',
  184. 'description',
  185. ['supplementaryData' => ['_anyKey_']],
  186. ]
  187. ],
  188. ['amexRewards' =>
  189. [
  190. 'requestId',
  191. 'points',
  192. 'currencyAmount',
  193. 'currencyIsoCode'
  194. ]
  195. ],
  196. ['venmo' =>
  197. [
  198. # TODO: Snake case version included for backwards compatiblity. Remove in the next major version
  199. 'profile_id',
  200. 'profileId'
  201. ]
  202. ]
  203. ],
  204. ],
  205. ['customFields' => ['_anyKey_']],
  206. ['descriptor' => ['name', 'phone', 'url']],
  207. ['paypalAccount' => ['payeeId', 'payeeEmail']],
  208. # TODO: Snake case version included for backwards compatiblity. Remove in the next major version
  209. ['apple_pay_card' => ['number', 'cardholder_name', 'cryptogram', 'expiration_month', 'expiration_year', 'eci_indicator']],
  210. ['applePayCard' => ['number', 'cardholderName', 'cryptogram', 'expirationMonth', 'expirationYear', 'eciIndicator']],
  211. ['industry' =>
  212. ['industryType',
  213. ['data' =>
  214. [
  215. 'folioNumber',
  216. 'checkInDate',
  217. 'checkOutDate',
  218. 'travelPackage',
  219. 'departureDate',
  220. 'lodgingCheckInDate',
  221. 'lodgingCheckOutDate',
  222. 'lodgingName',
  223. 'roomRate'
  224. ]
  225. ]
  226. ]
  227. ],
  228. ['lineItems' => ['quantity', 'name', 'description', 'kind', 'unitAmount', 'unitTaxAmount', 'totalAmount', 'discountAmount', 'taxAmount', 'unitOfMeasure', 'productCode', 'commodityCode', 'url']],
  229. ];
  230. }
  231. public static function submitForSettlementSignature()
  232. {
  233. return ['orderId', ['descriptor' => ['name', 'phone', 'url']]];
  234. }
  235. public static function updateDetailsSignature()
  236. {
  237. return ['amount', 'orderId', ['descriptor' => ['name', 'phone', 'url']]];
  238. }
  239. public static function refundSignature()
  240. {
  241. return ['amount', 'orderId'];
  242. }
  243. /**
  244. *
  245. * @access public
  246. * @param array $attribs
  247. * @return Result\Successful|Result\Error
  248. */
  249. public function credit($attribs)
  250. {
  251. return $this->create(array_merge($attribs, ['type' => Transaction::CREDIT]));
  252. }
  253. /**
  254. *
  255. * @access public
  256. * @param array $attribs
  257. * @return Result\Successful|Result\Error
  258. * @throws Exception\ValidationError
  259. */
  260. public function creditNoValidate($attribs)
  261. {
  262. $result = $this->credit($attribs);
  263. return Util::returnObjectOrThrowException(__CLASS__, $result);
  264. }
  265. /**
  266. * @access public
  267. * @param string id
  268. * @return Transaction
  269. */
  270. public function find($id)
  271. {
  272. $this->_validateId($id);
  273. try {
  274. $path = $this->_config->merchantPath() . '/transactions/' . $id;
  275. $response = $this->_http->get($path);
  276. return Transaction::factory($response['transaction']);
  277. } catch (Exception\NotFound $e) {
  278. throw new Exception\NotFound(
  279. 'transaction with id ' . $id . ' not found'
  280. );
  281. }
  282. }
  283. /**
  284. * new sale
  285. * @param array $attribs
  286. * @return Result\Successful|Result\Error
  287. */
  288. public function sale($attribs)
  289. {
  290. return $this->create(array_merge(['type' => Transaction::SALE], $attribs));
  291. }
  292. /**
  293. * roughly equivalent to the ruby bang method
  294. * @access public
  295. * @param array $attribs
  296. * @return array
  297. * @throws Exception\ValidationsFailed
  298. */
  299. public function saleNoValidate($attribs)
  300. {
  301. $result = $this->sale($attribs);
  302. return Util::returnObjectOrThrowException(__CLASS__, $result);
  303. }
  304. /**
  305. * Returns a ResourceCollection of transactions matching the search query.
  306. *
  307. * If <b>query</b> is a string, the search will be a basic search.
  308. * If <b>query</b> is a hash, the search will be an advanced search.
  309. * 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}
  310. *
  311. * @param mixed $query search query
  312. * @param array $options options such as page number
  313. * @return ResourceCollection
  314. * @throws InvalidArgumentException
  315. */
  316. public function search($query)
  317. {
  318. $criteria = [];
  319. foreach ($query as $term) {
  320. $criteria[$term->name] = $term->toparam();
  321. }
  322. $path = $this->_config->merchantPath() . '/transactions/advanced_search_ids';
  323. $response = $this->_http->post($path, ['search' => $criteria]);
  324. if (array_key_exists('searchResults', $response)) {
  325. $pager = [
  326. 'object' => $this,
  327. 'method' => 'fetch',
  328. 'methodArgs' => [$query]
  329. ];
  330. return new ResourceCollection($response, $pager);
  331. } else {
  332. throw new Exception\DownForMaintenance();
  333. }
  334. }
  335. public function fetch($query, $ids)
  336. {
  337. $criteria = [];
  338. foreach ($query as $term) {
  339. $criteria[$term->name] = $term->toparam();
  340. }
  341. $criteria["ids"] = TransactionSearch::ids()->in($ids)->toparam();
  342. $path = $this->_config->merchantPath() . '/transactions/advanced_search';
  343. $response = $this->_http->post($path, ['search' => $criteria]);
  344. if (array_key_exists('creditCardTransactions', $response)) {
  345. return Util::extractattributeasarray(
  346. $response['creditCardTransactions'],
  347. 'transaction'
  348. );
  349. } else {
  350. throw new Exception\DownForMaintenance();
  351. }
  352. }
  353. /**
  354. * void a transaction by id
  355. *
  356. * @param string $id transaction id
  357. * @return Result\Successful|Result\Error
  358. */
  359. public function void($transactionId)
  360. {
  361. $this->_validateId($transactionId);
  362. $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/void';
  363. $response = $this->_http->put($path);
  364. return $this->_verifyGatewayResponse($response);
  365. }
  366. /**
  367. *
  368. */
  369. public function voidNoValidate($transactionId)
  370. {
  371. $result = $this->void($transactionId);
  372. return Util::returnObjectOrThrowException(__CLASS__, $result);
  373. }
  374. public function submitForSettlement($transactionId, $amount = null, $attribs = [])
  375. {
  376. $this->_validateId($transactionId);
  377. Util::verifyKeys(self::submitForSettlementSignature(), $attribs);
  378. $attribs['amount'] = $amount;
  379. $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/submit_for_settlement';
  380. $response = $this->_http->put($path, ['transaction' => $attribs]);
  381. return $this->_verifyGatewayResponse($response);
  382. }
  383. public function submitForSettlementNoValidate($transactionId, $amount = null, $attribs = [])
  384. {
  385. $result = $this->submitForSettlement($transactionId, $amount, $attribs);
  386. return Util::returnObjectOrThrowException(__CLASS__, $result);
  387. }
  388. public function updateDetails($transactionId, $attribs = [])
  389. {
  390. $this->_validateId($transactionId);
  391. Util::verifyKeys(self::updateDetailsSignature(), $attribs);
  392. $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/update_details';
  393. $response = $this->_http->put($path, ['transaction' => $attribs]);
  394. return $this->_verifyGatewayResponse($response);
  395. }
  396. public function submitForPartialSettlement($transactionId, $amount, $attribs = [])
  397. {
  398. $this->_validateId($transactionId);
  399. Util::verifyKeys(self::submitForSettlementSignature(), $attribs);
  400. $attribs['amount'] = $amount;
  401. $path = $this->_config->merchantPath() . '/transactions/'. $transactionId . '/submit_for_partial_settlement';
  402. $response = $this->_http->post($path, ['transaction' => $attribs]);
  403. return $this->_verifyGatewayResponse($response);
  404. }
  405. public function holdInEscrow($transactionId)
  406. {
  407. $this->_validateId($transactionId);
  408. $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/hold_in_escrow';
  409. $response = $this->_http->put($path, []);
  410. return $this->_verifyGatewayResponse($response);
  411. }
  412. public function releaseFromEscrow($transactionId)
  413. {
  414. $this->_validateId($transactionId);
  415. $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/release_from_escrow';
  416. $response = $this->_http->put($path, []);
  417. return $this->_verifyGatewayResponse($response);
  418. }
  419. public function cancelRelease($transactionId)
  420. {
  421. $this->_validateId($transactionId);
  422. $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/cancel_release';
  423. $response = $this->_http->put($path, []);
  424. return $this->_verifyGatewayResponse($response);
  425. }
  426. public function refund($transactionId, $amount_or_options = null)
  427. {
  428. self::_validateId($transactionId);
  429. if(gettype($amount_or_options) == "array") {
  430. $options = $amount_or_options;
  431. } else {
  432. $options = [
  433. "amount" => $amount_or_options
  434. ];
  435. }
  436. Util::verifyKeys(self::refundSignature(), $options);
  437. $params = ['transaction' => $options];
  438. $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/refund';
  439. $response = $this->_http->post($path, $params);
  440. return $this->_verifyGatewayResponse($response);
  441. }
  442. /**
  443. * sends the create request to the gateway
  444. *
  445. * @ignore
  446. * @param var $subPath
  447. * @param array $params
  448. * @return Result\Successful|Result\Error
  449. */
  450. public function _doCreate($subPath, $params)
  451. {
  452. $fullPath = $this->_config->merchantPath() . $subPath;
  453. $response = $this->_http->post($fullPath, $params);
  454. return $this->_verifyGatewayResponse($response);
  455. }
  456. /**
  457. * verifies that a valid transaction id is being used
  458. * @ignore
  459. * @param string transaction id
  460. * @throws InvalidArgumentException
  461. */
  462. private function _validateId($id = null) {
  463. if (empty($id)) {
  464. throw new InvalidArgumentException(
  465. 'expected transaction id to be set'
  466. );
  467. }
  468. if (!preg_match('/^[0-9a-z]+$/', $id)) {
  469. throw new InvalidArgumentException(
  470. $id . ' is an invalid transaction id.'
  471. );
  472. }
  473. }
  474. /**
  475. * generic method for validating incoming gateway responses
  476. *
  477. * creates a new Transaction object and encapsulates
  478. * it inside a Result\Successful object, or
  479. * encapsulates a Errors object inside a Result\Error
  480. * alternatively, throws an Unexpected exception if the response is invalid.
  481. *
  482. * @ignore
  483. * @param array $response gateway response values
  484. * @return Result\Successful|Result\Error
  485. * @throws Exception\Unexpected
  486. */
  487. private function _verifyGatewayResponse($response)
  488. {
  489. if (isset($response['transaction'])) {
  490. // return a populated instance of Transaction
  491. return new Result\Successful(
  492. Transaction::factory($response['transaction'])
  493. );
  494. } else if (isset($response['apiErrorResponse'])) {
  495. return new Result\Error($response['apiErrorResponse']);
  496. } else {
  497. throw new Exception\Unexpected(
  498. "Expected transaction or apiErrorResponse"
  499. );
  500. }
  501. }
  502. }
  503. class_alias('Braintree\TransactionGateway', 'Braintree_TransactionGateway');