Highway to PSR2
[openemr.git] / portal / patient / fwk / libs / verysimple / Payment / PayPal.php
blob620211c064640fa2c3a04ce9363263e639111a87
1 <?php
2 /** @package verysimple::Payment */
4 /**
5 * import supporting libraries
6 */
7 require_once("PaymentProcessor.php");
9 /**
10 * PayPal extends the generic PaymentProcessor object to process
11 * a PaymentRequest through the PayPal direct payment API.
13 * @package verysimple::Payment
14 * @author VerySimple Inc.
15 * @copyright 1997-2012 VerySimple, Inc.
16 * @license http://www.gnu.org/licenses/lgpl.html LGPL
17 * @version 2.1
19 class PayPal extends PaymentProcessor
21 // used by paypal - 'sandbox' or 'beta-sandbox' or 'live'
22 private $environment = 'sandbox';
24 /**
25 * Called on contruction
27 * @param bool $test
28 * set to true to enable test mode. default = false
30 function Init($testmode)
32 // set the post url depending on whether we're in test mode or not
33 $this->environment = $testmode ? 'sandbox' : 'live';
36 /**
38 * @see PaymentProcessor::Refund()
40 function Refund(RefundRequest $req)
42 $resp = new PaymentResponse();
43 $resp->OrderNumber = $req->InvoiceId;
45 $nvpStr = "&TRANSACTIONID=" . urlencode($req->TransactionId);
47 $nvpStr .= "&CURRENCYCODE=" . urlencode($req->TransactionCurrency);
49 if ($req->RefundType == RefundRequest::$REFUND_TYPE_PARTIAL) {
50 if (! $req->RefundAmount) {
51 $resp->IsSuccess = false;
52 $resp->ResponseMessage = "RefundAmount is required for partial refund";
53 return $resp;
56 $nvpStr .= "&REFUNDTYPE=Partial&AMT=" . urldecode($req->RefundAmount);
57 } else {
58 $nvpStr .= "&REFUNDTYPE=Full";
61 if ($req->Memo) {
62 $nvpStr .= "&NOTE=" . urlencode($req->Memo);
65 if ($req->InvoiceId) {
66 $nvpStr .= "&INVOICEID=" . urlencode($req->InvoiceId);
69 // Execute the API operation
70 $resp->RawResponse = $this->PPHttpPost('RefundTransaction', $nvpStr);
72 if ("SUCCESS" == strtoupper($resp->RawResponse ["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($resp->RawResponse ["ACK"])) {
74 * SAMPLE SUCCESS RESPONSE
75 * Array (
76 * [REFUNDTRANSACTIONID] => 5L51568382268602F
77 * [FEEREFUNDAMT] => 0%2e18
78 * [GROSSREFUNDAMT] => 5%2e15
79 * [NETREFUNDAMT] => 4%2e97
80 * [CURRENCYCODE] => USD
81 * [TIMESTAMP] => 2012%2d03%2d22T23%3a25%3a25Z
82 * [CORRELATIONID] => 2a2cc13c7c64a
83 * [ACK] => Success
84 * [VERSION] => 62%2e0
85 * [BUILD] => 2649250
86 * )
89 $resp->IsSuccess = true;
90 $resp->TransactionId = $resp->RawResponse ['REFUNDTRANSACTIONID'];
91 $resp->OrderNumber = $req->InvoiceId;
92 $resp->ResponseMessage = urldecode($resp->RawResponse ['GROSSREFUNDAMT'] . ' ' . $resp->RawResponse ['CURRENCYCODE']) . " was sucessfully refunded.";
93 } else {
95 * SAMPLE ERROR RESPONSE:
96 * Array (
97 * [TIMESTAMP] => 2012%2d03%2d22T23%3a15%3a22Z
98 * [CORRELATIONID] => ae19bab2b4b1
99 * [ACK] => Failure [VERSION] => 62%2e0
100 * [BUILD] => 2649250
101 * [L_ERRORCODE0] => 10004
102 * [L_SHORTMESSAGE0] => Transaction%20refused%20because%20of%20an%20invalid%20argument%2e%20See%20additional%20error%20messages%20for%20details%2e
103 * [L_LONGMESSAGE0] => The%20transaction%20id%20is%20not%20valid
104 * [L_SEVERITYCODE0] => Error
108 $resp->IsSuccess = false;
109 $resp->ResponseCode = urldecode($this->GetArrayVal($resp->RawResponse, "L_ERRORCODE0"));
110 $resp->ResponseMessage = $this->GetErrorMessage($resp->RawResponse);
113 $resp->ParsedResponse = "";
114 $delim = "";
115 foreach (array_keys($resp->RawResponse) as $key) {
116 $resp->ParsedResponse .= $delim . $key . "='" . urldecode($resp->RawResponse [$key]) . "'";
117 $delim = ", ";
120 return $resp;
124 * Process a PaymentRequest
126 * @param PaymentRequest $req
127 * Request object to be processed
128 * @return PaymentResponse
130 function Process(PaymentRequest $req)
132 $resp = new PaymentResponse();
133 $resp->OrderNumber = $req->OrderNumber;
135 // before bothering with contacting the processor, check for some basic fields
136 if ($req->CCNumber == '') {
137 $resp->IsSuccess = false;
138 $resp->ResponseCode = "0";
139 $resp->ResponseMessage = "No Credit Card Number Provided";
140 return $resp;
143 if ($req->CustomerFirstName == '') {
144 $resp->IsSuccess = false;
145 $resp->ResponseCode = "0";
146 $resp->ResponseMessage = "No Cardholder Name Provided";
147 return $resp;
150 // post to paypal service
151 // Set request-specific fields.
152 $paymentType = $req->TransactionType == PaymentRequest::$TRANSACTION_TYPE_AUTH_CAPTURE ? urlencode('Sale') : urlencode('Authorization');
153 $firstName = urlencode($req->CustomerFirstName);
154 $lastName = urlencode($req->CustomerLastName);
155 $creditCardType = urlencode(trim($req->CCType));
156 $creditCardNumber = urlencode(trim($req->CCNumber));
158 // month needs to be two digits - padded with leading zero if necessary
159 $padDateMonth = urlencode(trim(str_pad($req->CCExpMonth, 2, '0', STR_PAD_LEFT)));
161 // year needs to be full 4-digit
162 $expDateYear = urlencode($this->GetFullYear(trim($req->CCExpYear)));
164 $email = (urlencode($req->CustomerEmail));
165 $invoiceNum = (urlencode($req->InvoiceNumber));
166 $orderDesc = (urlencode($req->OrderDescription));
167 $cvv2Number = urlencode($req->CCSecurityCode);
168 $address1 = urlencode($req->CustomerStreetAddress);
169 $address2 = urlencode($req->CustomerStreetAddress2);
170 $city = urlencode($req->CustomerCity);
171 $state = urlencode($req->CustomerState);
172 $zip = urlencode($req->CustomerZipCode);
173 $amount = urlencode($req->TransactionAmount);
174 $currencyID = urlencode($req->TransactionCurrency); // or other currency ('GBP', 'EUR', 'JPY', 'CAD', 'AUD')
176 // soft descriptor can only be a max of 22 chars with no non-alphanumeric characters
177 $softdescriptor = urlencode(substr(preg_replace("/[^a-zA-Z0-9\s]/", "", $req->SoftDescriptor), 0, 22));
179 // legit country code list: https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_api_nvp_country_codes
180 $country = urlencode(strtoupper($req->CustomerCountry)); // US or other valid country code
181 if ($country == "USA") {
182 $country = "US";
185 // Add request-specific fields to the request string.
186 $nvpStr = "&PAYMENTACTION=$paymentType&AMT=$amount&CREDITCARDTYPE=$creditCardType&ACCT=$creditCardNumber" . "&EXPDATE=$padDateMonth$expDateYear&CVV2=$cvv2Number&FIRSTNAME=$firstName&LASTNAME=$lastName" . "&STREET=$address1&CITY=$city&STATE=$state&ZIP=$zip&COUNTRYCODE=$country&CURRENCYCODE=$currencyID" . "&DESC=$orderDesc&INVNUM=$invoiceNum&EMAIL=$email&SOFTDESCRIPTOR=$softdescriptor";
188 // make the post - use a try/catch in case of network errors
189 try {
190 $resp->RawResponse = $this->PPHttpPost('DoDirectPayment', $nvpStr);
192 if ("SUCCESS" == strtoupper($resp->RawResponse ["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($resp->RawResponse ["ACK"])) {
193 $resp->IsSuccess = true;
194 $resp->TransactionId = urldecode($this->GetArrayVal($resp->RawResponse, "TRANSACTIONID"));
195 $resp->ResponseCode = urldecode("AVSCODE=" . $this->GetArrayVal($resp->RawResponse, "AVSCODE") . ",CVV2MATCH=" . $this->GetArrayVal($resp->RawResponse, "CVV2MATCH"));
196 $resp->ResponseMessage = urldecode("Charge of " . $this->GetArrayVal($resp->RawResponse, "AMT") . " Posted");
197 } else {
198 // for error descriptions, refrer to https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_errorcodes
199 $resp->IsSuccess = false;
200 $resp->ResponseCode = urldecode($this->GetArrayVal($resp->RawResponse, "L_ERRORCODE0"));
201 $resp->ResponseMessage = $this->GetErrorMessage($resp->RawResponse);
204 $resp->ParsedResponse = "";
205 $delim = "";
206 foreach (array_keys($resp->RawResponse) as $key) {
207 $resp->ParsedResponse .= $delim . $key . "='" . urldecode($resp->RawResponse [$key]) . "'";
208 $delim = ", ";
210 } catch (Exception $ex) {
211 // this means we had a connection error talking to the gateway
212 $resp->IsSuccess = false;
213 $resp->ResponseCode = $ex->getCode();
214 $resp->ResponseMessage = $ex->getMessage();
217 return $resp;
221 * Given a response array, attempt to return the most sensible error message.
223 * @param array $httpParsedResponseAr
225 private function GetErrorMessage($httpParsedResponseAr)
228 * SAMPLE RETURN VALUE
230 * [TIMESTAMP] => 2012%2d03%2d23T01%3a47%3a01Z
231 * [CORRELATIONID] => c9dcc0b7acd94
232 * [ACK] => Failure
233 * [VERSION] => 62%2e0
234 * [BUILD] => 2649250
235 * [L_ERRORCODE0] => 10507
236 * [L_SHORTMESSAGE0] => Invalid%20Configuration
237 * [L_LONGMESSAGE0] => This%20transaction%20cannot%20be%20processed%2e%20Please%20contact%20PayPal%20Customer%20Service%2e
238 * [L_SEVERITYCODE0] => Error
239 * [AMT] => 103%2e00
240 * [CURRENCYCODE] => USD
243 $errmsg = $this->GetArrayVal($httpParsedResponseAr, "L_SHORTMESSAGE0") . ": ";
245 // figure out the message as PayPal reports it
246 $longmessage = $this->GetArrayVal($httpParsedResponseAr, "L_LONGMESSAGE0");
248 // paypal prepends this to every message, so strip it out
249 $longmessage = str_replace("This%20transaction%20cannot%20be%20processed%2e", "", $longmessage);
251 if ($longmessage != "") {
252 // this will generally be the best description of the error
253 $errmsg .= $longmessage;
254 } else {
255 // paypal didn't give a simple error description so we have to try to decipher the gateway response
257 // this is the response code from the gateway
258 $processor_code = $this->GetArrayVal($httpParsedResponseAr, "L_ERRORPARAMVALUE0");
260 if ($processor_code) {
261 // this will usually be "ProcessorResponse" in which case we don't need to display it
262 $processor_prefix = $this->GetArrayVal($httpParsedResponseAr, "L_ERRORPARAMID0");
263 $processor_prefix = ($processor_prefix == "ProcessorResponse") ? '' : $processor_prefix . ' - ';
265 $processor_message = $processor_prefix . $this->getProcessorResponseDescription($processor_code);
267 $errmsg .= $processor_message;
271 return urldecode($errmsg);
275 * Util to return array values without throwing an undefined error
277 * @param Array $arr
278 * @param variant $key
279 * @param variant $not_defined_val
280 * value to return if array key doesn't exist
281 * @return variant
283 private function GetArrayVal($arr, $key, $not_defined_val = "")
285 return array_key_exists($key, $arr) ? $arr [$key] : $not_defined_val;
289 * Send HTTP POST Request.
290 * Throws an Exception if the server communication failed
292 * @param
293 * string The API method name
294 * @param
295 * string The POST Message fields in &name=value pair format
296 * @return array Parsed HTTP Response body
298 private function PPHttpPost($methodName_, $nvpStr_)
301 // Set up your API credentials, PayPal end point, and API version.
302 $API_UserName = urlencode($this->Username);
303 $API_Password = urlencode($this->Password);
304 $API_Signature = urlencode($this->Signature);
305 $API_Endpoint = "https://api-3t.paypal.com/nvp";
307 if ("sandbox" === $this->environment || "beta-sandbox" === $this->environment) {
308 $API_Endpoint = "https://api-3t." . $this->environment . ".paypal.com/nvp";
311 // $version = urlencode('51.0');
312 $version = urlencode('62.0');
314 // Set the curl parameters.
315 $ch = curl_init();
316 curl_setopt($ch, CURLOPT_URL, $API_Endpoint);
317 curl_setopt($ch, CURLOPT_VERBOSE, 1);
319 // Turn off the server and peer verification (TrustManager Concept).
320 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
321 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
323 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
324 curl_setopt($ch, CURLOPT_POST, 1);
326 // Set the API operation, version, and API signature in the request.
327 $nvpreq = "METHOD=$methodName_&VERSION=$version&PWD=$API_Password&USER=$API_UserName&SIGNATURE=$API_Signature$nvpStr_";
329 // Set the request as a POST FIELD for curl.
330 curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpreq);
332 // Get response from the server.
333 $httpResponse = curl_exec($ch);
335 if (! $httpResponse) {
336 throw new Exception("$methodName_ failed: " . curl_error($ch) . '(' . curl_errno($ch) . ')');
339 // Extract the response details.
340 $httpResponseAr = explode("&", $httpResponse);
342 $httpParsedResponseAr = array ();
343 foreach ($httpResponseAr as $i => $value) {
344 $tmpAr = explode("=", $value);
345 if (sizeof($tmpAr) > 1) {
346 $httpParsedResponseAr [$tmpAr [0]] = $tmpAr [1];
350 if ((0 == sizeof($httpParsedResponseAr)) || ! array_key_exists('ACK', $httpParsedResponseAr)) {
351 throw new Exception("Invalid HTTP Response for POST request($nvpreq) to $API_Endpoint.");
354 return $httpParsedResponseAr;
358 * This returns a formatted error given a payment processor error response code.
360 * @link https://www.x.com/blogs/matt/2010/10/26/error-codes-explained-15005
361 * @param string $code
362 * @return string possible description of error
364 private function getProcessorResponseDescription($code)
366 return "Transaction was rejected by the issuing bank with error code $code.";
368 // @TODO these have proven to be unreliable, but maybe somebody can do something better?
370 * $responses = Array();
371 * $responses['0005'] = "The transaction was declined without explanation by the card issuer.";
372 * $responses['0013'] = "The transaction amount is greater than the maximum the issuer allows.";
373 * $responses['0014'] = "The issuer indicates that this card is not valid.";
374 * $responses['0051'] = "The credit limit for this account has been exceeded.";
375 * $responses['0054'] = "The card is expired.";
376 * $responses['1015'] = "The credit card number was invalid.";
377 * $responses['1511'] = "Duplicate transaction attempt.";
378 * $responses['1899'] = "Timeout waiting for host response.";
379 * $responses['2075'] = "Approval from the card issuer's voice center is required to process this transaction.";
381 * return array_key_exists($code,$responses)
382 * ? "Error Code " . $code . ": " .$responses[$code]
383 * : "Error Code " . $code;