It is a full-fledged payments library.
- Paypal Express Checkout Nvp
- Paypal Pro Checkout Nvp
- Authorize.Net AIM
- Be2Bill
- All gateways supported by omnipay lib via bridge.
Note: The code snippets presented below are only for demonstration purposes (a pseudo code). Its goal is to illustrate general approach to various tasks. To see real live examples follow the links provided when appropriate.
Note: If you'd like to see real world examples we have a sandbox: online, code.
In general, you have to create a request and have to have an action which knows what to do with such request. A payment contains sets of actions and can execute a request. So, payment is the place where a request and an action meet each other.
<?php
$payment = new Payment;
$payment->addAction(new CaptureAction));
//CaptureAction does its job.
$payment->execute($capture = new CaptureRequest(array(
'amount' => 100,
'currency' => 'USD'
));
var_export($capture->getModel());
<?php
class CaptureAction implements ActionInterface
{
public function execute($request)
{
$model = $request->getModel();
//capture payment logic here
$model['status'] = 'success';
$model['transaction_id'] = 'an_id';
}
public function supports($request)
{
return $request instanceof CaptureRequest;
}
}
Here's a real world example.
That's a big picture. Now let's talk about the details:
-
An action does not want to do all the job alone, so it delegates some responsibilities to other actions. In order to achieve this the action must be a payment aware action. Only then, it can create a sub request and pass it to the payment.
<?php class FooAction extends PaymentAwareAction { public function execute($request) { //do its jobs // delegate some job to bar action. $this->payment->execute(new BarRequest); } }
See paypal capture action.
-
What about redirects? Some payments like paypal requires authorization on their side. The payum can handle such cases and for that we use something called interactive requests. It is a special request object, which extends an exception. You can throw interactive redirect request at any time and catch it at a top level.
<?php class FooAction implements ActionInterface { public function execute($request) { throw new RedirectUrlInteractiveRequest('http://example.com/auth'); } }
<?php try { $payment->addAction(new FooAction); $payment->execute(new FooRequest); } catch (RedirectUrlInteractiveRequest $redirectUrlInteractiveRequest) { header( 'Location: '.$redirectUrlInteractiveRequest->getUrl()); exit; }
See paypal authorize token action.
-
Good status handling is very important. Statuses must not be hard coded and should be easy to reuse, hence we use an interface to hanle this. Status request is provided by default by our library, however you are free to use your own and you can do so by implementing status interface.
<?php class FooAction implements ActionInterface { public function execute($request) { if ('success condition') { $request->markSuccess(); } else if ('pending condition') { $request->markPending(); } else { $request->markUnknown(); } } public function supports($request) { return $request instanceof StatusRequestInterface; } }
<?php $payment->addAction(new FooAction); $payment->execute($status = new BinaryMaskStatusRequest); $status->isSuccess(); $status->isPending(); // or $status->getStatus();
The status logic could be a bit complicated or pretty simple.
-
There must be a way to extend the payment with custom logic. Extension to the rescue. Let's look at the example below. Imagine you want to check permissions before user can capture the payment:
<?php class PermissionExtension implements ExtensionInterface { public function onPreExecute($request) { if (false == in_array('ROLE_CUSTOMER', $request->getModel()->getRoles())) { throw new Exception('The user does not have required roles.'); } // congrats, user have enough rights. } }
<?php $payment->addExtension(new PermissionExtension);
// here the place the exception may be thrown.
$payment->execute(new FooRequest);
```
The [storage extension][storage-extension-interface] may be a good extension example.
-
Before you are redirected to a gateway side, you may want to store data somewhere, right? We take care of that too. This is handled by storage and its storage extension for payment. The extension can solve two tasks. First it can save a model after the request is processed. Second, it could find a model by its id before request is processed. Currently Doctrine and filesystem (use it for tests only!) storages are supported.
<?php $storage = new FooStorage; $payment = new Payment; $payment->addExtension(new StorageExtension($storage));
-
The payment API can have different versions? No problem! A payment may contain a set of APIs. When API aware action is added to a payment it tries to set an API one by one to the action until the action accepts one.
<?php class FooAction implements ActionInterface, ApiAwareInterface { public function setApi($api) { if (false == $api instanceof FirstApi) { throw new UnsupportedApiException('Not supported.'); } $this->api = $api; } } class BarAction implements ActionInterface, ApiAwareInterface { public function setApi($api) { if (false == $api instanceof SecondApi) { throw new UnsupportedApiException('Not supported.'); } $this->api = $api; } }
<?php $payment = new Payment; $payment->addApi(new FirstApi); $payment->addApi(new SecondApi); //here the ApiVersionOne will be injected to FooAction $payment->addAction(new FooAction); //here the ApiVersionTwo will be injected to BarAction $payment->addAction(new BarAction);
See authorize.net capture action.
As a result of such architecture we have decoupled, easy to extend and reusable library. For example, you can add your domain specific actions or a logger extension. Thanks to its flexibility any task could be achieved.
Note: There is a doc on how to setup and use payum bundle with supported payment gateways.
The bundle allows you easy configure payments, add storages, custom actions/extensions/apis. Nothing is hardcoded: all payments and storages are added via factories (payment factories, storage factories) in the bundle build method. You can add your payment this way too!
Also, it provides a nice secured capture controller. It's extremely reusable. Check the sandbox (code) for more details.
The bundle supports omnipay gateways (up to 25) out of the box. They could be configured the same way as native payments. The capture controller works here too.
<?php
//Source: Payum2\Examples\ReadmeTest::bigPicture()
use Payum2\Examples\Action\CaptureAction;
use Payum2\Examples\Action\StatusAction;
use Payum2\Request\CaptureRequest;
use Payum2\Payment;
//Populate payment with actions.
$payment = new Payment;
$payment->addAction(new CaptureAction());
//Create request and model. It could be anything supported by an action.
$captureRequest = new CaptureRequest(array(
'amount' => 10,
'currency' => 'EUR'
));
//Execute request
$payment->execute($captureRequest);
echo 'We are done!';
echo 'We are done!';
<?php
//Source: Payum2\Examples\ReadmeTest::interactiveRequests()
use Payum2\Examples\Request\AuthorizeRequest;
use Payum2\Examples\Action\AuthorizeAction;
use Payum2\Request\CaptureRequest;
use Payum2\Request\RedirectUrlInteractiveRequest;
use Payum2\Payment;
$payment = new Payment;
$payment->addAction(new AuthorizeAction());
$request = new AuthorizeRequest($model);
if ($interactiveRequest = $payment->execute($request, $catchInteractive = true)) {
if ($interactiveRequest instanceof RedirectUrlInteractiveRequest) {
echo 'User must be redirected to '.$interactiveRequest->getUrl();
}
throw $interactiveRequest;
}
<?php
//Source: Payum2\Examples\ReadmeTest::gettingRequestStatus()
use Payum2\Examples\Action\StatusAction;
use Payum2\Request\BinaryMaskStatusRequest;
use Payum2\Payment;
//Populate payment with actions.
$payment = new Payment;
$payment->addAction(new StatusAction());
$statusRequest = new BinaryMaskStatusRequest($model);
$payment->execute($statusRequest);
//Or there is a status which require our attention.
if ($statusRequest->isSuccess()) {
echo 'We are done!';
}
echo 'Uhh something wrong. Check other possible statuses!';
echo 'Uhh something wrong. Check other possible statuses!';
<?php
//Source: Payum2\Examples\ReadmeTest::persistPaymentDetails()
use Payum2\Payment;
use Payum2\Storage\FilesystemStorage;
use Payum2\Extension\StorageExtension;
$storage = new FilesystemStorage('path_to_storage_dir', 'YourModelClass', 'idProperty');
$payment = new Payment;
$payment->addExtension(new StorageExtension($storage));
//do capture for example.
What's inside?
- The extension will try to find model on
onPreExecute
if an id given. - Second, It saves the model after execute, on
onInteractiveRequest
andpostRequestExecute
.
You can star the lib on github or packagist. You may also drop a message on Twitter.
If you are having general issues with payum, we suggest posting your issue on stackoverflow. Feel free to ping @maksim_ka2 on Twitter if you can't find a solution.
If you believe you have found a bug, please report it using the GitHub issue tracker, or better yet, fork the library and submit a pull request.
Payum is released under the MIT License. For more information, see License.