Mastering PHP Payments with Omnipay

A presentation at ConFoo Montreal in March 2017 in Montreal, QC, Canada by Drew McLellan

Slide 1

Slide 1

PHP Payments with Omnipay

CONFOO MONTREAL 2017

Slide 2

Slide 2

Hello! I’m Drew McLellan. Lead dev on Perch CMS. @drewm
github.com/drewm

Slide 3

Slide 3

Ecommerce sucks.

Nasty bespoke checkout methods. Promotions and shipping and carts. OMG taxes! Payment gateways really suck!

Slide 4

Slide 4

Payment gateways suck.

Slide 5

Slide 5

Payment gateways

  • Each gateway has its own requirements.
  • Most are badly designed.
  • Most are poorly documented.
  • Most have horrible SDKs.
  • All are idiosyncratic in some way.

Slide 6

Slide 6

Payment gateways

We all end up building solutions that are tightly coupled to a given gateway’s solution.

This makes it really hard to change gateway, to move code from project to project, or add additional payment options.

Slide 7

Slide 7

Who changes payment gateway anyway?

Slide 8

Slide 8

Case study: me

  • 2009: Launched Perch CMS on PayPal
  • 2010: Switched to PayPoint.net with PayPal option
  • 2011: Added our own PayPal integration back
  • 2012: Switched to SagePay + PayPal
  • 2014: Switched to Stripe + PayPal

Slide 9

Slide 9

… and then we built an ecommerce product

Slide 10

Slide 10

Shop add-on for Perch CMS

Slide 11

Slide 11

Perch Shop

We wanted to support as many payment gateways globally as we could.

We didn’t want to support any gateways, really.

An abstraction layer sounded like a great idea.

Slide 12

Slide 12

Enter Omnipay.

omnipay.thephpleague.com

Slide 13

Slide 13

Omnipay

Omnipay is a payment processing library for PHP.

It acts as an abstraction layer between your code and the implementation details of using a payment gateway API.

It has drivers for many different gateways.

Slide 14

Slide 14

Omnipay will fix your payment gateway problems like PDO fixes your MySQL problems.

Slide 15

Slide 15

PDO for Payments

Omnipay gives you a consistent API across different implementations.

That makes it easy to move code from project to project, and means less code needs to be changed if the underlying gateway changes.

Slide 16

Slide 16

PDO for Payments

Omnipay won’t make your MSSQL queries run on Postgres. (So to speak.)

Different gateways still have different process flows, and different weird requirements.

Omnipay just eases some of the pain and unifies the interface.

Slide 17

Slide 17

Gateway support

Slide 18

Slide 18

Gateway drivers Payment gateways are supported by drivers - a basic Adaptor pattern. Omnipay core provides the framework. Each gateway then has its own driver. There are o ffi cial, third party and then custom gateway drivers.

Slide 19

Slide 19

O ffi cial Gateways 2Checkout
Authorize.Net
Buckaroo
CardSave
Coinbase
Dummy
eWAY
First Data
GoCardless
Manual
Migs
Mollie
MultiSafepay
Netaxept (BBS)
Netbanx
PayFast
Pay fl ow
PaymentExpress (DPS) PayPal
Pin Payments
Sage Pay
SecurePay
Stripe
Ta r g e t P a y
WorldPay

Slide 20

Slide 20

Third-party gateways Agms Alipay Barclays ePDQ CardGate Cybersource Cybersource SOAP DataCash ecoPayz Fasapay Fat Zebra Globalcloudpay Helcim Neteller Network Merchants Inc. (NMI) Pacnet PaymentSense PayPro PayU Realex SecPay Sisow Skrill Wirecard

Slide 21

Slide 21

Let’s take a look.

Slide 22

Slide 22

Set up the gateway

Calling Omnipay::create() instantiates a new gateway object.

To make that gateway object useful, we need to set the security credentials. For Stripe, that’s an API key.

Other gateways have different credentials that need to be set.

Slide 23

Slide 23

Make a card payment

The gateway’s purchase() method takes an amount, a currency and details of the payment card. This can be literal card details as shown, but is often a card token. After detailing the purchase, the send() method sends the message to the gateway.

Slide 24

Slide 24

Make a card payment

For token payments (like when using stripe.js) you can pass in a token instead of a card.

Slide 25

Slide 25

Payment response

The response has an isSuccessful() method to check for success.

Slide 26

Slide 26

Redirects

Many gateways respond to a payment request with a URL to send the customer to. This is often the case for payment fl ows where the customer gives their card details direct to the gateway and not the merchant site.

Slide 27

Slide 27

Payment response

The response has an isSuccessful() method to check for success. Some gateways take payment o ff -site. Those will test true for isRedirect(). If neither is the case, the payment failed.

Slide 28

Slide 28

Redirects

After redirection, the gateway will usually make a call back to your code to indicate whether the transaction was successful or not.

Slide 29

Slide 29

Complete after redirect

$gateway->completePurchase([
	'amount' => '10.00', 
	'currency' => 'USD', 
	'transactionId' => '1234' 
])->send(); 

When returning from an off - site gateway, you need to complete the purchase using the same options. Some gateways validate options to make sure the transaction hasn’t been messed with.

Slide 30

Slide 30

Options

Slide 31

Slide 31

Options Most actions involve an $options array. It’s often quite hard to fi gure out what should be in it, as every gateway expects something di ff erent. There are a few common options, however.

Slide 32

Slide 32

Options card token amount currency description transactionId clientIp returnUrl cancelUrl

Slide 33

Slide 33

Setting options $response

=

$gateway -> purchase ([

'amount'

=>

'10.00' ,

'currency'

=>

'USD' ,

'card'

=> [ ... ],

'description'

=>

'Event tickets' ,

'transactionId'

=>

$order -> id ,

'clientIp'

=>

$_SERVER [ 'REMOTE_ADDR' ],

'returnUrl'

=>

‘https://.../complete-payment/' ,

'cancelUrl'

=>

‘https://.../failed-payment/'

]) -> send (); Options are passed into most Omnipay action methods as an associative array.

Slide 34

Slide 34

Cards firstName lastName number expiryMonth expiryYear startMonth startYear cvv issueNumber type billingAddress1 billingAddress2 billingCity billingPostcode billingState billingCountry billingPhone shippingAddress1 shippingAddress2 shippingCity shippingPostcode shippingState shippingCountry shippingPhone company email

Slide 35

Slide 35

Yay abstraction! billingAddress1 ==> adrStreet

Slide 36

Slide 36

What can we do?

Slide 37

Slide 37

Types of transaction Authorize (and then capture) Purchase Refund Void

Slide 38

Slide 38

Authorize gateway

= Omnipay :: create ( 'Stripe' ); $gateway -> setApiKey ( 'abc123' ); $response

=

$gateway -> authorize ([

'amount'

=>

'10.00' ,

'currency'

=>

'USD' ,

'card'

=> [ ... ] ]) -> send (); if ( $response -> isSuccessful ()) {

$transactionId

=

$response -> getTransactionReference ();

$response

=

$gateway -> capture ([

'amount'

=>

'10.00' ,

'currency'

=>

'USD' ,

'transactionId'

=>

$transactionId

]) -> send (); } Authorization is performed with the authorize() method. This enables us to get the transaction reference. When we want to take the money, we use the capture()

method.

Slide 39

Slide 39

Purchase // Send token purchase request $response

=

$gateway -> purchase ([

'amount'

=>

'10.00' ,

'currency'

=>

'USD' ,

'token'

=>

'abcd1234'

]) -> send ();

$transactionId

=

$response -> getTransactionReference (); Very straightforward, as we’ve already seen.

Slide 40

Slide 40

Refund $response

=

$gateway -> refund ([

'amount'

=>

'10.00' ,

'currency'

=>

'USD' ,

'transactionId'

=>

'abc123'

]) -> send (); Transactions can be refunded, although the bounds within this can be performed may depend on the gateway.

Slide 41

Slide 41

Void $response

=

$gateway -> void ([

'amount'

=>

'10.00' ,

'currency'

=>

'USD' ,

'transactionId'

=>

'abc123'

]) -> send (); A transaction can generally only be voided within the fi rst 24 hours.

Slide 42

Slide 42

To ke n b i l l i n g $response

=

$gateway -> createCard ([

'card'

=> [ ... ], ]) -> send (); $cardId

=

$response -> getTransactionReference (); Create, update and delete cards. Creating a card gives you a cardReference which can be used in future transactions.

Slide 43

Slide 43

To ke n b i l l i n g $gateway -> purchase ([

'amount'

=>

'10.00' ,

'cardReference'

=>

'abc123'

]) -> send(); Create, update and delete cards. Creating a card gives you a cardReference which can be used in future transactions.

Slide 44

Slide 44

What can’t we do?

Slide 45

Slide 45

Limitations No recurring billing. Not much of anything else.

Slide 46

Slide 46

e.g. getting location details Omnipay has a fetchTransaction() method which returns details of the transaction. The response is gateway dependant, so may or may not have the information we need. If it doesn’t there may not be an Omnipay method available.

Slide 47

Slide 47

Going out of scope When you need to do something the gateway driver doesn’t provide, things can get messy. You either need to try to extend the driver, or fall back to code outside of Omnipay. If your requirement is common, you might want to submit a patch.

Slide 48

Slide 48

Going out of scope What you’re trying to do might not be a goal for the project. See also: recurring payments.

Slide 49

Slide 49

Contributing

Slide 50

Slide 50

Contributing Gateway drivers are maintained as individual open source projects with their own maintainers. Making a change is as easy as making a Github pull request… which is to say it’s of unknown ease. Could be accepted, or rejected, or ignored. Yay open source.

Slide 51

Slide 51

Contributing You can develop your own gateway driver. There are guidelines to follow if you’d like it to be adopted as o ffi cial. Yay open source.

Slide 52

Slide 52

What’s good?

Slide 53

Slide 53

What’s good Learn one API to use with all providers Write code that can be moved between projects Makes the friction of switching between providers much lower Open source: bene fi t from others’ work Open source: fi x and contribute back when needed

Slide 54

Slide 54

What’s bad?

Slide 55

Slide 55

What’s bad? API is abstracted, but gateway fl ow is not Limited to a lowest common denominator for functionality No recurring payments Open source: gateways are sometimes incomplete Open source: getting PRs accepted can be hit and miss

Slide 56

Slide 56

On balance… Omnipay is a useful library that takes a lot of friction away. Be aware of what problems it isn’t solving for you, and use it for the problems it does solve.

Slide 57

Slide 57

Thanks! @drewm