Nvp.php 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Paypal\Model\Api;
  7. use Magento\Payment\Model\Cart;
  8. use Magento\Payment\Model\Method\Logger;
  9. /**
  10. * NVP API wrappers model
  11. * @TODO: move some parts to abstract, don't hesitate to throw exceptions on api calls
  12. *
  13. * @method string getToken()
  14. * @SuppressWarnings(PHPMD.TooManyFields)
  15. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  16. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  17. */
  18. class Nvp extends \Magento\Paypal\Model\Api\AbstractApi
  19. {
  20. /**
  21. * Paypal methods definition
  22. */
  23. const DO_DIRECT_PAYMENT = 'DoDirectPayment';
  24. const DO_CAPTURE = 'DoCapture';
  25. const DO_AUTHORIZATION = 'DoAuthorization';
  26. const DO_VOID = 'DoVoid';
  27. const REFUND_TRANSACTION = 'RefundTransaction';
  28. const SET_EXPRESS_CHECKOUT = 'SetExpressCheckout';
  29. const GET_EXPRESS_CHECKOUT_DETAILS = 'GetExpressCheckoutDetails';
  30. const DO_EXPRESS_CHECKOUT_PAYMENT = 'DoExpressCheckoutPayment';
  31. const CALLBACK_RESPONSE = 'CallbackResponse';
  32. /**
  33. * Paypal ManagePendingTransactionStatus actions
  34. */
  35. const PENDING_TRANSACTION_ACCEPT = 'Accept';
  36. const PENDING_TRANSACTION_DENY = 'Deny';
  37. /**
  38. * Capture type (make authorization close or remain open)
  39. *
  40. * @var string
  41. */
  42. protected $_captureTypeComplete = 'Complete';
  43. /**
  44. * Capture type (make authorization close or remain open)
  45. *
  46. * @var string
  47. */
  48. protected $_captureTypeNotcomplete = 'NotComplete';
  49. /**
  50. * Global public interface map
  51. *
  52. * @var array
  53. */
  54. protected $_globalMap = [
  55. // each call
  56. 'VERSION' => 'version',
  57. 'USER' => 'api_username',
  58. 'PWD' => 'api_password',
  59. 'SIGNATURE' => 'api_signature',
  60. 'BUTTONSOURCE' => 'build_notation_code',
  61. // for Unilateral payments
  62. 'SUBJECT' => 'business_account',
  63. // commands
  64. 'PAYMENTACTION' => 'payment_action',
  65. 'RETURNURL' => 'return_url',
  66. 'CANCELURL' => 'cancel_url',
  67. 'INVNUM' => 'inv_num',
  68. 'TOKEN' => 'token',
  69. 'CORRELATIONID' => 'correlation_id',
  70. 'SOLUTIONTYPE' => 'solution_type',
  71. 'GIROPAYCANCELURL' => 'giropay_cancel_url',
  72. 'GIROPAYSUCCESSURL' => 'giropay_success_url',
  73. 'BANKTXNPENDINGURL' => 'giropay_bank_txn_pending_url',
  74. 'IPADDRESS' => 'ip_address',
  75. 'NOTIFYURL' => 'notify_url',
  76. 'RETURNFMFDETAILS' => 'fraud_management_filters_enabled',
  77. 'NOTE' => 'note',
  78. 'REFUNDTYPE' => 'refund_type',
  79. 'ACTION' => 'action',
  80. 'REDIRECTREQUIRED' => 'redirect_required',
  81. 'SUCCESSPAGEREDIRECTREQUESTED' => 'redirect_requested',
  82. 'REQBILLINGADDRESS' => 'require_billing_address',
  83. // style settings
  84. 'PAGESTYLE' => 'page_style',
  85. 'HDRIMG' => 'hdrimg',
  86. 'HDRBORDERCOLOR' => 'hdrbordercolor',
  87. 'HDRBACKCOLOR' => 'hdrbackcolor',
  88. 'PAYFLOWCOLOR' => 'payflowcolor',
  89. 'LOCALECODE' => 'locale_code',
  90. 'PAL' => 'pal',
  91. 'USERSELECTEDFUNDINGSOURCE' => 'funding_source',
  92. // transaction info
  93. 'TRANSACTIONID' => 'transaction_id',
  94. 'AUTHORIZATIONID' => 'authorization_id',
  95. 'REFUNDTRANSACTIONID' => 'refund_transaction_id',
  96. 'COMPLETETYPE' => 'complete_type',
  97. 'AMT' => 'amount',
  98. 'ITEMAMT' => 'subtotal_amount',
  99. 'GROSSREFUNDAMT' => 'refunded_amount', // possible mistake, check with API reference
  100. // payment/billing info
  101. 'CURRENCYCODE' => 'currency_code',
  102. 'PAYMENTSTATUS' => 'payment_status',
  103. 'PENDINGREASON' => 'pending_reason',
  104. 'PROTECTIONELIGIBILITY' => 'protection_eligibility',
  105. 'PAYERID' => 'payer_id',
  106. 'PAYERSTATUS' => 'payer_status',
  107. 'ADDRESSID' => 'address_id',
  108. 'ADDRESSSTATUS' => 'address_status',
  109. 'EMAIL' => 'email',
  110. // backwards compatibility
  111. 'FIRSTNAME' => 'firstname',
  112. 'LASTNAME' => 'lastname',
  113. // shipping rate
  114. 'SHIPPINGOPTIONNAME' => 'shipping_rate_code',
  115. 'NOSHIPPING' => 'suppress_shipping',
  116. // paypal direct credit card information
  117. 'CREDITCARDTYPE' => 'credit_card_type',
  118. 'ACCT' => 'credit_card_number',
  119. 'EXPDATE' => 'credit_card_expiration_date',
  120. 'CVV2' => 'credit_card_cvv2',
  121. 'STARTDATE' => 'maestro_solo_issue_date',
  122. 'ISSUENUMBER' => 'maestro_solo_issue_number',
  123. 'CVV2MATCH' => 'cvv2_check_result',
  124. 'AVSCODE' => 'avs_result',
  125. 'SHIPPINGAMT' => 'shipping_amount',
  126. 'TAXAMT' => 'tax_amount',
  127. 'INITAMT' => 'init_amount',
  128. 'STATUS' => 'status',
  129. //Next two fields are used for Brazil only
  130. 'TAXID' => 'buyer_tax_id',
  131. 'TAXIDTYPE' => 'buyer_tax_id_type',
  132. 'BILLINGAGREEMENTID' => 'billing_agreement_id',
  133. 'REFERENCEID' => 'reference_id',
  134. 'BILLINGAGREEMENTSTATUS' => 'billing_agreement_status',
  135. 'BILLINGTYPE' => 'billing_type',
  136. 'SREET' => 'street',
  137. 'CITY' => 'city',
  138. 'STATE' => 'state',
  139. 'COUNTRYCODE' => 'countrycode',
  140. 'ZIP' => 'zip',
  141. 'PAYERBUSINESS' => 'payer_business',
  142. ];
  143. /**
  144. * Filter callback for preparing internal amounts to NVP request
  145. *
  146. * @var array
  147. */
  148. protected $_exportToRequestFilters = [
  149. 'AMT' => 'formatPrice',
  150. 'ITEMAMT' => 'formatPrice',
  151. 'TRIALAMT' => 'formatPrice',
  152. 'SHIPPINGAMT' => 'formatPrice',
  153. 'TAXAMT' => 'formatPrice',
  154. 'INITAMT' => 'formatPrice',
  155. 'CREDITCARDTYPE' => '_filterCcType',
  156. 'AUTOBILLAMT' => '_filterBillFailedLater',
  157. 'BILLINGPERIOD' => '_filterPeriodUnit',
  158. 'TRIALBILLINGPERIOD' => '_filterPeriodUnit',
  159. 'FAILEDINITAMTACTION' => '_filterInitialAmountMayFail',
  160. 'BILLINGAGREEMENTSTATUS' => '_filterBillingAgreementStatus',
  161. 'NOSHIPPING' => '_filterInt',
  162. ];
  163. /**
  164. * Filter callback for preparing internal amounts to NVP request
  165. *
  166. * @var array
  167. */
  168. protected $_importFromRequestFilters = [
  169. 'REDIRECTREQUIRED' => '_filterToBool',
  170. 'SUCCESSPAGEREDIRECTREQUESTED' => '_filterToBool',
  171. 'PAYMENTSTATUS' => '_filterPaymentStatusFromNvpToInfo',
  172. ];
  173. /**
  174. * Request map for each API call
  175. *
  176. * @var string[]
  177. */
  178. protected $_eachCallRequest = ['VERSION', 'USER', 'PWD', 'SIGNATURE', 'BUTTONSOURCE'];
  179. /**
  180. * SetExpressCheckout request map
  181. *
  182. * @var string[]
  183. */
  184. protected $_setExpressCheckoutRequest = [
  185. 'PAYMENTACTION',
  186. 'AMT',
  187. 'CURRENCYCODE',
  188. 'RETURNURL',
  189. 'CANCELURL',
  190. 'INVNUM',
  191. 'SOLUTIONTYPE',
  192. 'NOSHIPPING',
  193. 'GIROPAYCANCELURL',
  194. 'GIROPAYSUCCESSURL',
  195. 'BANKTXNPENDINGURL',
  196. 'PAGESTYLE',
  197. 'HDRIMG',
  198. 'HDRBORDERCOLOR',
  199. 'HDRBACKCOLOR',
  200. 'PAYFLOWCOLOR',
  201. 'LOCALECODE',
  202. 'BILLINGTYPE',
  203. 'SUBJECT',
  204. 'ITEMAMT',
  205. 'SHIPPINGAMT',
  206. 'TAXAMT',
  207. 'REQBILLINGADDRESS',
  208. 'USERSELECTEDFUNDINGSOURCE',
  209. ];
  210. /**
  211. * SetExpressCheckout response map
  212. *
  213. * @var string[]
  214. */
  215. protected $_setExpressCheckoutResponse = ['TOKEN'];
  216. /**
  217. * GetExpressCheckoutDetails request map
  218. *
  219. * @var string[]
  220. */
  221. protected $_getExpressCheckoutDetailsRequest = ['TOKEN', 'SUBJECT'];
  222. /**
  223. * DoExpressCheckoutPayment request map
  224. *
  225. * @var string[]
  226. */
  227. protected $_doExpressCheckoutPaymentRequest = [
  228. 'TOKEN',
  229. 'PAYERID',
  230. 'PAYMENTACTION',
  231. 'AMT',
  232. 'CURRENCYCODE',
  233. 'IPADDRESS',
  234. 'BUTTONSOURCE',
  235. 'NOTIFYURL',
  236. 'RETURNFMFDETAILS',
  237. 'SUBJECT',
  238. 'ITEMAMT',
  239. 'SHIPPINGAMT',
  240. 'TAXAMT',
  241. ];
  242. /**
  243. * DoExpressCheckoutPayment response map
  244. *
  245. * @var string[]
  246. */
  247. protected $_doExpressCheckoutPaymentResponse = [
  248. 'TRANSACTIONID',
  249. 'AMT',
  250. 'PAYMENTSTATUS',
  251. 'PENDINGREASON',
  252. 'REDIRECTREQUIRED',
  253. ];
  254. /**
  255. * DoDirectPayment request map
  256. *
  257. * @var string[]
  258. */
  259. protected $_doDirectPaymentRequest = [
  260. 'PAYMENTACTION',
  261. 'IPADDRESS',
  262. 'RETURNFMFDETAILS',
  263. 'AMT',
  264. 'CURRENCYCODE',
  265. 'INVNUM',
  266. 'NOTIFYURL',
  267. 'EMAIL',
  268. 'ITEMAMT',
  269. 'SHIPPINGAMT',
  270. 'TAXAMT',
  271. 'CREDITCARDTYPE',
  272. 'ACCT',
  273. 'EXPDATE',
  274. 'CVV2',
  275. 'STARTDATE',
  276. 'ISSUENUMBER',
  277. 'AUTHSTATUS3DS',
  278. 'MPIVENDOR3DS',
  279. 'CAVV',
  280. 'ECI3DS',
  281. 'XID',
  282. ];
  283. /**
  284. * DoDirectPayment response map
  285. *
  286. * @var string[]
  287. */
  288. protected $_doDirectPaymentResponse = [
  289. 'TRANSACTIONID',
  290. 'AMT',
  291. 'AVSCODE',
  292. 'CVV2MATCH',
  293. 'VPAS',
  294. 'ECISUBMITTED3DS',
  295. ];
  296. /**
  297. * DoReauthorization request map
  298. *
  299. * @var string[]
  300. */
  301. protected $_doReauthorizationRequest = ['AUTHORIZATIONID', 'AMT', 'CURRENCYCODE'];
  302. /**
  303. * DoReauthorization response map
  304. *
  305. * @var string[]
  306. */
  307. protected $_doReauthorizationResponse = [
  308. 'AUTHORIZATIONID',
  309. 'PAYMENTSTATUS',
  310. 'PENDINGREASON',
  311. 'PROTECTIONELIGIBILITY',
  312. ];
  313. /**
  314. * DoCapture request map
  315. *
  316. * @var string[]
  317. */
  318. protected $_doCaptureRequest = ['AUTHORIZATIONID', 'COMPLETETYPE', 'AMT', 'CURRENCYCODE', 'NOTE', 'INVNUM'];
  319. /**
  320. * DoCapture response map
  321. *
  322. * @var string[]
  323. */
  324. protected $_doCaptureResponse = ['TRANSACTIONID', 'CURRENCYCODE', 'AMT', 'PAYMENTSTATUS', 'PENDINGREASON'];
  325. /**
  326. * DoAuthorization request map
  327. *
  328. * @var string[]
  329. */
  330. protected $_doAuthorizationRequest = ['TRANSACTIONID', 'AMT', 'CURRENCYCODE'];
  331. /**
  332. * DoAuthorization response map
  333. *
  334. * @var string[]
  335. */
  336. protected $_doAuthorizationResponse = ['TRANSACTIONID', 'AMT'];
  337. /**
  338. * DoVoid request map
  339. *
  340. * @var string[]
  341. */
  342. protected $_doVoidRequest = ['AUTHORIZATIONID', 'NOTE'];
  343. /**
  344. * GetTransactionDetailsRequest
  345. *
  346. * @var string[]
  347. */
  348. protected $_getTransactionDetailsRequest = ['TRANSACTIONID'];
  349. /**
  350. * GetTransactionDetailsResponse
  351. *
  352. * @var string[]
  353. */
  354. protected $_getTransactionDetailsResponse = [
  355. 'PAYERID',
  356. 'FIRSTNAME',
  357. 'LASTNAME',
  358. 'TRANSACTIONID',
  359. 'PARENTTRANSACTIONID',
  360. 'CURRENCYCODE',
  361. 'AMT',
  362. 'PAYMENTSTATUS',
  363. 'PENDINGREASON',
  364. ];
  365. /**
  366. * RefundTransaction request map
  367. *
  368. * @var string[]
  369. */
  370. protected $_refundTransactionRequest = ['TRANSACTIONID', 'REFUNDTYPE', 'CURRENCYCODE', 'NOTE'];
  371. /**
  372. * RefundTransaction response map
  373. *
  374. * @var string[]
  375. */
  376. protected $_refundTransactionResponse = ['REFUNDTRANSACTIONID', 'GROSSREFUNDAMT'];
  377. /**
  378. * ManagePendingTransactionStatus request map
  379. *
  380. * @var string[]
  381. */
  382. protected $_managePendingTransactionStatusRequest = ['TRANSACTIONID', 'ACTION'];
  383. /**
  384. * ManagePendingTransactionStatus response map
  385. *
  386. * @var string[]
  387. */
  388. protected $_managePendingTransactionStatusResponse = ['TRANSACTIONID', 'STATUS'];
  389. /**
  390. * GetPalDetails response map
  391. *
  392. * @var string[]
  393. */
  394. protected $_getPalDetailsResponse = ['PAL'];
  395. /**
  396. * Map for billing address import/export
  397. *
  398. * @var array
  399. */
  400. protected $_billingAddressMap = [
  401. 'BUSINESS' => 'company',
  402. 'NOTETEXT' => 'customer_notes',
  403. 'EMAIL' => 'email',
  404. 'FIRSTNAME' => 'firstname',
  405. 'LASTNAME' => 'lastname',
  406. 'MIDDLENAME' => 'middlename',
  407. 'SALUTATION' => 'prefix',
  408. 'SUFFIX' => 'suffix',
  409. 'COUNTRYCODE' => 'country_id', // iso-3166 two-character code
  410. 'STATE' => 'region',
  411. 'CITY' => 'city',
  412. 'STREET' => 'street',
  413. 'STREET2' => 'street2',
  414. 'ZIP' => 'postcode',
  415. 'PHONENUM' => 'telephone',
  416. ];
  417. /**
  418. * Map for billing address to do request (not response)
  419. * Merging with $_billingAddressMap
  420. *
  421. * @var array
  422. */
  423. protected $_billingAddressMapRequest = [];
  424. /**
  425. * Map for shipping address import/export (extends billing address mapper)
  426. * @var array
  427. */
  428. protected $_shippingAddressMap = [
  429. 'SHIPTOCOUNTRYCODE' => 'country_id',
  430. 'SHIPTOSTATE' => 'region',
  431. 'SHIPTOCITY' => 'city',
  432. 'SHIPTOSTREET' => 'street',
  433. 'SHIPTOSTREET2' => 'street2',
  434. 'SHIPTOZIP' => 'postcode',
  435. 'SHIPTOPHONENUM' => 'telephone',
  436. // 'SHIPTONAME' will be treated manually in address import/export methods
  437. ];
  438. /**
  439. * Map for callback request
  440. * @var array
  441. */
  442. protected $_callbackRequestMap = [
  443. 'SHIPTOCOUNTRY' => 'country_id',
  444. 'SHIPTOSTATE' => 'region',
  445. 'SHIPTOCITY' => 'city',
  446. 'SHIPTOSTREET' => 'street',
  447. 'SHIPTOSTREET2' => 'street2',
  448. 'SHIPTOZIP' => 'postcode',
  449. ];
  450. /**
  451. * Payment information response specifically to be collected after some requests
  452. * @var string[]
  453. */
  454. protected $_paymentInformationResponse = [
  455. 'PAYERID',
  456. 'PAYERSTATUS',
  457. 'CORRELATIONID',
  458. 'ADDRESSID',
  459. 'ADDRESSSTATUS',
  460. 'PAYMENTSTATUS',
  461. 'PENDINGREASON',
  462. 'PROTECTIONELIGIBILITY',
  463. 'EMAIL',
  464. 'SHIPPINGOPTIONNAME',
  465. 'TAXID',
  466. 'TAXIDTYPE',
  467. ];
  468. /**
  469. * Line items export mapping settings
  470. * @var array
  471. */
  472. protected $_lineItemTotalExportMap = [
  473. Cart::AMOUNT_SUBTOTAL => 'ITEMAMT',
  474. Cart::AMOUNT_TAX => 'TAXAMT',
  475. Cart::AMOUNT_SHIPPING => 'SHIPPINGAMT',
  476. ];
  477. /**
  478. * Line items export mapping settings
  479. * @var array
  480. */
  481. protected $_lineItemExportItemsFormat = [
  482. 'id' => 'L_NUMBER%d',
  483. 'name' => 'L_NAME%d',
  484. 'qty' => 'L_QTY%d',
  485. 'amount' => 'L_AMT%d',
  486. ];
  487. /**
  488. * Shipping options export to request mapping settings
  489. * @var array
  490. */
  491. protected $_shippingOptionsExportItemsFormat = [
  492. 'is_default' => 'L_SHIPPINGOPTIONISDEFAULT%d',
  493. 'amount' => 'L_SHIPPINGOPTIONAMOUNT%d',
  494. 'code' => 'L_SHIPPINGOPTIONNAME%d',
  495. 'name' => 'L_SHIPPINGOPTIONLABEL%d',
  496. 'tax_amount' => 'L_TAXAMT%d',
  497. ];
  498. /**
  499. * init Billing Agreement request map
  500. *
  501. * @var string[]
  502. */
  503. protected $_customerBillingAgreementRequest = ['RETURNURL', 'CANCELURL', 'BILLINGTYPE'];
  504. /**
  505. * init Billing Agreement response map
  506. *
  507. * @var string[]
  508. */
  509. protected $_customerBillingAgreementResponse = ['TOKEN'];
  510. /**
  511. * Billing Agreement details request map
  512. *
  513. * @var string[]
  514. */
  515. protected $_billingAgreementCustomerDetailsRequest = ['TOKEN'];
  516. /**
  517. * Billing Agreement details response map
  518. *
  519. * @var string[]
  520. */
  521. protected $_billingAgreementCustomerDetailsResponse = [
  522. 'EMAIL',
  523. 'PAYERID',
  524. 'PAYERSTATUS',
  525. 'SHIPTOCOUNTRYCODE',
  526. 'PAYERBUSINESS',
  527. ];
  528. /**
  529. * Create Billing Agreement request map
  530. *
  531. * @var string[]
  532. */
  533. protected $_createBillingAgreementRequest = ['TOKEN'];
  534. /**
  535. * Create Billing Agreement response map
  536. *
  537. * @var string[]
  538. */
  539. protected $_createBillingAgreementResponse = ['BILLINGAGREEMENTID'];
  540. /**
  541. * Update Billing Agreement request map
  542. *
  543. * @var string[]
  544. */
  545. protected $_updateBillingAgreementRequest = [
  546. 'REFERENCEID',
  547. 'BILLINGAGREEMENTDESCRIPTION',
  548. 'BILLINGAGREEMENTSTATUS',
  549. 'BILLINGAGREEMENTCUSTOM',
  550. ];
  551. /**
  552. * Update Billing Agreement response map
  553. *
  554. * @var string[]
  555. */
  556. protected $_updateBillingAgreementResponse = [
  557. 'REFERENCEID',
  558. 'BILLINGAGREEMENTDESCRIPTION',
  559. 'BILLINGAGREEMENTSTATUS',
  560. 'BILLINGAGREEMENTCUSTOM',
  561. ];
  562. /**
  563. * Do Reference Transaction request map
  564. *
  565. * @var string[]
  566. */
  567. protected $_doReferenceTransactionRequest = [
  568. 'REFERENCEID',
  569. 'PAYMENTACTION',
  570. 'AMT',
  571. 'ITEMAMT',
  572. 'SHIPPINGAMT',
  573. 'TAXAMT',
  574. 'INVNUM',
  575. 'NOTIFYURL',
  576. 'CURRENCYCODE',
  577. ];
  578. /**
  579. * Do Reference Transaction response map
  580. *
  581. * @var string[]
  582. */
  583. protected $_doReferenceTransactionResponse = ['BILLINGAGREEMENTID', 'TRANSACTIONID'];
  584. /**
  585. * Fields that should be replaced in debug with '***'
  586. *
  587. * @var string[]
  588. */
  589. protected $_debugReplacePrivateDataKeys = [
  590. 'ACCT',
  591. 'EXPDATE',
  592. 'CVV2',
  593. 'CARDISSUE',
  594. 'CARDSTART',
  595. 'CREDITCARDTYPE',
  596. 'USER',
  597. 'PWD',
  598. 'SIGNATURE',
  599. ];
  600. /**
  601. * Map of credit card types supported by this API
  602. *
  603. * @var array
  604. */
  605. protected $_supportedCcTypes = [
  606. 'VI' => 'Visa',
  607. 'MC' => 'MasterCard',
  608. 'DI' => 'Discover',
  609. 'AE' => 'Amex',
  610. 'SM' => 'Maestro',
  611. 'SO' => 'Solo',
  612. ];
  613. /**
  614. * Required fields in the response
  615. *
  616. * @var array
  617. */
  618. protected $_requiredResponseParams = [self::DO_DIRECT_PAYMENT => ['ACK', 'CORRELATIONID', 'AMT']];
  619. /**
  620. * Warning codes recollected after each API call
  621. *
  622. * @var array
  623. */
  624. protected $_callWarnings = [];
  625. /**
  626. * Error codes recollected after each API call
  627. *
  628. * @var array
  629. */
  630. protected $_callErrors = [];
  631. /**
  632. * Whether to return raw response information after each call
  633. *
  634. * @var bool
  635. */
  636. protected $_rawResponseNeeded = false;
  637. /**
  638. * @var \Magento\Directory\Model\CountryFactory
  639. */
  640. protected $_countryFactory;
  641. /**
  642. * @var \Magento\Paypal\Model\Api\ProcessableExceptionFactory
  643. */
  644. protected $_processableExceptionFactory;
  645. /**
  646. * @var \Magento\Framework\Exception\LocalizedExceptionFactory
  647. */
  648. protected $_frameworkExceptionFactory;
  649. /**
  650. * @var \Magento\Framework\HTTP\Adapter\CurlFactory
  651. */
  652. protected $_curlFactory;
  653. /**
  654. * API call HTTP headers
  655. *
  656. * @var array
  657. */
  658. protected $_headers = [];
  659. /**
  660. * @param \Magento\Customer\Helper\Address $customerAddress
  661. * @param \Psr\Log\LoggerInterface $logger
  662. * @param Logger $customLogger
  663. * @param \Magento\Framework\Locale\ResolverInterface $localeResolver
  664. * @param \Magento\Directory\Model\RegionFactory $regionFactory
  665. * @param \Magento\Directory\Model\CountryFactory $countryFactory
  666. * @param ProcessableExceptionFactory $processableExceptionFactory
  667. * @param \Magento\Framework\Exception\LocalizedExceptionFactory $frameworkExceptionFactory
  668. * @param \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory
  669. * @param array $data
  670. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  671. */
  672. public function __construct(
  673. \Magento\Customer\Helper\Address $customerAddress,
  674. \Psr\Log\LoggerInterface $logger,
  675. Logger $customLogger,
  676. \Magento\Framework\Locale\ResolverInterface $localeResolver,
  677. \Magento\Directory\Model\RegionFactory $regionFactory,
  678. \Magento\Directory\Model\CountryFactory $countryFactory,
  679. \Magento\Paypal\Model\Api\ProcessableExceptionFactory $processableExceptionFactory,
  680. \Magento\Framework\Exception\LocalizedExceptionFactory $frameworkExceptionFactory,
  681. \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory,
  682. array $data = []
  683. ) {
  684. parent::__construct($customerAddress, $logger, $customLogger, $localeResolver, $regionFactory, $data);
  685. $this->_countryFactory = $countryFactory;
  686. $this->_processableExceptionFactory = $processableExceptionFactory;
  687. $this->_frameworkExceptionFactory = $frameworkExceptionFactory;
  688. $this->_curlFactory = $curlFactory;
  689. }
  690. /**
  691. * API endpoint getter
  692. *
  693. * @return string
  694. */
  695. public function getApiEndpoint()
  696. {
  697. $url = $this->getUseCertAuthentication() ? 'https://api%s.paypal.com/nvp' : 'https://api-3t%s.paypal.com/nvp';
  698. return sprintf($url, $this->_config->getValue('sandboxFlag') ? '.sandbox' : '');
  699. }
  700. /**
  701. * Return Paypal Api version
  702. *
  703. * @return string
  704. */
  705. public function getVersion()
  706. {
  707. return '72.0';
  708. }
  709. /**
  710. * Retrieve billing agreement type
  711. *
  712. * @return string
  713. */
  714. public function getBillingAgreementType()
  715. {
  716. return 'MerchantInitiatedBilling';
  717. }
  718. /**
  719. * SetExpressCheckout call
  720. *
  721. * TODO: put together style and giropay settings
  722. *
  723. * @return void
  724. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_SetExpressCheckout
  725. */
  726. public function callSetExpressCheckout()
  727. {
  728. $this->_prepareExpressCheckoutCallRequest($this->_setExpressCheckoutRequest);
  729. $request = $this->_exportToRequest($this->_setExpressCheckoutRequest);
  730. $this->_exportLineItems($request);
  731. // import/suppress shipping address, if any
  732. $options = $this->getShippingOptions();
  733. if ($this->getAddress()) {
  734. $request = $this->_importAddresses($request);
  735. $request['ADDROVERRIDE'] = 1;
  736. } elseif ($options && count($options) <= 10) {
  737. // doesn't support more than 10 shipping options
  738. $request['CALLBACK'] = $this->getShippingOptionsCallbackUrl();
  739. $request['CALLBACKTIMEOUT'] = 6;
  740. // max value
  741. $request['MAXAMT'] = $request['AMT'] + 999.00;
  742. // it is impossible to calculate max amount
  743. $this->_exportShippingOptions($request);
  744. }
  745. $response = $this->call(self::SET_EXPRESS_CHECKOUT, $request);
  746. $this->_importFromResponse($this->_setExpressCheckoutResponse, $response);
  747. }
  748. /**
  749. * GetExpressCheckoutDetails call
  750. *
  751. * @return void
  752. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_GetExpressCheckoutDetails
  753. */
  754. public function callGetExpressCheckoutDetails()
  755. {
  756. $this->_prepareExpressCheckoutCallRequest($this->_getExpressCheckoutDetailsRequest);
  757. $request = $this->_exportToRequest($this->_getExpressCheckoutDetailsRequest);
  758. $response = $this->call(self::GET_EXPRESS_CHECKOUT_DETAILS, $request);
  759. $this->_importFromResponse($this->_paymentInformationResponse, $response);
  760. $this->_exportAddresses($response);
  761. }
  762. /**
  763. * DoExpressCheckout call
  764. *
  765. * @return void
  766. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_DoExpressCheckoutPayment
  767. */
  768. public function callDoExpressCheckoutPayment()
  769. {
  770. $this->_prepareExpressCheckoutCallRequest($this->_doExpressCheckoutPaymentRequest);
  771. $request = $this->_exportToRequest($this->_doExpressCheckoutPaymentRequest);
  772. $this->_exportLineItems($request);
  773. if ($this->getAddress()) {
  774. $request = $this->_importAddresses($request);
  775. $request['ADDROVERRIDE'] = 1;
  776. }
  777. $response = $this->call(self::DO_EXPRESS_CHECKOUT_PAYMENT, $request);
  778. $this->_importFromResponse($this->_paymentInformationResponse, $response);
  779. $this->_importFromResponse($this->_doExpressCheckoutPaymentResponse, $response);
  780. $this->_importFromResponse($this->_createBillingAgreementResponse, $response);
  781. }
  782. /**
  783. * Process a credit card payment
  784. *
  785. * @return void
  786. */
  787. public function callDoDirectPayment()
  788. {
  789. $request = $this->_exportToRequest($this->_doDirectPaymentRequest);
  790. $this->_exportLineItems($request);
  791. if ($this->getAddress()) {
  792. $request = $this->_importAddresses($request);
  793. }
  794. $response = $this->call(self::DO_DIRECT_PAYMENT, $request);
  795. $this->_importFromResponse($this->_doDirectPaymentResponse, $response);
  796. }
  797. /**
  798. * Do Reference Transaction call
  799. *
  800. * @return void
  801. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_DoReferenceTransaction
  802. */
  803. public function callDoReferenceTransaction()
  804. {
  805. $request = $this->_exportToRequest($this->_doReferenceTransactionRequest);
  806. $this->_exportLineItems($request);
  807. $response = $this->call('DoReferenceTransaction', $request);
  808. $this->_importFromResponse($this->_doReferenceTransactionResponse, $response);
  809. }
  810. /**
  811. * Check whether the last call was returned with fraud warning
  812. *
  813. * @return bool
  814. * @SuppressWarnings(PHPMD.BooleanGetMethodName)
  815. */
  816. public function getIsFraudDetected()
  817. {
  818. return in_array(11610, $this->_callWarnings);
  819. }
  820. /**
  821. * Made additional request to PayPal to get authorization id
  822. *
  823. * @return void
  824. */
  825. public function callDoReauthorization()
  826. {
  827. $request = $this->_exportToRequest($this->_doReauthorizationRequest);
  828. $response = $this->call('DoReauthorization', $request);
  829. $this->_importFromResponse($this->_doReauthorizationResponse, $response);
  830. }
  831. /**
  832. * DoCapture call
  833. *
  834. * @return void
  835. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_DoCapture
  836. */
  837. public function callDoCapture()
  838. {
  839. $this->setCompleteType($this->_getCaptureCompleteType());
  840. $request = $this->_exportToRequest($this->_doCaptureRequest);
  841. $response = $this->call(self::DO_CAPTURE, $request);
  842. $this->_importFromResponse($this->_paymentInformationResponse, $response);
  843. $this->_importFromResponse($this->_doCaptureResponse, $response);
  844. }
  845. /**
  846. * DoAuthorization call
  847. *
  848. * @return $this
  849. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_DoAuthorization
  850. */
  851. public function callDoAuthorization()
  852. {
  853. $request = $this->_exportToRequest($this->_doAuthorizationRequest);
  854. $response = $this->call(self::DO_AUTHORIZATION, $request);
  855. $this->_importFromResponse($this->_paymentInformationResponse, $response);
  856. $this->_importFromResponse($this->_doAuthorizationResponse, $response);
  857. return $this;
  858. }
  859. /**
  860. * DoVoid call
  861. *
  862. * @return void
  863. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_DoVoid
  864. */
  865. public function callDoVoid()
  866. {
  867. $request = $this->_exportToRequest($this->_doVoidRequest);
  868. $this->call(self::DO_VOID, $request);
  869. }
  870. /**
  871. * GetTransactionDetails
  872. *
  873. * @return void
  874. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_GetTransactionDetails
  875. */
  876. public function callGetTransactionDetails()
  877. {
  878. $request = $this->_exportToRequest($this->_getTransactionDetailsRequest);
  879. $response = $this->call('GetTransactionDetails', $request);
  880. $this->_importFromResponse($this->_getTransactionDetailsResponse, $response);
  881. }
  882. /**
  883. * RefundTransaction call
  884. *
  885. * @return void
  886. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_RefundTransaction
  887. */
  888. public function callRefundTransaction()
  889. {
  890. $request = $this->_exportToRequest($this->_refundTransactionRequest);
  891. if ($this->getRefundType() === \Magento\Paypal\Model\Config::REFUND_TYPE_PARTIAL) {
  892. $request['AMT'] = $this->formatPrice($this->getAmount());
  893. }
  894. $response = $this->call(self::REFUND_TRANSACTION, $request);
  895. $this->_importFromResponse($this->_refundTransactionResponse, $response);
  896. }
  897. /**
  898. * ManagePendingTransactionStatus
  899. *
  900. * @return void
  901. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_ManagePendingTransactionStatus
  902. */
  903. public function callManagePendingTransactionStatus()
  904. {
  905. $request = $this->_exportToRequest($this->_managePendingTransactionStatusRequest);
  906. if (isset($request['ACTION'])) {
  907. $request['ACTION'] = $this->_filterPaymentReviewAction($request['ACTION']);
  908. }
  909. $response = $this->call('ManagePendingTransactionStatus', $request);
  910. $this->_importFromResponse($this->_managePendingTransactionStatusResponse, $response);
  911. }
  912. /**
  913. * GetPalDetails call
  914. *
  915. * @return void
  916. * @link https://www.x.com/docs/DOC-1300
  917. * @link https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECButtonIntegration
  918. */
  919. public function callGetPalDetails()
  920. {
  921. $response = $this->call('getPalDetails', []);
  922. $this->_importFromResponse($this->_getPalDetailsResponse, $response);
  923. }
  924. /**
  925. * Set Customer BillingAgreement call
  926. *
  927. * @return void
  928. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_SetCustomerBillingAgreement
  929. */
  930. public function callSetCustomerBillingAgreement()
  931. {
  932. $request = $this->_exportToRequest($this->_customerBillingAgreementRequest);
  933. $response = $this->call('SetCustomerBillingAgreement', $request);
  934. $this->_importFromResponse($this->_customerBillingAgreementResponse, $response);
  935. }
  936. /**
  937. * Get Billing Agreement Customer Details call
  938. *
  939. * @return void
  940. * @link https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_GetBillingAgreementCustomerDetails
  941. */
  942. public function callGetBillingAgreementCustomerDetails()
  943. {
  944. $request = $this->_exportToRequest($this->_billingAgreementCustomerDetailsRequest);
  945. $response = $this->call('GetBillingAgreementCustomerDetails', $request);
  946. $this->_importFromResponse($this->_billingAgreementCustomerDetailsResponse, $response);
  947. }
  948. /**
  949. * Create Billing Agreement call
  950. *
  951. * @return void
  952. */
  953. public function callCreateBillingAgreement()
  954. {
  955. $request = $this->_exportToRequest($this->_createBillingAgreementRequest);
  956. $response = $this->call('CreateBillingAgreement', $request);
  957. $this->_importFromResponse($this->_createBillingAgreementResponse, $response);
  958. }
  959. /**
  960. * Billing Agreement Update call
  961. *
  962. * @return void
  963. */
  964. public function callUpdateBillingAgreement()
  965. {
  966. $request = $this->_exportToRequest($this->_updateBillingAgreementRequest);
  967. try {
  968. $response = $this->call('BillAgreementUpdate', $request);
  969. } catch (\Magento\Framework\Exception\LocalizedException $e) {
  970. if (in_array(10201, $this->_callErrors)) {
  971. $this->setIsBillingAgreementAlreadyCancelled(true);
  972. }
  973. throw $e;
  974. }
  975. $this->_importFromResponse($this->_updateBillingAgreementResponse, $response);
  976. }
  977. /**
  978. * Import callback request array into $this public data
  979. *
  980. * @param array $request
  981. * @return \Magento\Framework\DataObject
  982. */
  983. public function prepareShippingOptionsCallbackAddress(array $request)
  984. {
  985. $address = new \Magento\Framework\DataObject();
  986. \Magento\Framework\DataObject\Mapper::accumulateByMap($request, $address, $this->_callbackRequestMap);
  987. $address->setExportedKeys(array_values($this->_callbackRequestMap));
  988. $this->_applyStreetAndRegionWorkarounds($address);
  989. return $address;
  990. }
  991. /**
  992. * Prepare response for shipping options callback
  993. *
  994. * @return string
  995. */
  996. public function formatShippingOptionsCallback()
  997. {
  998. $response = [];
  999. if (!$this->_exportShippingOptions($response)) {
  1000. $response['NO_SHIPPING_OPTION_DETAILS'] = '1';
  1001. }
  1002. $response = $this->_addMethodToRequest(self::CALLBACK_RESPONSE, $response);
  1003. return $this->_buildQuery($response);
  1004. }
  1005. /**
  1006. * Add method to request array
  1007. *
  1008. * @param string $methodName
  1009. * @param array $request
  1010. * @return array
  1011. */
  1012. protected function _addMethodToRequest($methodName, $request)
  1013. {
  1014. $request['METHOD'] = $methodName;
  1015. return $request;
  1016. }
  1017. /**
  1018. * Additional response processing.
  1019. * Hack to cut off length from API type response params.
  1020. *
  1021. * @param array $response
  1022. * @return array
  1023. */
  1024. protected function _postProcessResponse($response)
  1025. {
  1026. foreach ($response as $key => $value) {
  1027. $pos = strpos($key, '[');
  1028. if ($pos === false) {
  1029. continue;
  1030. }
  1031. unset($response[$key]);
  1032. if ($pos !== 0) {
  1033. $modifiedKey = substr($key, 0, $pos);
  1034. $response[$modifiedKey] = $value;
  1035. }
  1036. }
  1037. return $response;
  1038. }
  1039. /**
  1040. * Do the API call
  1041. *
  1042. * @param string $methodName
  1043. * @param array $request
  1044. * @return array
  1045. * @throws \Magento\Framework\Exception\LocalizedException|\Exception
  1046. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1047. */
  1048. public function call($methodName, array $request)
  1049. {
  1050. $request = $this->_addMethodToRequest($methodName, $request);
  1051. $eachCallRequest = $this->_prepareEachCallRequest($methodName);
  1052. if ($this->getUseCertAuthentication()) {
  1053. $key = array_search('SIGNATURE', $eachCallRequest);
  1054. if ($key) {
  1055. unset($eachCallRequest[$key]);
  1056. }
  1057. }
  1058. $request = $this->_exportToRequest($eachCallRequest, $request);
  1059. $debugData = ['url' => $this->getApiEndpoint(), $methodName => $request];
  1060. try {
  1061. $http = $this->_curlFactory->create();
  1062. $config = ['timeout' => 60, 'verifypeer' => $this->_config->getValue('verifyPeer')];
  1063. if ($this->getUseProxy()) {
  1064. $config['proxy'] = $this->getProxyHost() . ':' . $this->getProxyPort();
  1065. }
  1066. if ($this->getUseCertAuthentication()) {
  1067. $config['ssl_cert'] = $this->getApiCertificate();
  1068. }
  1069. $http->setConfig($config);
  1070. $http->write(
  1071. \Zend_Http_Client::POST,
  1072. $this->getApiEndpoint(),
  1073. '1.1',
  1074. $this->_headers,
  1075. $this->_buildQuery($request)
  1076. );
  1077. $response = $http->read();
  1078. } catch (\Exception $e) {
  1079. $debugData['http_error'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  1080. $this->_debug($debugData);
  1081. throw $e;
  1082. }
  1083. $response = preg_split('/^\r?$/m', $response, 2);
  1084. $response = trim($response[1]);
  1085. $response = $this->_deformatNVP($response);
  1086. $debugData['response'] = $response;
  1087. $this->_debug($debugData);
  1088. $response = $this->_postProcessResponse($response);
  1089. // handle transport error
  1090. if ($http->getErrno()) {
  1091. $this->_logger->critical(
  1092. new \Exception(
  1093. sprintf('PayPal NVP CURL connection error #%s: %s', $http->getErrno(), $http->getError())
  1094. )
  1095. );
  1096. $http->close();
  1097. throw new \Magento\Framework\Exception\LocalizedException(
  1098. __('Payment Gateway is unreachable at the moment. Please use another payment option.')
  1099. );
  1100. }
  1101. // cUrl resource must be closed after checking it for errors
  1102. $http->close();
  1103. if (!$this->_validateResponse($methodName, $response)) {
  1104. $this->_logger->critical(new \Exception(__('PayPal response hasn\'t required fields.')));
  1105. throw new \Magento\Framework\Exception\LocalizedException(
  1106. __('Something went wrong while processing your order.')
  1107. );
  1108. }
  1109. $this->_callErrors = [];
  1110. if ($this->_isCallSuccessful($response)) {
  1111. if ($this->_rawResponseNeeded) {
  1112. $this->setRawSuccessResponseData($response);
  1113. }
  1114. return $response;
  1115. }
  1116. $this->_handleCallErrors($response);
  1117. return $response;
  1118. }
  1119. /**
  1120. * Setter for 'raw response needed' flag
  1121. *
  1122. * @param bool $flag
  1123. * @return $this
  1124. */
  1125. public function setRawResponseNeeded($flag)
  1126. {
  1127. $this->_rawResponseNeeded = $flag;
  1128. return $this;
  1129. }
  1130. /**
  1131. * Handle logical errors
  1132. *
  1133. * @param array $response
  1134. * @return void
  1135. * @throws \Magento\Paypal\Model\Api\ProcessableException|\Magento\Framework\Exception\LocalizedException
  1136. * @SuppressWarnings(PHPMD.NPathComplexity)
  1137. */
  1138. protected function _handleCallErrors($response)
  1139. {
  1140. $errors = $this->_extractErrorsFromResponse($response);
  1141. if (empty($errors)) {
  1142. return;
  1143. }
  1144. $errorMessages = [];
  1145. foreach ($errors as $error) {
  1146. $errorMessages[] = $error['message'];
  1147. $this->_callErrors[] = $error['code'];
  1148. }
  1149. $errorMessages = implode(' ', $errorMessages);
  1150. $exceptionLogMessage = sprintf(
  1151. 'PayPal NVP gateway errors: %s Correlation ID: %s. Version: %s.',
  1152. $errorMessages,
  1153. isset($response['CORRELATIONID']) ? $response['CORRELATIONID'] : '',
  1154. isset($response['VERSION']) ? $response['VERSION'] : ''
  1155. );
  1156. $this->_logger->critical($exceptionLogMessage);
  1157. /**
  1158. * The response code 10415 'Transaction has already been completed for this token'
  1159. * must not fails place order. The old Paypal interface does not lock 'Send' button
  1160. * it may result to re-send data.
  1161. */
  1162. if (in_array((string)ProcessableException::API_TRANSACTION_HAS_BEEN_COMPLETED, $this->_callErrors)) {
  1163. return;
  1164. }
  1165. $exceptionPhrase = __('PayPal gateway has rejected request. %1', $errorMessages);
  1166. /** @var \Magento\Framework\Exception\LocalizedException $exception */
  1167. $firstError = $errors[0]['code'];
  1168. $exception = $this->_isProcessableError($firstError)
  1169. ? $this->_processableExceptionFactory->create(
  1170. ['phrase' => $exceptionPhrase, 'code' => $firstError]
  1171. )
  1172. : $this->_frameworkExceptionFactory->create(
  1173. ['phrase' => $exceptionPhrase]
  1174. );
  1175. throw $exception;
  1176. }
  1177. /**
  1178. * Format error message from error code, short error message and long error message
  1179. *
  1180. * @param string $errorCode
  1181. * @param string $shortErrorMessage
  1182. * @param string $longErrorMessage
  1183. * @return string
  1184. */
  1185. protected function _formatErrorMessage($errorCode, $shortErrorMessage, $longErrorMessage)
  1186. {
  1187. $longErrorMessage = preg_replace('/\.$/', '', $longErrorMessage);
  1188. $shortErrorMessage = preg_replace('/\.$/', '', $shortErrorMessage);
  1189. return $longErrorMessage ? sprintf('%s (#%s: %s).', $longErrorMessage, $errorCode, $shortErrorMessage)
  1190. : sprintf('#%s: %s.', $errorCode, $shortErrorMessage);
  1191. }
  1192. /**
  1193. * Check whether PayPal error can be processed
  1194. *
  1195. * @param int $errorCode
  1196. * @return bool
  1197. */
  1198. protected function _isProcessableError($errorCode)
  1199. {
  1200. $processableErrorsList = $this->getProcessableErrors();
  1201. if (!$processableErrorsList || !is_array($processableErrorsList)) {
  1202. return false;
  1203. }
  1204. return in_array($errorCode, $processableErrorsList);
  1205. }
  1206. /**
  1207. * Extract errors from PayPal's response and return them in array
  1208. *
  1209. * @param array $response
  1210. * @return array
  1211. */
  1212. protected function _extractErrorsFromResponse($response)
  1213. {
  1214. $errors = [];
  1215. for ($i = 0; isset($response["L_ERRORCODE{$i}"]); $i++) {
  1216. $errorCode = $response["L_ERRORCODE{$i}"];
  1217. $errorMessage = $this->_formatErrorMessage(
  1218. $errorCode,
  1219. $response["L_SHORTMESSAGE{$i}"],
  1220. isset($response["L_LONGMESSAGE{$i}"]) ? $response["L_LONGMESSAGE{$i}"] : null
  1221. );
  1222. $errors[] = [
  1223. 'code' => $errorCode,
  1224. 'message' => $errorMessage,
  1225. ];
  1226. }
  1227. return $errors;
  1228. }
  1229. /**
  1230. * Catch success calls and collect warnings
  1231. *
  1232. * @param array $response
  1233. * @return bool success flag
  1234. */
  1235. protected function _isCallSuccessful($response)
  1236. {
  1237. if (!isset($response['ACK'])) {
  1238. return false;
  1239. }
  1240. $ack = strtoupper($response['ACK']);
  1241. $this->_callWarnings = [];
  1242. if ($ack == 'SUCCESS' || $ack == 'SUCCESSWITHWARNING') {
  1243. // collect warnings
  1244. if ($ack == 'SUCCESSWITHWARNING') {
  1245. for ($i = 0; isset($response["L_ERRORCODE{$i}"]); $i++) {
  1246. $this->_callWarnings[] = $response["L_ERRORCODE{$i}"];
  1247. }
  1248. }
  1249. return true;
  1250. }
  1251. return false;
  1252. }
  1253. /**
  1254. * Validate response array.
  1255. *
  1256. * @param string $method
  1257. * @param array $response
  1258. * @return bool
  1259. */
  1260. protected function _validateResponse($method, $response)
  1261. {
  1262. if (isset($this->_requiredResponseParams[$method])) {
  1263. foreach ($this->_requiredResponseParams[$method] as $param) {
  1264. if (!isset($response[$param])) {
  1265. return false;
  1266. }
  1267. }
  1268. }
  1269. return true;
  1270. }
  1271. /**
  1272. * Parse an NVP response string into an associative array
  1273. * @param string $nvpstr
  1274. * @return array
  1275. */
  1276. protected function _deformatNVP($nvpstr)
  1277. {
  1278. $intial = 0;
  1279. $nvpArray = [];
  1280. $nvpstr = strpos($nvpstr, "\r\n\r\n") !== false ? substr($nvpstr, strpos($nvpstr, "\r\n\r\n") + 4) : $nvpstr;
  1281. while (strlen($nvpstr)) {
  1282. //position of Key
  1283. $keypos = strpos($nvpstr, '=');
  1284. //position of value
  1285. $valuepos = strpos($nvpstr, '&') ? strpos($nvpstr, '&') : strlen($nvpstr);
  1286. /*getting the Key and Value values and storing in a Associative Array*/
  1287. $keyval = substr($nvpstr, $intial, $keypos);
  1288. $valval = substr($nvpstr, $keypos + 1, $valuepos - $keypos - 1);
  1289. //decoding the response
  1290. $nvpArray[urldecode($keyval)] = urldecode($valval);
  1291. $nvpstr = substr($nvpstr, $valuepos + 1, strlen($nvpstr));
  1292. }
  1293. return $nvpArray;
  1294. }
  1295. /**
  1296. * NVP doesn't support passing discount total as a separate amount - add it as a line item
  1297. *
  1298. * @param array $request
  1299. * @param int $i
  1300. * @return true|null
  1301. */
  1302. protected function _exportLineItems(array &$request, $i = 0)
  1303. {
  1304. if (!$this->_cart) {
  1305. return;
  1306. }
  1307. $this->_cart->setTransferDiscountAsItem();
  1308. return parent::_exportLineItems($request, $i);
  1309. }
  1310. /**
  1311. * Create billing and shipping addresses basing on response data
  1312. *
  1313. * @param array $data
  1314. * @return void
  1315. * @deprecated 100.2.4 typo in method name
  1316. * @see _exportAddresses
  1317. */
  1318. protected function _exportAddressses($data)
  1319. {
  1320. $this->_exportAddresses($data);
  1321. }
  1322. /**
  1323. * Create billing and shipping addresses basing on response data
  1324. *
  1325. * @param array $data
  1326. * @return void
  1327. */
  1328. protected function _exportAddresses($data)
  1329. {
  1330. $address = new \Magento\Framework\DataObject();
  1331. \Magento\Framework\DataObject\Mapper::accumulateByMap($data, $address, $this->_billingAddressMap);
  1332. $address->setExportedKeys(array_values($this->_billingAddressMap));
  1333. $this->_applyStreetAndRegionWorkarounds($address);
  1334. $this->setExportedBillingAddress($address);
  1335. // assume there is shipping address if there is at least one field specific to shipping
  1336. if (isset($data['SHIPTONAME'])) {
  1337. $shippingAddress = clone $address;
  1338. \Magento\Framework\DataObject\Mapper::accumulateByMap($data, $shippingAddress, $this->_shippingAddressMap);
  1339. $this->_applyStreetAndRegionWorkarounds($shippingAddress);
  1340. // PayPal doesn't provide detailed shipping name fields, so the name will be overwritten
  1341. $shippingAddress->addData(['firstname' => $data['SHIPTONAME']]);
  1342. $this->setExportedShippingAddress($shippingAddress);
  1343. }
  1344. }
  1345. /**
  1346. * Adopt specified address object to be compatible with Magento
  1347. *
  1348. * @param \Magento\Framework\DataObject $address
  1349. * @return void
  1350. */
  1351. protected function _applyStreetAndRegionWorkarounds(\Magento\Framework\DataObject $address)
  1352. {
  1353. // merge street addresses into 1
  1354. if ($address->getData('street2') !== null) {
  1355. $address->setStreet(implode("\n", [$address->getData('street'), $address->getData('street2')]));
  1356. $address->unsetData('street2');
  1357. }
  1358. // attempt to fetch region_id from directory
  1359. if ($address->getCountryId() && $address->getRegion()) {
  1360. $regions = $this->_countryFactory->create()->loadByCode(
  1361. $address->getCountryId()
  1362. )->getRegionCollection()->addRegionCodeOrNameFilter(
  1363. $address->getRegion()
  1364. )->setPageSize(
  1365. 1
  1366. );
  1367. foreach ($regions as $region) {
  1368. $address->setRegionId($region->getId());
  1369. $address->setExportedKeys(array_merge($address->getExportedKeys(), ['region_id']));
  1370. break;
  1371. }
  1372. }
  1373. }
  1374. /**
  1375. * Prepare request data basing on provided addresses
  1376. *
  1377. * @param array $to
  1378. * @return array
  1379. */
  1380. protected function _importAddresses(array $to)
  1381. {
  1382. $billingAddress = $this->getBillingAddress() ? $this->getBillingAddress() : $this->getAddress();
  1383. $shippingAddress = $this->getAddress();
  1384. $to = \Magento\Framework\DataObject\Mapper::accumulateByMap(
  1385. $billingAddress,
  1386. $to,
  1387. array_merge(array_flip($this->_billingAddressMap), $this->_billingAddressMapRequest)
  1388. );
  1389. $regionCode = $this->_lookupRegionCodeFromAddress($billingAddress);
  1390. if ($regionCode) {
  1391. $to['STATE'] = $regionCode;
  1392. }
  1393. if (!$this->getSuppressShipping()) {
  1394. $to = \Magento\Framework\DataObject\Mapper::accumulateByMap(
  1395. $shippingAddress,
  1396. $to,
  1397. array_flip($this->_shippingAddressMap)
  1398. );
  1399. $regionCode = $this->_lookupRegionCodeFromAddress($shippingAddress);
  1400. if ($regionCode) {
  1401. $to['SHIPTOSTATE'] = $regionCode;
  1402. }
  1403. $this->_importStreetFromAddress($shippingAddress, $to, 'SHIPTOSTREET', 'SHIPTOSTREET2');
  1404. $this->_importStreetFromAddress($billingAddress, $to, 'STREET', 'STREET2');
  1405. $to['SHIPTONAME'] = $shippingAddress->getName();
  1406. }
  1407. return $to;
  1408. }
  1409. /**
  1410. * Filter for credit card type
  1411. *
  1412. * @param string $value
  1413. * @return string
  1414. */
  1415. protected function _filterCcType($value)
  1416. {
  1417. if (isset($this->_supportedCcTypes[$value])) {
  1418. return $this->_supportedCcTypes[$value];
  1419. }
  1420. return '';
  1421. }
  1422. /**
  1423. * Filter for true/false values (converts to boolean)
  1424. *
  1425. * @param mixed $value
  1426. * @return bool|mixed
  1427. */
  1428. protected function _filterToBool($value)
  1429. {
  1430. if ('false' === $value || '0' === $value) {
  1431. return false;
  1432. } elseif ('true' === $value || '1' === $value) {
  1433. return true;
  1434. }
  1435. return $value;
  1436. }
  1437. /**
  1438. * Filter for 'AUTOBILLAMT'
  1439. *
  1440. * @param string $value
  1441. * @return string
  1442. */
  1443. protected function _filterBillFailedLater($value)
  1444. {
  1445. return $value ? 'AddToNextBilling' : 'NoAutoBill';
  1446. }
  1447. /**
  1448. * Filter for 'BILLINGPERIOD' and 'TRIALBILLINGPERIOD'
  1449. *
  1450. * @param string $value
  1451. * @return string
  1452. */
  1453. protected function _filterPeriodUnit($value)
  1454. {
  1455. switch ($value) {
  1456. case 'day':
  1457. return 'Day';
  1458. case 'week':
  1459. return 'Week';
  1460. case 'semi_month':
  1461. return 'SemiMonth';
  1462. case 'month':
  1463. return 'Month';
  1464. case 'year':
  1465. return 'Year';
  1466. default:
  1467. break;
  1468. }
  1469. }
  1470. /**
  1471. * Filter for 'FAILEDINITAMTACTION'
  1472. *
  1473. * @param string $value
  1474. * @return string
  1475. */
  1476. protected function _filterInitialAmountMayFail($value)
  1477. {
  1478. return $value ? 'ContinueOnFailure' : 'CancelOnFailure';
  1479. }
  1480. /**
  1481. * Filter for billing agreement status
  1482. *
  1483. * @param string $value
  1484. * @return string
  1485. */
  1486. protected function _filterBillingAgreementStatus($value)
  1487. {
  1488. switch ($value) {
  1489. case 'canceled':
  1490. return 'Canceled';
  1491. case 'active':
  1492. return 'Active';
  1493. default:
  1494. break;
  1495. }
  1496. }
  1497. /**
  1498. * Convert payment status from NVP format to paypal/info model format
  1499. *
  1500. * @param string $value
  1501. * @return string|null
  1502. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1503. */
  1504. protected function _filterPaymentStatusFromNvpToInfo($value)
  1505. {
  1506. switch ($value) {
  1507. case 'None':
  1508. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_NONE;
  1509. case 'Completed':
  1510. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_COMPLETED;
  1511. case 'Denied':
  1512. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_DENIED;
  1513. case 'Expired':
  1514. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_EXPIRED;
  1515. case 'Failed':
  1516. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_FAILED;
  1517. case 'In-Progress':
  1518. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_INPROGRESS;
  1519. case 'Pending':
  1520. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_PENDING;
  1521. case 'Refunded':
  1522. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_REFUNDED;
  1523. case 'Partially-Refunded':
  1524. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_REFUNDEDPART;
  1525. case 'Reversed':
  1526. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_REVERSED;
  1527. case 'Canceled-Reversal':
  1528. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_UNREVERSED;
  1529. case 'Processed':
  1530. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_PROCESSED;
  1531. case 'Voided':
  1532. return \Magento\Paypal\Model\Info::PAYMENTSTATUS_VOIDED;
  1533. default:
  1534. break;
  1535. }
  1536. }
  1537. /**
  1538. * Convert payment review action to NVP-compatible value
  1539. *
  1540. * @param string $value
  1541. * @return string|null
  1542. */
  1543. protected function _filterPaymentReviewAction($value)
  1544. {
  1545. switch ($value) {
  1546. case \Magento\Paypal\Model\Pro::PAYMENT_REVIEW_ACCEPT:
  1547. return 'Accept';
  1548. case \Magento\Paypal\Model\Pro::PAYMENT_REVIEW_DENY:
  1549. return 'Deny';
  1550. default:
  1551. break;
  1552. }
  1553. }
  1554. /**
  1555. * Return capture type
  1556. *
  1557. * @return string
  1558. */
  1559. protected function _getCaptureCompleteType()
  1560. {
  1561. return $this->getIsCaptureComplete() ? $this->_captureTypeComplete : $this->_captureTypeNotcomplete;
  1562. }
  1563. /**
  1564. * Return each call request without unused fields in case of Express Checkout Unilateral payments
  1565. *
  1566. * @param string $methodName Current method name
  1567. * @return array
  1568. */
  1569. protected function _prepareEachCallRequest($methodName)
  1570. {
  1571. $expressCheckoutMethods = [
  1572. self::SET_EXPRESS_CHECKOUT,
  1573. self::GET_EXPRESS_CHECKOUT_DETAILS,
  1574. self::DO_EXPRESS_CHECKOUT_PAYMENT,
  1575. ];
  1576. if (!in_array($methodName, $expressCheckoutMethods) || !$this->_config->shouldUseUnilateralPayments()) {
  1577. return $this->_eachCallRequest;
  1578. }
  1579. return array_diff($this->_eachCallRequest, ['USER', 'PWD', 'SIGNATURE']);
  1580. }
  1581. /**
  1582. * Check the EC request against unilateral payments mode and remove the SUBJECT if needed
  1583. *
  1584. * @param &array $requestFields
  1585. * @return void
  1586. */
  1587. protected function _prepareExpressCheckoutCallRequest(&$requestFields)
  1588. {
  1589. if (!$this->_config->shouldUseUnilateralPayments()) {
  1590. $key = array_search('SUBJECT', $requestFields);
  1591. if ($key) {
  1592. unset($requestFields[$key]);
  1593. }
  1594. }
  1595. }
  1596. }