1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30: 31: 32: 33:
34:
35: class Mage_Paypal_Model_Payflowpro extends Mage_Payment_Model_Method_Cc
36: {
37: 38: 39:
40: const TRXTYPE_AUTH_ONLY = 'A';
41: const TRXTYPE_SALE = 'S';
42: const TRXTYPE_CREDIT = 'C';
43: const TRXTYPE_DELAYED_CAPTURE = 'D';
44: const TRXTYPE_DELAYED_VOID = 'V';
45: const TRXTYPE_DELAYED_VOICE = 'F';
46: const TRXTYPE_DELAYED_INQUIRY = 'I';
47:
48: 49: 50:
51: const TENDER_CC = 'C';
52:
53: 54: 55:
56: const TRANSACTION_URL = 'https://payflowpro.paypal.com/transaction';
57: const TRANSACTION_URL_TEST_MODE = 'https://pilot-payflowpro.paypal.com/transaction';
58:
59: 60: 61:
62: const RESPONSE_CODE_APPROVED = 0;
63: const RESPONSE_CODE_INVALID_AMOUNT = 4;
64: const RESPONSE_CODE_FRAUDSERVICE_FILTER = 126;
65: const RESPONSE_CODE_DECLINED = 12;
66: const RESPONSE_CODE_DECLINED_BY_FILTER = 125;
67: const RESPONSE_CODE_DECLINED_BY_MERCHANT = 128;
68: const RESPONSE_CODE_CAPTURE_ERROR = 111;
69: const RESPONSE_CODE_VOID_ERROR = 108;
70:
71: 72: 73:
74: protected $_code = Mage_Paypal_Model_Config::METHOD_PAYFLOWPRO;
75:
76: 77: 78:
79: protected $_isGateway = true;
80: protected $_canAuthorize = true;
81: protected $_canCapture = true;
82: protected $_canCapturePartial = false;
83: protected $_canRefund = true;
84: protected $_canVoid = true;
85: protected $_canUseInternal = true;
86: protected $_canUseCheckout = true;
87: protected $_canUseForMultishipping = true;
88: protected $_canSaveCc = false;
89: protected $_isProxy = false;
90: protected $_canFetchTransactionInfo = true;
91:
92: 93: 94:
95: protected $_clientTimeout = 45;
96:
97: 98: 99: 100: 101:
102: protected $_debugReplacePrivateDataKeys = array('user', 'pwd', 'acct', 'expdate', 'cvv2');
103:
104: 105: 106: 107: 108:
109: protected $_centinelFieldMap = array(
110: 'centinel_mpivendor' => 'MPIVENDOR3DS',
111: 'centinel_authstatus' => 'AUTHSTATUS3DS',
112: 'centinel_cavv' => 'CAVV',
113: 'centinel_eci' => 'ECI',
114: 'centinel_xid' => 'XID',
115: );
116:
117: 118: 119: 120: 121: 122:
123: public function isAvailable($quote = null)
124: {
125: $storeId = Mage::app()->getStore($this->getStore())->getId();
126: $config = Mage::getModel('paypal/config')->setStoreId($storeId);
127: if (parent::isAvailable($quote) && $config->isMethodAvailable($this->getCode())) {
128: return true;
129: }
130: return false;
131: }
132:
133: 134: 135: 136: 137: 138:
139: public function getConfigPaymentAction()
140: {
141: switch ($this->getConfigData('payment_action')) {
142: case Mage_Paypal_Model_Config::PAYMENT_ACTION_AUTH:
143: return Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE;
144: case Mage_Paypal_Model_Config::PAYMENT_ACTION_SALE:
145: return Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE;
146: }
147: }
148:
149: 150: 151: 152: 153: 154:
155: public function authorize(Varien_Object $payment, $amount)
156: {
157: $request = $this->_buildPlaceRequest($payment, $amount);
158: $request->setTrxtype(self::TRXTYPE_AUTH_ONLY);
159: $this->_setReferenceTransaction($payment, $request);
160: $response = $this->_postRequest($request);
161: $this->_processErrors($response);
162:
163: switch ($response->getResultCode()){
164: case self::RESPONSE_CODE_APPROVED:
165: $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
166: break;
167: case self::RESPONSE_CODE_FRAUDSERVICE_FILTER:
168: $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
169: $payment->setIsTransactionPending(true);
170: $payment->setIsFraudDetected(true);
171: break;
172: }
173: return $this;
174: }
175:
176: 177: 178: 179: 180: 181:
182: public function capture(Varien_Object $payment, $amount)
183: {
184: if ($payment->getReferenceTransactionId()) {
185: $request = $this->_buildPlaceRequest($payment, $amount);
186: $request->setTrxtype(self::TRXTYPE_SALE);
187: $request->setOrigid($payment->getReferenceTransactionId());
188: } elseif ($payment->getParentTransactionId()) {
189: $request = $this->_buildBasicRequest($payment);
190: $request->setTrxtype(self::TRXTYPE_DELAYED_CAPTURE);
191: $request->setOrigid($payment->getParentTransactionId());
192: } else {
193: $request = $this->_buildPlaceRequest($payment, $amount);
194: $request->setTrxtype(self::TRXTYPE_SALE);
195: }
196:
197: $response = $this->_postRequest($request);
198: $this->_processErrors($response);
199:
200: switch ($response->getResultCode()){
201: case self::RESPONSE_CODE_APPROVED:
202: $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
203: break;
204: case self::RESPONSE_CODE_FRAUDSERVICE_FILTER:
205: $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
206: $payment->setIsTransactionPending(true);
207: $payment->setIsFraudDetected(true);
208: break;
209: }
210: return $this;
211: }
212:
213: 214: 215: 216: 217: 218:
219: public function void(Varien_Object $payment)
220: {
221: $request = $this->_buildBasicRequest($payment);
222: $request->setTrxtype(self::TRXTYPE_DELAYED_VOID);
223: $request->setOrigid($payment->getParentTransactionId());
224: $response = $this->_postRequest($request);
225: $this->_processErrors($response);
226:
227: if ($response->getResultCode() == self::RESPONSE_CODE_APPROVED){
228: $payment->setTransactionId($response->getPnref())
229: ->setIsTransactionClosed(1)
230: ->setShouldCloseParentTransaction(1);
231: }
232:
233: return $this;
234: }
235:
236: 237: 238: 239: 240: 241:
242: public function cancel(Varien_Object $payment)
243: {
244: return $this->void($payment);
245: }
246:
247: 248: 249: 250: 251: 252:
253: public function refund(Varien_Object $payment, $amount)
254: {
255: $request = $this->_buildBasicRequest($payment);
256: $request->setTrxtype(self::TRXTYPE_CREDIT);
257: $request->setOrigid($payment->getParentTransactionId());
258: $request->setAmt(round($amount,2));
259: $response = $this->_postRequest($request);
260: $this->_processErrors($response);
261:
262: if ($response->getResultCode() == self::RESPONSE_CODE_APPROVED){
263: $payment->setTransactionId($response->getPnref())
264: ->setIsTransactionClosed(1);
265: }
266: return $this;
267: }
268:
269: 270: 271: 272: 273: 274: 275:
276: public function fetchTransactionInfo(Mage_Payment_Model_Info $payment, $transactionId)
277: {
278: $request = $this->_buildBasicRequest($payment);
279: $request->setTrxtype(self::TRXTYPE_DELAYED_INQUIRY);
280: $request->setOrigid($transactionId);
281: $response = $this->_postRequest($request);
282:
283: $this->_processErrors($response);
284:
285: if (!$this->_isTransactionUnderReview($response->getOrigresult())) {
286: $payment->setTransactionId($response->getOrigpnref())
287: ->setIsTransactionClosed(0);
288: if ($response->getOrigresult() == self::RESPONSE_CODE_APPROVED) {
289: $payment->setIsTransactionApproved(true);
290: } else if ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_MERCHANT) {
291: $payment->setIsTransactionDenied(true);
292: }
293: }
294:
295: $rawData = $response->getData();
296: return ($rawData) ? $rawData : array();
297: }
298:
299: 300: 301: 302: 303: 304:
305: protected static function _isTransactionUnderReview($status)
306: {
307: if (in_array($status, array(self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_DECLINED_BY_MERCHANT))) {
308: return false;
309: }
310: return true;
311: }
312:
313: 314: 315: 316: 317: 318:
319: protected function _getTransactionUrl($testMode = null)
320: {
321: $testMode = is_null($testMode) ? $this->getConfigData('sandbox_flag') : (bool)$testMode;
322: if ($testMode) {
323: return self::TRANSACTION_URL_TEST_MODE;
324: }
325: return self::TRANSACTION_URL;
326: }
327:
328: 329: 330: 331: 332: 333:
334: protected function _postRequest(Varien_Object $request)
335: {
336: $debugData = array('request' => $request->getData());
337:
338: $client = new Varien_Http_Client();
339: $result = new Varien_Object();
340:
341: $_config = array(
342: 'maxredirects' => 5,
343: 'timeout' => 30,
344: 'verifypeer' => $this->getConfigData('verify_peer')
345: );
346:
347: $_isProxy = $this->getConfigData('use_proxy', false);
348: if ($_isProxy) {
349: $_config['proxy'] = $this->getConfigData('proxy_host')
350: . ':'
351: . $this->getConfigData('proxy_port');
352: $_config['httpproxytunnel'] = true;
353: $_config['proxytype'] = CURLPROXY_HTTP;
354: }
355:
356: $client->setUri($this->_getTransactionUrl())
357: ->setConfig($_config)
358: ->setMethod(Zend_Http_Client::POST)
359: ->setParameterPost($request->getData())
360: ->setHeaders('X-VPS-VIT-CLIENT-CERTIFICATION-ID: 33baf5893fc2123d8b191d2d011b7fdc')
361: ->setHeaders('X-VPS-Request-ID: ' . $request->getRequestId())
362: ->setHeaders('X-VPS-CLIENT-TIMEOUT: ' . $this->_clientTimeout);
363:
364: try {
365: 366: 367: 368:
369: $response = $client->setUrlEncodeBody(false)->request();
370: }
371: catch (Exception $e) {
372: $result->setResponseCode(-1)
373: ->setResponseReasonCode($e->getCode())
374: ->setResponseReasonText($e->getMessage());
375:
376: $debugData['result'] = $result->getData();
377: $this->_debug($debugData);
378: throw $e;
379: }
380:
381:
382:
383: $response = strstr($response->getBody(), 'RESULT');
384: $valArray = explode('&', $response);
385:
386: foreach($valArray as $val) {
387: $valArray2 = explode('=', $val);
388: $result->setData(strtolower($valArray2[0]), $valArray2[1]);
389: }
390:
391: $result->setResultCode($result->getResult())
392: ->setRespmsg($result->getRespmsg());
393:
394: $debugData['result'] = $result->getData();
395: $this->_debug($debugData);
396:
397: return $result;
398: }
399:
400: 401: 402: 403: 404: 405: 406:
407: protected function _buildPlaceRequest(Varien_Object $payment, $amount)
408: {
409: $request = $this->_buildBasicRequest($payment);
410: $request->setAmt(round($amount,2));
411: $request->setAcct($payment->getCcNumber());
412: $request->setExpdate(sprintf('%02d',$payment->getCcExpMonth()) . substr($payment->getCcExpYear(),-2,2));
413: $request->setCvv2($payment->getCcCid());
414:
415: if ($this->getIsCentinelValidationEnabled()){
416: $params = array();
417: $params = $this->getCentinelValidator()->exportCmpiData($params);
418: $request = Varien_Object_Mapper::accumulateByMap($params, $request, $this->_centinelFieldMap);
419: }
420:
421: $order = $payment->getOrder();
422: if(!empty($order)){
423: $request->setCurrency($order->getBaseCurrencyCode());
424:
425: $orderIncrementId = $order->getIncrementId();
426: $request->setCustref($orderIncrementId)
427: ->setComment1($orderIncrementId);
428:
429: $billing = $order->getBillingAddress();
430: if (!empty($billing)) {
431: $request->setFirstname($billing->getFirstname())
432: ->setLastname($billing->getLastname())
433: ->setStreet(implode(' ', $billing->getStreet()))
434: ->setCity($billing->getCity())
435: ->setState($billing->getRegionCode())
436: ->setZip($billing->getPostcode())
437: ->setCountry($billing->getCountry())
438: ->setEmail($payment->getOrder()->getCustomerEmail());
439: }
440: $shipping = $order->getShippingAddress();
441: if (!empty($shipping)) {
442: $this->_applyCountryWorkarounds($shipping);
443: $request->setShiptofirstname($shipping->getFirstname())
444: ->setShiptolastname($shipping->getLastname())
445: ->setShiptostreet(implode(' ', $shipping->getStreet()))
446: ->setShiptocity($shipping->getCity())
447: ->setShiptostate($shipping->getRegionCode())
448: ->setShiptozip($shipping->getPostcode())
449: ->setShiptocountry($shipping->getCountry());
450: }
451: }
452: return $request;
453: }
454:
455: 456: 457: 458: 459: 460:
461: protected function _buildBasicRequest(Varien_Object $payment)
462: {
463: $request = new Varien_Object();
464: $request
465: ->setUser($this->getConfigData('user'))
466: ->setVendor($this->getConfigData('vendor'))
467: ->setPartner($this->getConfigData('partner'))
468: ->setPwd($this->getConfigData('pwd'))
469: ->setVerbosity($this->getConfigData('verbosity'))
470: ->setTender(self::TENDER_CC)
471: ->setRequestId($this->_generateRequestId());
472: return $request;
473: }
474:
475: 476: 477: 478: 479:
480: protected function _generateRequestId()
481: {
482: return Mage::helper('core')->uniqHash();
483: }
484:
485: 486: 487: 488: 489:
490: protected function _processErrors(Varien_Object $response)
491: {
492: if ($response->getResultCode() == self::RESPONSE_CODE_VOID_ERROR) {
493: throw new Mage_Paypal_Exception(Mage::helper('paypal')->__('You cannot void a verification transaction'));
494: } elseif ($response->getResultCode() != self::RESPONSE_CODE_APPROVED
495: && $response->getResultCode() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER) {
496: Mage::throwException($response->getRespmsg());
497: }
498: }
499:
500: 501: 502: 503: 504: 505:
506: protected function _applyCountryWorkarounds(Varien_Object $address)
507: {
508: if ($address->getCountry() == 'PR') {
509: $address->setCountry('US');
510: $address->setRegionCode('PR');
511: }
512: }
513:
514: 515: 516: 517: 518: 519: 520:
521: protected function _setReferenceTransaction(Varien_Object $payment, $request)
522: {
523: return $this;
524: }
525: }
526: