I'm in the process of adding support for recurring fees to the uc_moneris module. I've been spending a lot of time looking at code since the documentation is lacking on the uc_recurring module, and there has been rather dramatic changes in the 2.x version of the module. I'd like to work on laying out the process so we can perhaps determine some best practices.
I'm working with 2.x here. The method of processing a recurring payment, as I understand:
1. uc_recurring_order code runs when the user hits 'Submit' on an order, via hook_order. At this point, individual instances of recurring fees are found in the order, and uc_recurring_process is run on each fee instance. The arguments are the order and the individual fee to process.
2. uc_recurring_process searches for a payment handler function. One change from 1.x to 2.x is that the handler is not specified explicitly but instead relies on the chosen payment method to support it. It searches for the function 'uc_recurring_[payment_method]_fee', and it takes the order and individual fee as arguments.
3. This function, uc_recurring_moneris_fee in my case, actually processes the fee, carrying out the transaction with the payment provider. While it would probably share a good deal of code with your payment processor's main processing function (uc_moneris_charge in my case) it seems to make most sense to create a different function specifically for processing recurring transactions. It improves readability, for one thing.
4. After the fee has been processed, successfully or not, the uc_recurring_process function loops through the same process for each of the other fees. If successful, each recurring fee instance is saved using the uc_recurring_fee_save function, which in turn saves this information to the database. One concerning aspect of this is that it, by default, will save the credit card information (encrypted) in the database, presumably for reference down the road. You can avoid this by using pass-by-reference on the fee argument in your custom recurring fee processing function and modifying the $data array so it doesn't save it.
Speaking of down-the-road, the other important aspect of the payment processor is handling renewals - that is, the payments that are made after each interval. Moneris handles this all internally after the initial transaction, so it's not necessary to do anything when each interval elapses. It is possible to process each payment as an regular purchase transaction at this point, if your payment process does not support recurring transactions. This is what the default recurring fee handler in 1.x does, I believe. In this case you would need to store the CC information in the database.
In order to update or terminate a recurring payment set up with Moneris, one only needs the (Moneris, not Ubercart) order number. It is not necessary to store the credit card information, since transactions are going through automatically. To make PCI compliance easier, you should avoid storing this CC information in your database after the authorization - certainly that is something Ubercart avoids for normal transactions.
Renewal transactions are handled by the uc_recurring_[payment_method]_renew function. In my case, this function won't do anything.
I'll try and update this as I move forward with coding. Perhaps one of the maintainers could review and correct me, and eventually turn this into a documentation page or something.
Comments
Comment #1
univate commented1. yes
2. yes
3. yes - no problem sharing code with standard charge function if you can, although most gateways often require you to send a different message on a recurring payment to a normal one off payment.
4. uc_recurring doesn't save any credit card details to the database, unless the gateway actually adds this data to be saved in the fee->data[] variable (which it shouldn't, it should just save an id or something that can be referred back to later)
You will need to implement the "uc_recurring_[fee_handler]_renew()" function even if the gateway takes care of everything, in this case you should just return TRUE - is there a way you can check that a renewal has been processed, as that is what you can do in the renew function instead and save a payment record if it has.
I just posted some template code here - #459052: eWay recurring payment support I have just updated to make it generic (so we can put it in some docs later), it described the 3 functions you need to implement.
Comment #2
chiddicks commentedI really appreciate the quick reply. I'm making some progress, but I have a question.
The payment method id (example credit, check, cod) is actually what you need to create the function with, based on looking at the code and testing, not the payment gateway id (example moneris, authorizenet) as I assumed initially.
So, my function is called uc_recurring_credit_fee based on the uc_recurring_[payment_method]_fee naming convention. Otherwise it doesn't work. It would be the same for every credit card payment gateway, leading to the uncomfortable possibility of multiple gateways being enabled and having the same function name defined. What would be ideal in my view would be to check the default gateway for each method, if it exists, and to use it in the naming convention rather than the method itself.
Am I off base, or should I tackle making a patch to uc_recurring?
Comment #3
univate commentedNo your correct, but in the patch for ubercart here - #426466: Treat recurring fees as new orders - includes a wrapper function for uc_credit that attempts to calls the default credit card gateway that has been setup:
ie:
This is why the the second and third function above refer to [fee_handler] and not [payment_method], when you set up the recurring fee it will continue to use the same fee_handler for ever. But you still have the ability to swap credit gateways for a new one and the old recurring payments will continue to just be processed by the same fee_handler that they where setup with.
Comment #4
univate commented