Using Drupal Commerce for node subscription - part III: Paypal subscription and recurring payments
Submitted by Shushu on Wed, 01/23/2013 - 16:15
In part III we described the creation of a cart with a subscription information in it (the product with the Interval field).
Now the conclusion: how we make it till automatic recurring payment - the golden golden goal for any e-Commerce site.
Copy-paste is the best, you just need to know what you are looking for
We left the user redirected to the checkout, and Paypal is the only payment method we enabled for him.
(Why not to pass via the cart ? Since not really needed in our case)
Pressing on the "checkout with Paypal" button, the user will be redirected to Paypal for the payment. This is done internally in the Commerce Paypal by creating a form and submitting it to the Paypal servers.
(Here comes the "why I like Drupal so much, why the hooks mechanism is so powerful and why only after you learn how to use it the concepts of AOP really becomes a reality...).
Commerce subscription products PayPal do the job of making a payment a subscription one, but it do it somehow hardcoded. But copy-paste the right hook_form_FORM_ID_alter, and you get to nicely change what you need in this form object. But well, we don't want to make anything hard-coded, we want flexibility and modularity - let's make a Rules event out of it !
In this new module (again, writing real code! or at least copy-paste it...) we created a "Before user redirect to paypal for checkout" event, and we trigger it via the form_alter we found out, passing the form object.
It could have been simple, but a form is not a reference, and Rules don't like non-references.
In the great world of open soruce and Drupal, all you need to do is to find who to copy from, and looking around we found the Form Rules support, which ahd to solve this problem already.
Just take the form, create an ArrayObject out of it, call_user_func_array to execute the rules event, and in the end - recreate the form array (code can be seen in the module).
So we have the form data in a Rules event. Now we need to do something with it. Here we copy-paste from the Commerce subscription products PayPal, and just add several other parameters to the action. The new action "Add subscription information" gets the form and the needed values we provide in the rule, make the changes in the form submitted to Paypal, and this way we get to what we wanted - a subscription product !
Well done, now what ?
Paypal interaction with any site is done via their IPN. If you want you can dive into it (and you might not come back...).
What we need is to finetune Drupal, so it will know to handle the IPNs the way we want. the Commerce Paypal module already do almost everything, and provides the hook_commerce_paypal_ipn_process for us to build on. All needed was to take the IPN values and send them into a new event - "Process Paypal IPN", where we can build a rule to do whatever we want.
There are many different situations when handling IPNs, and the txn_types can provide different situations. Now, with the event, you as the site builder can define any logic you want from it.
We, for example, decided not to automatically disable user subscription. Instead, at least till it will become too much of a work, we send an email both to the user notifying him nicely about the problem, and to a site maintainer which will handle it one-on-one with the user.
The automatic task that we do need to handle is payment - when the txn_type is subscr_payment, we should first create a new order with the details, and second update the relevant product with its new expiration date.
The second task is more or less easy.
The first task wasn't out-of-the-box, so we again took code from the existing commerce_sp_paypal module, and created the "Clone order" Rules action, which takes the original order provided by the IPN, and create a new order to work with.
It was a long development week, jumping into the world of recurring payment mechanism and existing Drupal Commerce world, but in the end it was mainly a project done the way we think it should be:
- Understand the requirements
- Look for existing solutions, use them as much as possible
- Where not possible, define the basic building blocks needed
- Use existing code examples and knowledge to build the building blocks
- Re-build the solution based on your requirements using the new blocks
And the most important - release the building blocks back to the community, even if you don't have the time for that !
Hope this 3 parts article will be useful for someone.