Payments Specification
The Commerce project needs a way of handling payments, but its not the only project that needs to deal with payments. My thoughts are lets design a generic framework for handling payments not just for Commerce, but any other project that want some form of payment handling. A greater goal could be a project we can export to the greater PHP community although I think we should focus on Drupal as a first step.
What is a payment?
A payments can mean different things depending on the context, but in general terms it is money transferring from one party to another. In the context of commerce, the main case that comes to mind is receiving money from purchases, but there are cases where we need to send money as well (e.g. affiliate payments), so we should design a system that is generic enough to take care of all cases.
What is required to make a payment?
A payment has three elements:
* amount (includes details like currency)
* sender
* recipient
In an online store the sender is the shopper/customer and the recepient is the store owner or the merchant account where the money is going. But this could easily be reversed, the website might be the sender and the recepient could be an affiliate. In practical terms the sender and recipient are really the same thing since you can't randomly specify any sender and a recipient, so we need to combine this into the one element which I'll refer to as the 'processor'.
How should this all look to Developers
What do developers really want? They want to be able to ignore the end payment method, they just want to be able to specify an amount, give the customer details and then have that payment occur.
What this might look like in code:
<?php
// create the payment
$payment = new AuthorizeNet(100.00, 'USD');
// sender & recipient: payment occurring via credit card
$payment->setMerchant(array('username' => 'abc', 'password' => 'abc'));
$payment->setCreditCard('4444333322221111', array('month' => '02', 'year' => '12'), '123');
// Then charge it:
$payment->charge();
// In drupal we may want to allow others to hook into all this so we could have various wrapper functions as a way of abstracting various operations.
// e.g:
$payment = payments_create_payment('AuthorizeNet', 100, 'USD');
$payment->setCreditCard('4444333322221111', array('month' => '02', 'year' => '12'), '123');
payments_charge_payment($payment);
?>
Whats under the hood, how do we create new payment methods/gateways
In ubercart there was two concepts to deal with 'payment methods' and 'payment gateways'. What I propose is having a base payment class instead that is extendable for payment gateways to implement there own methods to perform the generic functions all payments require e.g. the ability to charge a payment.
So..
<?php
class Payment {
public $amount = 0;
public $currency = 'USD';
public __construct($amount = 0, $currency = 'USD') {
$this->amount = $amount;
$this->currency = $currency;
}
public function authorize() {}
public function capture() {}
public function refund() {}
}
class AuthorizeNet extends Payment {
public __construct($amount = 0, $currency = 'USD') {
parent::__construct($amount, $currency);
}
public function authorize() {
parent::authorize()
// other stuff
}
public function capture() {
parent::capture()
// other stuff
}
// etc...
}
?>
Getting into more advanced payment functionality
One of my motivations is making recurring payments work better. This structure will make adding features like recurring functionality into a payment gateway easier.
Lets take our Authorize.net gateway again, its supports a feature call CIM (customer information profiles - where the gateway will store credit card details for you).
PHP5 has a concept called interfaces that allow you to force a class to include specific functions and we can check for this interfaces at a later time when trying to decide whether a feature is supported in a gateway.
<?php
interface StoredProfile {
public function createProfile();
}
class AuthorizeNetCIM extends AuthorizeNet implements StoredProfile {
public $profile = NULL;
public __construct($amount = 0, $currency = 'USD') {
parent::__construct($amount, $currency);
}
public function capture() {
parent::capture();
if (isset($this->profile)) {
// don't need credit card details we can use the cim profile
}
else {
// we can still try and use the credit card details if available from the parent class
return parent::capture();
}
}
// Add the interface function(s) expected for the StoredProfile feature to work
public function createProfile() {
// do stuff
}
}
?>
So to use the CIM functionality we don't really need to know anything about CIM, the only difference is the sender details we specify is an ID instead of the credit card details.
<?php
// first create the stored profile
$payment = payments_create_payment('AuthorizeNetCIM');
$payment->setMerchant(array('username' => 'abc', 'password' => 'abc'));
$payment->setCreditCard('4444333322221111', array('month' => '02', 'year' => '12'), '123');
$payment->createProfile();
// then at a later date when we want to charge a payment to the stored profile
$payment = payments_create_payment('AuthorizeNetCIM', 100.00, 'USD');
// sender & recipient: payment occurring via a CIM profile this time
$payment->setMerchant(array('username' => 'abc', 'password' => 'abc'));
$payment->profile = '123456789';
// Then charge it:
$payment->charge();
?>
In our code where we want to use stored profiles (e.g. a quick checkout feature), we don't need to know or care what gateway is being using if we find a previous payment which implements the StoredProfile interface then we can just charge the payment to that:
<?php
$payment = payment_load_payment($id);
if ($payment instanceof(StoredProfile)) {
$payment->setAmount(100, 'USD');
$payment->charge();
}
?>
This functionality is not just useful for recurring payments, there are lots of other examples - mass payments is a feature paypal (and possibly others) support which give you the ability to send lots of payments in one batch operation. I can see this as a feature that an affiliate system would like to have where they can pay all their affiliates without really caring about the payment specific stuff. They just supply a list of recipient account details and the payment processor takes that list and pays everyone.
Comments
+10
+10
+10 also! Do post a github
+10 also! Do post a github URL if you start on this, would love to follow!
Definitely the way to go,
Definitely the way to go, this is a great start.
The current proposal needs work - there needs to be a little OO design introduced. For example, you shouldn't have to explicitly instantiate processor-specific classes. When you say
$payment = new AuthorizeNet(100.00, 'USD');
you have inextricably linked your application code to a specific processor. Instead, the AbstractFactory design pattern should be used. PaymentProcessor could be an interface, and the factory could return concrete implementation classes that implement that interface. The specific implementation, e.g. AuthorizeNetPaymentProcessor, would be returned from the factory and would be unknown to the code using the payment API.So I think the first thing that needs to be done is to agree on an interface for PaymentProcessor that provides all the API methods needed for charging, refunding, etc. IMO this interface should also define methods needed for recurring payments (more about this below). Then we need an abstract class AbstractPaymentProcessor that implements PaymentProcessor and provides functionality needed by all processors, such as accessor/mutators for protected members, e.g. your example of setCreditCard(). Then we need some concrete subclasses of AbstractPaymentProcessor which provide processor-specific implementations, e.g. AuthorizeNetPaymentProcessor. Then we need the abstract factory implementation to instantiate singletons of a processor-specific concrete class.
The design will have some things in common with DBTNG. In DBTNG, the DB is abstracted out so that the application does not need to know either the specific database implementation being used (MySQL v. PostgreSQL v. SQLite, for instance), nor does the application need to know the *capabilities* of the DB - the DBTNG API provides a compatibility layer on top of the DB so that the application is guaranteed the functionality whether or not that functionality is natively implemented by the DB.
In much the same way, we will want to guarantee, for example, recurring payment compatibility whether or not the specific processor has a recurring payment API. I know this is something that has been talked about before - this is the place to add it.
Likewise, the methods defined by the PaymentProcessor interface should be chainable, so that you can do something like
$payment = $processor->createPayment()->setMerchant('xxx')->setCreditCard('xxx')->charge();
(to use your example). This requires that functions like "charge()" return "this" PaymentProcessor object. This is why specifying and documenting the interface should probably be the first step - it forces us to think about the use cases for a PaymentProcessor, what the method signatures should be, and where the division of responsibility will be between the processor-specific implementation classes and drupalcommerce.Also, I would argue against providing a procedural wrapper layer for the Payments API such as in the example you give above with
payments_charge_payment($payment);
.This is only part of the OO design for a complete payments API. There are many more issues to think about.
Thanks for the feedback and
Thanks for the feedback and thoughts.
I agree with you, that your application specific code would not normally instantiate a "new AuthorizeNet()" object, this would generally happen in the payment api layer.
I also agree that payments interface can take design ideas from the DBTNG, but there are some distinct difference between DB's and Payment processing functionality.
1) There is only likely to be a few different DB supported in Drupal, there are hundreds if not thousands of possible payment methods & gateways that may need to be developed - so implementing new payment gateways should ideally be as simple as possible and avoid unnecessary complexity
2) From an applications viewpoint DB's are not really that different in terms of their features, they just have a bunch of CRUD operations (ie: they all have create, read, update, delete operations), their complexity is more in their implementations instead. Payment functionality though are all different, some require credit card details, other a paypal email, others a debit card, others a mobile number etc... Some allow you to refund payments, others support recurring payments, others have batch payment functions. I'm not sure how we can implement a refund function if its not actually supported by the payment gateway (how would that actually work?)
This is why instead I would propose using interfaces that payment gateways need to implement by adding them to their declaration to clearly state the features that are supported. That way we can actually see in our application code if its possible to use a feature rather then trying to process a refund and having to deal with errors.
Having just finished writing
Having just finished writing a Quickbooks payment gateway module for Ubercart, I have to agree with univate on this. Keep the process of creating a new gateway as simple as possible.
Multiple payment gateways
I'd just like to add another thought.
Our bugbear has been that the web is international: so we can reasonably expect people to buy from say the USA, despite the fact that we are mostly based in the UK. Before Commerce there was no way to properly have prices in different currencies for the same product. Now we can!
There's one more twist tho. People located in the USA want to pay in dollars, and that's best going into an account that deals in dollars. People in the UK want to pay in GBP, which is best going into an account in GBP. Ditto Euros. So - we need payment gateways which are linked to currency. When there's a suitable gateway, then that should be at the top of the list that is shown to buyers. If there's not a currency specific gateway, then it should be handled by the default gateway, and there should be a message saying that the bill will appear on the statement with a conversion factor applied, to make up for the currency difference/change.
Good thoughts - I think we
Good thoughts - I think we can accomplish this with a simple currency condition in Rules, but will have to monitor it as we develop the payment system further.
Using Drupal with other processors?
A friend of mine told me that he read somewhere that using drupal and ubercart with anything else than authorise or paypal was a really bad idea and it was open to fraud and stuff. Is that really so? Or is it something which was a problem in the past and has now changed? I'm asking because I got a really nice deal from one company and I'd be happy to switch.
Cheers
J
There's nothing inherently
There's nothing inherently more secure with Authorize.Net / PayPal than other providers... it could just be he said that because I was maintaining those modules as part of Ubercart core. I'm doing the same for Drupal Commerce w/ http://drupal.org/project/commerce_paypal and http://drupal.org/project/commerce_authnet - so that same metric might still hold water. However, many other solid developers are taking on the other gateways based on these modules, so they should end up being just as secure. You can always get someone to do an audit for you if you don't know whether to trust a module or not. As long as they're integrating properly w/ the Payment API, there's no danger of card data loss.