Overview

Namespaces

  • mcfedr
    • Paypal
      • Exceptions
      • Notifications
      • Products
  • PHP

Classes

  • Authentication
  • Buyer
  • Paypal
  • Settings
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * For accessing the paypal api
  5:  * See the test file for examples of usage
  6:  * 
  7:  * This program is free software: you can redistribute it and/or modify
  8:  * it under the terms of the GNU General Public License as published by
  9:  * the Free Software Foundation, either version 3 of the License, or
 10:  * (at your option) any later version.
 11:  *
 12:  * This program is distributed in the hope that it will be useful,
 13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15:  * GNU General Public License for more details.
 16:  *
 17:  * You should have received a copy of the GNU General Public License
 18:  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 19:  * 
 20:  * @author Fred Cox <mcfedr@gmail.com>
 21:  * @copyright Copyright Fred Cox, 2011
 22:  * @license http://www.gnu.org/licenses/gpl.html GNU GENERAL PUBLIC LICENSE
 23:  */
 24: 
 25: namespace mcfedr\Paypal;
 26: 
 27: use mcfedr\Paypal\Exceptions\UnsupportedRefundException;
 28: 
 29: /**
 30:  * This class is used for generation of buttons and handling of paypal responses
 31:  */
 32: class Paypal {
 33: 
 34:     /**
 35:      * Authentication to use
 36:      * @var Authentication
 37:      */
 38:     private $authentication;
 39: 
 40:     /**
 41:      * Settings to use
 42:      * @var Settings
 43:      */
 44:     private $settings;
 45: 
 46:     /**
 47:      * Create a new paypal object
 48:      * 
 49:      * @param Authentication $authentication 
 50:      * @param Settings $settings 
 51:      */
 52:     public function __construct(Authentication $authentication, Settings $settings = null) {
 53:         $this->authentication = $authentication;
 54:         if ($settings == null) {
 55:             $settings = new Settings();
 56:         }
 57:         $this->settings = $settings;
 58:     }
 59: 
 60:     /**
 61:      * Get a button as html
 62:      * Use getButtonParams and getButtonAction if you need to customise
 63:      *
 64:      * @param Products\Subscription|Products\CartProduct|Products\CartProduct[] $products
 65:      * @param string $paidURL URL paypal returns user to when completed
 66:      * @param string $cancelURL URL to return to if user cancels payment
 67:      * @param string $notifyURL URL for instant notifications
 68:      * @param int $invoiceId Your id for this payment, must be unique
 69:      * @param string $custom Custom data that will be in any notifications
 70:      * @param Buyer $buyer Info about the buyer to autofill in
 71:      * @param string $label Label for the button
 72:      * @return string html form with a button
 73:      */
 74:     public function getButton($products, $paidURL, $cancelURL, $notifyURL = null, $invoiceId = null, $custom = null, Buyer $buyer = null, $label = "Checkout") {
 75:         $action = $this->getButtonAction();
 76:         $params = $this->getButtonParams($products, $paidURL, $cancelURL, $notifyURL, $invoiceId, $custom, $buyer);
 77:         $ret = "<form action=\"$action\" method=\"post\">";
 78:         foreach ($params as $key => $value) {
 79:             $ret .= "<input type=\"hidden\" name=\"$key\" value=\"$value\"/>";
 80:         }
 81:         $ret .= "<button type=\"submit\">$label</button>";
 82:         $ret .= "</form>";
 83:         return $ret;
 84:     }
 85: 
 86:     /**
 87:      * Get paypal button params for this payment
 88:      * Should be posted to url getButtonAction
 89:      * use <input type="hidden" name="$key" value="$value" />
 90:      * for each element of the array returned
 91:      * 
 92:      * @param Products\Subscription|Products\CartProduct|Products\CartProduct[] $products
 93:      * @param string $paidURL URL paypal returns user to when completed
 94:      * @param string $cancelURL URL to return to if user cancels payment
 95:      * @param string $notifyURL URL for instant notifications
 96:      * @param int $invoiceId Your id for this payment, must be unique
 97:      * @param string $custom Custom data that will be in any notifications
 98:      * @param Buyer $buyer Info about the buyer to autofill in
 99:      * @return array of string 
100:      */
101:     public function getButtonParams($products, $paidURL, $cancelURL, $notifyURL = null, $invoiceId = null, $custom = null, Buyer $buyer = null) {
102:         $params = array();
103:         if ($products instanceof Products\Subscription) {
104:             $params['cmd'] = '_xclick-subscriptions';
105:             $products->setParams($params);
106:         }
107:         else if (!is_array($products) || count($products) == 1) {
108:             if (!is_array($products)) {
109:                 $product = $products;
110:             }
111:             else {
112:                 $product = $products[0];
113:             }
114:             $params['cmd'] = '_xclick';
115:             $product->setParams($params);
116:         }
117:         else {
118:             $params['cmd'] = '_cart';
119:             $params['upload'] = 1;
120:             $i = 1;
121:             foreach ($products as $product) {
122:                 $product->setParams($params, "_$i");
123:                 $i++;
124:             }
125:         }
126: 
127:         $params['return'] = $paidURL;
128:         $params['cancel_return'] = $cancelURL;
129: 
130:         if (!empty($notifyURL)) {
131:             $params['notify_url'] = $notifyURL;
132:         }
133:         if (!empty($custom)) {
134:             $params['custom'] = $custom;
135:         }
136:         if (!empty($invoiceId)) {
137:             $params['invoice'] = $invoiceId;
138:         }
139: 
140:         if (!empty($buyer)) {
141:             $buyer->setParams($params);
142:         }
143: 
144:         $this->settings->setParams($params);
145: 
146:         $params['business'] = $this->authentication->getEmail();
147:         return $params;
148:     }
149: 
150:     /**
151:      * Get form action to go with the params from getButtonParams
152:      * 
153:      * @return string
154:      */
155:     public function getButtonAction() {
156:         if ($this->authentication->isSandbox()) {
157:             return 'https://www.sandbox.paypal.com/cgi-bin/webscr';
158:         }
159:         else {
160:             return 'https://www.paypal.com/cgi-bin/webscr';
161:         }
162:     }
163: 
164:     /**
165:      * Call this function on your instant notification url (IN)
166:      * And success url to use payment data transfer (PDT)
167: 
168:      * @throws Exceptions\CurlException
169:      * @throws Exceptions\NotificationVerificationException
170:      * @throws Exceptions\NotificationInvalidException
171:      * @param array $vars variables to use, normally $_POST
172:      * @return Notifications\Notification
173:      */
174:     public function handleNotification($vars = null) {
175:         if (is_null($vars)) {
176:             $vars = $_REQUEST;
177:         }
178:         $handled = false;
179: 
180:         if (isset($vars['txn_type'])) {
181:             switch ($vars['txn_type']) {
182:                 case Notifications\Notification::TXT_CART:
183:                 case Notifications\Notification::TXT_WEB_ACCEPT:
184:                     $handled = new Notifications\CartNotification($vars);
185:                     break;
186:                 case Notifications\Notification::TXT_MASSPAY:
187:                     $handled = new Notifications\MasspayNotifications($vars);
188:                     break;
189:                 case Notifications\Notification::TXT_SUBSCRIPTION_CANCEL:
190:                 case Notifications\Notification::TXT_SUBSCRIPTION_EXPIRE:
191:                 case Notifications\Notification::TXT_SUBSCRIPTION_FAILED:
192:                 case Notifications\Notification::TXT_SUBSCRIPTION_MODIFY:
193:                 case Notifications\Notification::TXT_SUBSCRIPTION_PAYMENT:
194:                 case Notifications\Notification::TXT_SUBSCRIPTION_START:
195:                     $handled = new Notifications\SubscriptionNotification($vars);
196:                     break;
197:             }
198:         }
199:         else if (isset($vars['payment_status'])) {
200:             switch ($vars['payment_status']) {
201:                 case 'Refunded':
202:                 case 'Reversed':
203:                 case 'Canceled_Reversal':
204:                     $handled = new Notifications\CartChangeNotification($vars);
205:                     break;
206:             }
207:         }
208:         else if (isset($vars['transaction_type'])) {
209:             switch($vars['transaction_type']) {
210:                 case Notifications\Notification::TXT_ADAPTIVE_CREATE:
211:                 case Notifications\Notification::TXT_ADAPTIVE_ADJUSTMENT:
212:                     $handled = new Notifications\AdaptivePaymentNotification($vars);
213:                     break;
214:             }
215:         }
216: 
217:         if (!$handled) {
218:             return false;
219:         }
220: 
221:         if (!$this->verifyNotification($handled, $vars)) {
222:             return false;
223:         }
224: 
225:         //Check if it is ok, if not an exception will be thrown
226:         $handled->isOK($this->authentication, $this->settings);
227: 
228:         if ($this->settings->logNotifications === true) {
229:             error_log('paypal notification ' . http_build_query($vars));
230:         }
231:         else if ($this->settings->logNotifications) {
232:             $this->settings->logNotifications->info('Paypal Notification', array(
233:                 'vars' => $vars
234:             ));
235:         }
236:         return $handled;
237:     }
238: 
239:     /**
240:      * Send a payment to an email address
241:      * Users MassPayments API
242:      * Warning, this works straight away, no confirming or anything
243:      * Money is sent instantly
244:      * 
245:      * @throws Exceptions\CurlException
246:      * @throws Exceptions\MasspayException
247:      * @param string|string[] $email one or more email addresses to send payment to
248:      * @param double|double[] $amount amount(s) to send to each address
249:      * @param string|string[] $id unique id for the payment(s)
250:      * @param string|string[] $note note to the user(s)
251:      * @param string $subject subject for email
252:      * @return bool whether succesful or not
253:      */
254:     public function sendPayment($email, $amount, $id = '', $note = '', $subject = null) {
255:         $params = array();
256:         $params['RECEIVERTYPE'] = 'EmailAddress';
257:         $params['EMAILSUBJECT'] = substr($subject, 0, 255);
258:         if (is_array($email)) {
259:             $count = count($email);
260: 
261:             $amountArray = is_array($amount);
262:             if ($amountArray && count($amount) != $count) {
263:                 return false;
264:             }
265: 
266:             $idArray = is_array($id);
267:             if ($idArray && count($id) != $count) {
268:                 return false;
269:             }
270: 
271:             $noteArray = is_array($note);
272:             if ($noteArray && count($note) != $count) {
273:                 return false;
274:             }
275: 
276:             for ($i = 0; $i < $count; $i++) {
277:                 $params["L_EMAIL$i"] = $email[$i];
278:                 if ($amountArray) {
279:                     $params["L_AMT$i"] = $amount[$i];
280:                 }
281:                 else {
282:                     $params["L_AMT$i"] = $amount;
283:                 }
284: 
285:                 if ($idArray) {
286:                     $params["L_UNIQUEID$i"] = substr($id[$i], 0, 30);
287:                 }
288:                 else {
289:                     $params["L_UNIQUEID$i"] = substr($id, 0, 30);
290:                 }
291: 
292:                 if ($noteArray) {
293:                     $params["L_NOTE$i"] = substr($note[$i], 0, 4000);
294:                 }
295:                 else {
296:                     $params["L_NOTE$i"] = substr($note, 0, 4000);
297:                 }
298:             }
299:         }
300:         else {
301:             $params['L_EMAIL0'] = $email;
302:             $params['L_AMT0'] = $amount;
303:             $params['L_UNIQUEID0'] = substr($id, 0, 30);
304:             $params['L_NOTE0'] = substr($note, 0, 4000);
305:         }
306:         $params['CURRENCYCODE'] = $this->settings->currency;
307:         $response = $this->callPaypalNVP('MassPay', $params);
308:         if ($response !== false) {
309:             if ($response['ACK'] == 'Success') {
310:                 return true;
311:             }
312:             else {
313:                 throw new Exceptions\MasspayException($response);
314:             }
315:         }
316:         else {
317:             return false;
318:         }
319:     }
320: 
321:     /**
322:      * Refund the payment
323:      *
324:      *
325:      * @param string $transactionId id
326:      * @param string $invoiceId optional internal payment id
327:      * @param string $type currently only Full is supported
328:      * @throws Exceptions\RefundException
329:      * @throws Exceptions\UnsupportedRefundException
330:      * @return bool successful
331:      */
332:     public function refundPayment($transactionId, $invoiceId = null, $type = 'Full') {
333:         $params = array();
334:         $params['TRANSACTIONID'] = $transactionId;
335:         $params['INVOICEID'] = $invoiceId;
336:         if($type != 'Full') {
337:             throw new UnsupportedRefundException($type);
338:         }
339:         $params['REFUNDTYPE'] = $type;
340:         $response = $this->callPaypalNVP('RefundTransaction', $params);
341:         if ($response !== false) {
342:             if ($response['ACK'] == 'Success') {
343:                 return true;
344:             }
345:             else {
346:                 throw new Exceptions\RefundException($response);
347:             }
348:         }
349:         else {
350:             return false;
351:         }
352:     }
353: 
354:     /**
355:      * Make a paypal NVP API call
356:      * 
357:      * @throws Exceptions\CurlException
358:      * @param string $method
359:      * @param array $params
360:      * @return array|bool the response vars as an assoc array or false on error
361:      */
362:     private function callPaypalNVP($method, $params) {
363:         $headerParams = array();
364:         if ($this->authentication->isSandbox()) {
365:             $url = 'https://api-3t.sandbox.paypal.com/nvp';
366:         }
367:         else {
368:             $url = 'https://api-3t.paypal.com/nvp';
369:         }
370:         $headerParams['USER'] = $this->authentication->getUsername();
371:         $headerParams['PWD'] = $this->authentication->getPassword();
372:         $headerParams['SIGNATURE'] = $this->authentication->getSignature();
373:         $headerParams['VERSION'] = '71.0';
374: 
375:         $data = http_build_query(array_merge(array('METHOD' => $method), $headerParams, $params));
376: 
377:         $response = $this->makeRequest($url, $data);
378:         if ($response === false) {
379:             return false;
380:         }
381: 
382:         parse_str($response, $nvpResArray);
383:         return $nvpResArray;
384:     }
385: 
386:     /**
387:      * Verify paypal notification
388:      *
389:      *
390:      * @param \mcfedr\Paypal\Notifications\Notification $notification
391:      * @param array $vars
392:      * @throws Exceptions\NotificationVerificationException
393:      * @return bool
394:      */
395:     private function verifyNotification($notification, $vars) {
396:         if ($this->authentication->isSandbox()) {
397:             $url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
398:         }
399:         else {
400:             $url = 'https://www.paypal.com/cgi-bin/webscr';
401:         }
402:         $data = http_build_query(array_merge(array('cmd' => '_notify-validate'), $vars));
403:         $response = $this->makeRequest($url, $data);
404:         if ($response === false) {
405:             return false;
406:         }
407:         $verified = $response == 'VERIFIED';
408:         if (!$verified) {
409:             throw new Exceptions\NotificationVerificationException($response, $notification);
410:         }
411:         return $verified;
412:     }
413: 
414:     /**
415:      * Makes a request using curl, basically sets some curl options
416:      * 
417:      * @throws Exceptions\CurlException
418:      * @param string $url
419:      * @param string $data
420:      * @return string|bool returns false on error 
421:      */
422:     private function makeRequest($url, $data) {
423:         //setting the curl parameters.
424:         $ch = curl_init();
425:         curl_setopt($ch, CURLOPT_URL, $url);
426:         //curl_setopt($ch, CURLOPT_VERBOSE, 1);
427:         //turning off the server and peer verification(TrustManager Concept).
428:         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
429:         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
430: 
431:         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
432:         curl_setopt($ch, CURLOPT_POST, 1);
433: 
434:         //setting the nvpreq as POST FIELD to curl
435:         curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
436: 
437:         //getting response from server
438:         $response = curl_exec($ch);
439: 
440:         if (curl_errno($ch)) {
441:             throw new Exceptions\CurlException($ch, $url, $data);
442:         }
443:         else {
444:             //closing the curl
445:             curl_close($ch);
446:         }
447:         return $response;
448:     }
449: 
450: }
451: 
Paypal API documentation generated by ApiGen 2.8.0