Skip to content

Commit

Permalink
Merge pull request #19 from Payum/api-order-unified-payments
Browse files Browse the repository at this point in the history
add unified order api, for all possible payments. Now it covers only pay...
  • Loading branch information
makasim committed Apr 18, 2014
2 parents 5e1a922 + 83d7427 commit 76de5c2
Show file tree
Hide file tree
Showing 10 changed files with 443 additions and 34 deletions.
76 changes: 48 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,31 @@ _**Note**: Never use built in web server on production. Set apache or nginx serv

## Use

First create a paypal payment:
There are two APIs available. First called `Order`.
It is unified API and the format supported by all payments.
While purchasing the server may ask for additional info, like credit card.

```bash
$ curl \
-X POST \
-H "Content-Type: application/json" \
http://server.payum.forma-dev.com/api/payment \
-d '{ "currency": "USD", "amount": 10, "meta": { "name": "paypal", "purchase_after_url": "http://google.com" } }';

{
"amount": 10,
"currency": "USD",
"details": null,
"meta": {
"links": {
"get": "http://dev.payum-server.com/api/order/_sgU9tUmXVuBzjb3VcREss79pmlsLBp_W549XQKeM_c",
"purchase": "http://dev.payum-server.com/purchase/sXzD1EtZKT-8sLmcPULgUOTroszWPqVg5QiFwP7uPfA"
}
}
}
```

The other API is called `Payment`. This API has payment specific format. Here's an example for Paypal and Stripe:

```bash
$ curl \
Expand All @@ -65,8 +89,6 @@ $ curl \

_**Note**: Do not store purchase url. Use it immediately._

or a stripe one:

```bash
$ curl \
-X POST \
Expand All @@ -93,39 +115,37 @@ $ curl \

_**Note**: Do not store purchase url. Use it immediately._

Redirect user to purchase. After users will be redirected back to purchase_after_url.
## Tips

## Get details and status
* Use get url to get payment\order details and its status.

```bash
$ curl -X GET http://dev.payum-server.com/api/payment/WOFJgK-VrsxXsZu8sMHP0NsSridaWz-aiLO99XJxVlk
```bash
$ curl -X GET http://dev.payum-server.com/api/payment/WOFJgK-VrsxXsZu8sMHP0NsSridaWz-aiLO99XJxVlk

{
"PAYMENTREQUEST_0_CURRENCYCODE": "USD",
"PAYMENTREQUEST_0_AMT": 10,
"meta": {
"name": "paypal",
"purchase_after_url": "http:\/\/google.com",
"links": {
"purchase": null,
"get": "http:\/\/dev.payum-server.com\/api\/payment\/WOFJgK-VrsxXsZu8sMHP0NsSridaWz-aiLO99XJxVlk"
},
"status": 2
{
"PAYMENTREQUEST_0_CURRENCYCODE": "USD",
"PAYMENTREQUEST_0_AMT": 10,
"meta": {
"name": "paypal",
"purchase_after_url": "http:\/\/google.com",
"links": {
"purchase": null,
"get": "http:\/\/dev.payum-server.com\/api\/payment\/WOFJgK-VrsxXsZu8sMHP0NsSridaWz-aiLO99XJxVlk"
},
"status": 2
}
}
}
```
```

Enjoy!
* Exceptions tracking

## Errors\Exceptions tracking
The server comes with built in support of [sentry](https://getsentry.com/welcome/) service. You just need to set a `SENTRY_DSN` environment (In Case you use apache add this `SetEnv SENTRY_DSN aDsn` to your vhost.):

The server comes with built in support of [sentry](https://getsentry.com/welcome/) service. You just need to set a `SENTRY_DSN` environment:

```bash
$ SENTRY_DSN=aDsn php -S 127.0.0.1:8000 web/index.php
```
```bash
$ SENTRY_DSN=aDsn php -S 127.0.0.1:8000 web/index.php
```

In Case you use apache add this `SetEnv SENTRY_DSN aDsn` to your vhost.
* Redirect user to a purchase url to proceed with a payment.

## License

Expand Down
8 changes: 8 additions & 0 deletions order-paypal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"amount": 10,
"currency": "USD",
"meta": {
"name": "paypal",
"purchase_after_url": "http:\/\/google.com"
}
}
49 changes: 49 additions & 0 deletions src/Payum/Server/Action/OrderStatusAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Payum\Server\Action;

use Payum\Core\Action\PaymentAwareAction;
use Payum\Core\Request\StatusRequestInterface;
use Payum\Server\Model\Order;

class OrderStatusAction extends PaymentAwareAction
{
/**
* {@inheritDoc}
*/
public function execute($request)
{
/** @var Order $order */
$order = $request->getModel();

if ($order->getDetails()) {
$request->setModel($order->getDetails());

$this->payment->execute($request);

$request->setModel($order);
} else {
$request->markNew();
}
}

/**
* {@inheritDoc}
*/
public function supports($request)
{
return
$request instanceof StatusRequestInterface &&
$request->getModel() instanceof Order
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Payum\Core\Request\SecuredCaptureRequest as CoreSecuredCaptureRequest;
use Silex\Application;

class PaypalExpressCheckoutCaptureAction extends PaymentAwareAction
class DetailsCaptureAction extends PaymentAwareAction
{
/**
* @var Application
Expand Down
54 changes: 54 additions & 0 deletions src/Payum/Server/Action/Paypal/OrderCaptureAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
namespace Payum\Server\Action\Paypal;

use Payum\Core\Action\PaymentAwareAction;
use Payum\Core\Storage\StorageInterface;
use Payum\Server\Model\Order;
use Payum\Server\Model\PaymentDetails;
use Payum\Server\Request\SecuredCaptureRequest;

class OrderCaptureAction extends PaymentAwareAction
{
/**
* @var \Payum\Core\Storage\StorageInterface
*/
private $paymentDetailsStorage;

/**
* @param StorageInterface $paymentDetailsStorage
*/
public function __construct(StorageInterface $paymentDetailsStorage)
{
$this->paymentDetailsStorage = $paymentDetailsStorage;
}

/**
* {@inheritDoc}
*/
public function execute($request)
{
/** @var Order $order */
$order = $request->getModel();

$details = new PaymentDetails;
$details['PAYMENTREQUEST_0_AMT'] = $order->getAmount();
$details['PAYMENTREQUEST_0_CURRENCYCODE'] = $order->getCurrency();
$this->paymentDetailsStorage->updateModel($details);

$order->setDetails($details);

$this->payment->execute($request);
}

/**
* {@inheritDoc}
*/
public function supports($request)
{
return
$request instanceof SecuredCaptureRequest &&
$request->getModel() instanceof Order &&
false == $request->getModel()->getDetails()
;
}
}
137 changes: 137 additions & 0 deletions src/Payum/Server/Controller/ApiOrderController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php
namespace Payum\Server\Controller;

use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Registry\RegistryInterface;
use Payum\Core\Request\BinaryMaskStatusRequest;
use Payum\Core\Security\GenericTokenFactoryInterface;
use Payum\Core\Security\HttpRequestVerifierInterface;
use Payum\Server\Model\Order;
use Payum\Server\Request\ProtectedDetailsRequest;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class ApiOrderController
{
/**
* @var GenericTokenFactoryInterface
*/
protected $tokenFactory;

/**
* @var HttpRequestVerifierInterface
*/
protected $httpRequestVerifier;

/**
* @var RegistryInterface
*/
protected $registry;

/**
* @var string
*/
protected $orderClass;

/**
* @param GenericTokenFactoryInterface $tokenFactory
* @param HttpRequestVerifierInterface $httpRequestVerifier
* @param RegistryInterface $registry
* @param string $orderClass
*/
public function __construct(
GenericTokenFactoryInterface $tokenFactory,
HttpRequestVerifierInterface $httpRequestVerifier,
RegistryInterface $registry,
$orderClass
) {
$this->tokenFactory = $tokenFactory;
$this->registry = $registry;
$this->orderClass = $orderClass;
$this->httpRequestVerifier = $httpRequestVerifier;
}

/**
* @param Request $request
*
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
*
* @return JsonResponse
*/
public function createAction(Request $request)
{
if ('json' !== $request->getContentType()) {
throw new BadRequestHttpException('The request content type is invalid.');
}

$rawDetails = json_decode($request->getContent(), true);
if (null === $rawDetails) {
throw new BadRequestHttpException('The request content is not valid json.');
}
if (empty($rawDetails['meta']['name'])) {
throw new BadRequestHttpException('The payment name must be set to meta.name.');
}
$name = $rawDetails['meta']['name'];

if (empty($rawDetails['meta']['purchase_after_url'])) {
throw new BadRequestHttpException('The purchase after url has to be set to meta.purchase_after_url.');
}
$afterUrl = $rawDetails['meta']['purchase_after_url'];

$storage = $this->registry->getStorageForClass($this->orderClass, $name);

/** @var Order $order */
$order = $storage->createModel();
$order->setAmount($rawDetails['amount']);
$order->setCurrency($rawDetails['currency']);

$storage->updateModel($order);

$captureToken = $this->tokenFactory->createCaptureToken($name, $order, $afterUrl);
$getToken = $this->tokenFactory->createToken($name, $order, 'order_get');

$meta = array();
$meta['links'] = array(
'purchase' => null,
'get' => $getToken->getTargetUrl(),
);
$order->setMeta($meta);

$storage->updateModel($order);

$meta = $order->getMeta();
$meta['links']['purchase'] = $captureToken->getTargetUrl();
$order->setMeta($meta);

$response = new JsonResponse(iterator_to_array($order));
$response->headers->set('Cache-Control', 'no-store, no-cache, max-age=0, post-check=0, pre-check=0');
$response->headers->set('Pragma', 'no-cache');

return $response;
}

/**
* @param Request $request
*
* @return JsonResponse
*/
public function getAction(Request $request)
{
$token = $this->httpRequestVerifier->verify($request);

$status = new BinaryMaskStatusRequest($token);
$this->registry->getPayment($token->getPaymentName())->execute($status);

/** @var Order $order */
$order = $status->getModel();
$meta = $order->getMeta();
$meta['status'] = $status->getStatus();
$order->setMeta($meta);

$storage = $this->registry->getStorageForClass($this->orderClass, $token->getPaymentName());
$storage->updateModel($order);

return new JsonResponse(iterator_to_array($order));
}
}
12 changes: 12 additions & 0 deletions src/Payum/Server/ControllerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Payum\Core\Exception\LogicException;
use Payum\Core\Request\InteractiveRequestInterface;
use Payum\Core\Request\RedirectUrlInteractiveRequest;
use Payum\Server\Controller\ApiOrderController;
use Payum\Server\Controller\ApiPaymentController;
use Payum\Server\Controller\IndexController;
use Payum\Server\Controller\NotifyController;
Expand Down Expand Up @@ -33,6 +34,15 @@ public function register(Application $app)
);
});

$app['controller.api_order'] = $app->share(function() use ($app) {
return new ApiOrderController(
$app['payum.security.token_factory'],
$app['payum.security.http_request_verifier'],
$app['payum'],
$app['payum.model.order_class']
);
});

$app['controller.purchase'] = $app->share(function() use ($app) {
return new PurchaseController(
$app['payum.security.token_factory'],
Expand All @@ -53,6 +63,8 @@ public function register(Application $app)
$app->get('/notify/{payum_token}', 'controller.notify:doAction')->bind('notify');
$app->post('/api/payment', 'controller.api_payment:createAction')->bind('payment_create');
$app->get('/api/payment/{payum_token}', 'controller.api_payment:getAction')->bind('payment_get');
$app->post('/api/order', 'controller.api_order:createAction')->bind('order_create');
$app->get('/api/order/{payum_token}', 'controller.api_order:getAction')->bind('order_get');

$app->error(function (\Exception $e, $code) {
if (false == $e instanceof InteractiveRequestInterface) {
Expand Down
Loading

0 comments on commit 76de5c2

Please sign in to comment.