Straight forward integration of Paymill payments into Symfony applications.
- Plug-and-play credit card form, inspired by Stripe's Checkout (optional)
- High level API to create payments
- Webhooks
- CRUD access to Paymill's API from the command line using Symfony commands
- Support for Paymill's client resources
- Uses Paymill's PHP library under the hood
This bundle uses functionality provided by JMSPaymentCoreBundle which allows you to add new payment backends (e.g. Paypal) with minimum changes to your code. These instructions will also guide you through the installation of that bundle.
Install with composer:
composer require memeoirs/paymill-bundle
Then register the bundles in AppKernel.php
:
// app/AppKernel.php
$bundles = array(
// ...
new JMS\Payment\CoreBundle\JMSPaymentCoreBundle(),
new Memeoirs\PaymillBundle\MemeoirsPaymillBundle(),
// ...
);
Include routing.yml
in your routing file (for webhooks):
// app/config/routing.yml
memeoirs_paymill:
resource: "@MemeoirsPaymillBundle/Resources/config/routing.yml"
JMSPaymentCoreBundle's configuration is as easy as choosing a random secret string which will be used for encrypting data. Note that if you change the secret all data encrypted with the old secret will become unreadable.
jms_payment_core:
secret: somesecret
Finally, you need to specify Paymill's private and public keys. You'll need to create a Paymill account if you don't have one and retrieve it's private and public keys. Refer to Paymill's documentation for information on how to accomplish this.
// app/config.yml
memeoirs_paymill:
api_private_key: paymill_api_private_key
api_public_key: paymill_api_public_key
JMSPaymentCoreBundle needs a few database tables so you'll have to create them. If you want to know more about the data model see JMSPaymentCoreBundle's documentation.
If you're using database migrations, you can create the new tables with following commands:
php app/console doctrine:migrations:diff
php app/console doctrine:migrations:migrate
Or, without migrations:
php app/console doctrine:schema:update
You'll need a new route:
// app/config/routing.yml
checkout:
pattern: /
defaults: { _controller: AcmeDemoBundle:Orders:checkout }
And a controller action to render the form:
namespace Acme\DemoBundle\Controller;
use Acme\DemoBundle\Entity\Order;
use Memeoirs\PaymillBundle\Controller\PaymillController;
class OrdersController extends PaymillController
{
public function checkoutAction ()
{
$em = $this->getDoctrine()->getManager();
// In a real world app, instead of instantiating an Order, you will
// probably retrieve it from the database
$order = new Order;
$order->setAmount(50);
$order->setCurrency('EUR');
$form = $this->getPaymillForm($order->getAmount(), $order->getCurrency());
return $this->render('AcmeDemoBundle::checkout.html.twig', array(
'form' => $form->createView(),
'order' => $order,
));
}
}
The twig template:
// src/Acme/DemoBundle/Resources/views/checkout.html.twig
{{ paymill_initialize(order.amount, order.currency) }}
{# looks better with bootstrap #}
<link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css">
<link rel="stylesheet" type="text/css" href="{{ asset('bundles/memeoirspaymill/css/paymill.css') }}">
{% form_theme form 'MemeoirsPaymillBundle::form.html.twig' %}
<form action="{{ path('checkout', {'id': order.id}) }}"
method="post" autocomplete="on" novalidate class="paymill well">
{{ form_widget(form) }}
<input type="submit" class="btn btn-success"
value="Pay {{ order.amount }} {{ order.currency }}" />
{{ form_errors(form) }}
</form>
paymill_initialize()
renders the Resources/views/init.html.twig template. If you need to change the output of paymill_initialize
you can use your own template:
// app/config/config.yml
memeoirs_paymill:
initialize_template: AcmeDemoBundle::init_paymill.html.twig
When the user clicks the buy button, an Ajax request is made to paymill's servers containing the credit card information. The response to this request is a unique token. The form is then submitted through Ajax, excluding the credit card information but including the token.
You'll handle the form submission in the same controller action that renders the form:
// Acme\DemoBundle\Controller\OrdersController
public function checkoutAction ()
{
// (...)
if ('POST' === $this->getRequest()->getMethod()) {
$form->bind($this->getRequest());
if ($form->isValid()) {
$instruction = $this->createPaymentInstruction($form);
$order->setPaymentInstruction($instruction);
$em->persist($order);
$em->flush($order);
// completePayment triggers a call to Paymill's API that creates the
// the payment. It returns a JSON response that indicates success or
// error. In the case of a successful operation the user will be
// redirected (in javascript) to 'orders_thankyou'.
return $this->completePayment($instruction, 'orders_thankyou', array(
'id' => $order->getId()
));
}
}
return $this->render('AcmeDemoBundle:::checkout.html.twig', array(
'form' => $form->createView(),
'order' => $order,
));
}
Paymill allows you to attach each payment to a certain client. To have this bundle automatically manage clients, you can pass the client information as additional data when creating the form:
// Acme\DemoBundle\Controller\OrdersController
public function checkoutAction ()
{
// ...
$form = $this->getPaymillForm($order->getAmount(), $order->getCurrency(), array(
'client' => array(
'email' => 'user2@example.com',
'description' => 'John Doe',
),
'description' => 'Two baskets of apples'
));
// ...
}
TODO
A webhook is a controller action to which Paymill POSTs events. As of now, this bundle is able to automatically handle notifications for the following event types: transaction.succeeded
and refund.succeeded
.
The only thing you need to do is create a webhook using the provided console command (see the Console section below):
app/console paymill:webhook:create --url=https://myapp.com/paymill/hook \
--event=transaction.succeeded --event=refund.succeeded
Everytime a successful transaction or refund happens, Paymill will post a request to the URL you provided, which maps to the MemeoirsPaymillBundle:Webhooks:hook controller action (make sure you included routing.yml in your routing file).
Currently only webhooks are supported
The console commands give you CRUD access to Paymill's API from the command line.
The paymill:webhook:list
command retrieves the list of the most recent webhooks:
app/console paymill:webhook:list
You can filter and paginate the results using a set of filters formatted as a HTTP query string. See here for the list of all available filters. To retrieve the second page of results ordered chronologically:
app/console paymill:webhook:list "count=10&offset=10&order=created_at_asc"
The paymill:webhook:create
command creates a new URL or Email webhook. For more information about webhooks see Paymill's API documentation.
To create a URL webhook specify the --url
option:
app/console paymill:webhook:create --url=https://myapp.com/some-paymil-webhook
If instead you wish to create an Email webhook specify the --email
option:
app/console paymill:webhook:create --email=payment@example.com
You can specifiy the events that trigger this webhook using multiple --event
options. If no --event
option is used, all events will be subescribed to. See here for the list of available event types.
app/console paymill:webhook:create --url=... --event=transaction.succeeded --event=refund.succeeded
To create an inactive webhook use the --disable
option:
app/console paymill:webhook:create --url=... --disable
The paymill:webhook:delete
command deletes webhooks. It takes a series of space-separated webhook ids as arguments:
app/console paymill:webhook:delete hook_c945c39154ab3b3e1ef6 hook_b4ae6600de00b9f69afa