Due to the way strtotime makes calculations, monthly recurrance will not actually be monthly and can skip months if the recurrance starts on the 31st of a month, or the 29th,30th, or 31st if the month happens to be january. This is due to the fact that strtotime will add however many days are in the current month of the given time for "+1 month". So if a customer purchases a monthly subscription on jan 30th, strtotime will add 31 days to that time, making the next recurrance March 1st, which will then become the 1st of the month from then on out, but will skip that initial feb charge. While this will *roughly* equate to monthly, for organizations that book revenue by the month, this can be a particularly sensitive issue, especially if, say converting from one system to another, and doing a massive import of recurring subscribers on the 31st of a month (like we did).
See this note on php.net: http://php.net/manual/en/function.strtotime.php#98878
1128 /**
1129 * Set the intervals after a successful charge.
1130 * @param $fee
1131 * The fee object passed by reference.
1132 */
1133 function uc_recurring_set_intervals(&$fee) {
1134 $fee->next_charge = strtotime('+'. $fee->regular_interval, $fee->next_charge) - $fee->data['extension'];
1135 if ($fee->remaining_intervals > 0) {
1136 $fee->remaining_intervals--;
1137 }
1138 else {
1139 $order = uc_order_load($fee->order_id);
1140 }
1141 $fee->charged_intervals++;
1142 $fee->attempts = 0;
1143 $fee->data['extension'] = 0;
1144 }
Comments
Comment #1
univate commentedInteresting... not sure the best solution here?
Comment #2
tinker commentedPossible solution:
If you want to test:
Comment #3
univate commentedOk, the problem I see here is that then the following month we will start charging on the 28th, instead of the 31st as expected.
Comment #4
EvanDonovan commentedWhat if instead of adding an interval, you were to periodize the recurrence - i.e., if someone purchases on the 1st, then make sure that it will always be re-run on the 1st?
I have some code that might help with that. This was written prior to my use of uc_recurring.module, to help calculate when an order would next be billed by Authorize.net's ARB function. It calculates when the next billing date for an order should be based on the Unix timestamp of the initial order.
The only problem that I think there is with this code is that it doesn't handle leap years, so if someone purchases on Feb. 29th, they will never get billed on Feb. 29th, even if that is possible. Also, since this is a theming function, the current return value is an array of a string and a timestamp.
Comment #5
tinker commented@univate - I understand your concern. The problem is that the module does not store the first recurrence time it only stores the created time and the next_charge time. If you really want it stay on the same day then there are two options:
1 - if recurring billing is restarted or edited overwrite the created time with the new next_charge time
2 - add "activation" time field or store it in serialized in the "data" field
Here is a revised function that takes into account the created date:
Comment #6
univate commentedI haven't really tested any of this, but if i was to commit something like this I would want to make sure that it didn't mess with other recurring intervals, as monthly is not the only supported interval.
Comment #7
jennypanighetti commentedSubscribing.
I too need an accurate monthly recurrence.
Comment #8
tinker commented@univate Could you tell me how the "created date" is used? Would it be OK to use this as the basis for the day of month the billing should occur? This would mean that when the recurrence is edited, the created date would have to be modified, if the day of month changes. Alternatively I could add a value to the "data" field, is that better? I don't see anything else using this value so it would not need a separate field in the table.
The following code change makes sure that only "month" recurrences use the new date calculation so everything would not be affected:
I could make a patch if you give me some direction.
Comment #9
phen commentedFWIW, my 2 cents...
I think the way it works now (as described in the issue summary) is probably better than the proposed solutions, which would result in the billing date skipping around every month. Once it gets to the first or second of the month it should stay there.
I don't know if there is an industry standard --I know my credit card companies all had different schemes for deciding what monthly billing means ... but (FWIW) the way UC_recurring does it matches with the way Paypal does it.
Comment #10
shaundychkoHere's a link directly to the right part of the PayPal page:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=develo...
If PayPal does it this way, then we should probably leave it alone.
Comment #11
tinker commentedBoth ways are the right way it just depends on how the company is keeping its books. If this gets fixed it would allow admin selection to choose which method you want to use.
Comment #12
MixologicBasically there's two, correct logical solutions to this.
While PayPal does do it that way, thats not to say that everybody in the payment industry does it that way or expects it to be that way.
Authorize.net, for one says the following:
So this is one of those things that could go either way. On one hand, having an admin selection choose which method you want to use would allow you to set it how you think it should work, on the other hand, thats one more thing to configure, adding more complexity to the system. Im more inclined to say this is a documentation issue, and that when selecting monthly recurrence it should specify the current behavior