Our current non-Drupal site allows the creation of discount codes to redeem against memberships. I'll be using uc_coupon to achieve the same thing on our D6/UC2 site. Can the recurring fees be set to be different from the discounted cost of the initial membership?

What if it's a 100% discount? Would it be best to treat this as a one off non-recurring purchase or can PayPal still be used to create a £0+£20+£20+£20 payment profile?

Happy to fund any work required as solution required in the next few months. Please PM me.

Comments

univate’s picture

Paypal (web payments standard) can handle up to 2 trial amount/intervals before the regular amount is charged on the actually recurring interval.

I haven't tested a free first payment but what you are asking I think I have implemented in the initial work I have done on the patch for paypal for uc_recurring 2.x-dev. The way I have set it up is that the first payment in an recurring order is treated as a trial amount, basically allowing you to purchase a number of items from the shop including a recurring product and it should work with discounts (but needs to be tested).

So you could purchase two items and the first payment would be:
Item 1 $10 per month
Item 2 $40
=======
Total $50 (first payment)

Each month paypal will then charge $10

So that is essentially working now.

dunx’s picture

I guess I'm after what PayPal calls a trial period, but how is this represented in UC using coupons for uc_recurring to handle? uc_recurring would need to know the discounted and normal price to create the necessary payment profile in PayPal. I'd need uc_recurring to bill the following for a 50% trail discount: £10, £20, £20...

The same issue raises it's head if I use uc_userpoints to reduce the initial cost of membership. Again, I might need to create a recurring payment profile of £6.32, £20, £20...

See http://drupal.org/node/480540

But, the whole idea for us using recurring fees for the majority is to retain existing members, getting them to select a discounted initial *and* a full price recurring item is a non-starter for us.

univate’s picture

Yes a discount is the same thing - it's just a different amount for the first payment as the rest of the recurring payments. So if you setup the product with a price of $10 and a recurring amount of $20 - this works fine.

If you are using a coupon module to apply the discount that should also work in theory as it does the same time, just the amount for the first payment is determined dynamically at checkout rather then being stored in the product.

If the first payment is free that also works, although I haven't tested it.

andrewoke’s picture

I was just testing the uc_coupon module at checkout with a coupon code (uc_coupon). Using the patched paypal module and most recent uc_recurring dev it doesn't apply the coupon to the order total at paypal.

The coupon does get applied all the way up to the submit order page, but when you get to paypal the price is whatever price you set for the trial and recurring amount. I haven't checked what's going on as it's not too important to me at the moment, just figured you should know.

Anonymous’s picture

I'm looking for something similar, but more complicated. I'm not sure if it's even possible.

I'd like to create a rewards system for my customers. As they purchase other products, refer other customers, I'd like discount their memberships based on these activities. Is there a way to hand out coupon codes that only apply to a recurring fee, so that the fee payments could look like:

$20 + $20 + $0 + $20 + ... etc.?

shaundychko’s picture

Version: 6.x-2.x-dev » 6.x-2.0-alpha3

I would like to confirm that uc_coupon still doesn't work with recurring fees. It appears to work until you reach the PayPal page, at which point the amount that will be charged is the monthly price entered when configuring the recurring fee feature. This is true regardless of whether the recurring fee is set to the be same as the selling price, or not. While it's possible, as Univate says, to get a price series of $10, $20, $20 using the method in comment #3, it still isn't possible to use coupons. This would be a great feature, particularly if you want to give someone an incentive to sign up as a fan on your facebook page, and get a coupon code as a reward.

Offlein’s picture

I believe Ubercart Coupons still do not work with Recurring as far (as I know). Don't know if there are plans for it or if suggestions are warranted, but it seems like UC_Recurring could hook_form_alter uc_coupon's coupon add form and include, say, a couple options:
1) Whether to allow it to be applied to products with recurring fees and 2) A default handling for how the coupon should work for recurring fees. ("Apply same percentage discount", "Apply same price discount").

Then in the Recurring Fees feature page for a product, if UC_coupon exists, you can override the default handling (per SKU).

univate’s picture

Status: Active » Postponed

Coupons and other discount modules should still work in some form for the original payment, what probably doesn't work is the ability to have coupon effect carry through to recurring payments.

I'm not in favor of interweaving interaction with other modules in this project though - as pointed out this can be achieved with hook_form_alter() etc.... so I would suggest creating a new module to implement this functionality and create the integration between uc_recurring and coupon module(s).

Offlein’s picture

Good idea!

And yes, to clarify - coupons definitely DO still work with the original transaction. Just not on the recurred ones. I wholly advocate someone [...er.. other than me..] developing a joiner module. Maybe we can pay someone to do it.

BlackCatWeb’s picture

Any news on this? This is an important feature.

EvanDonovan’s picture

Title: Recurring fees and uc_coupon » Applying coupon discount from uc_coupon to order total on recurring orders
Version: 6.x-2.0-alpha3 » 6.x-2.x-dev

So it sounds (from #1) that it is possible to have trial amounts for up to two recurrences in PayPal, although I had a bit of trouble understanding from the explanation how one would set that up? Does anyone know whether Authorize.net has a similar feature?

I have received a client request to have 3 months free for a membership as a "trial period" after which they would begin getting charged the full fee. I think that I could accomplish this with uc_coupon + uc_recurring if it was possible to a) set the initial order to be $0 (uc_coupon will handle this), b) set the first 2 recurring orders to be $0 (would need code for uc_recurring).

So I'm judging by this being postponed in #8 that you are not interested in including this in the core uc_recurring 2.x, univate?

It seems that, according to what Ryan Szama says in this forum post http://www.ubercart.org/forum/support/8327/coupons_and_recurring_authorize), it would be possible to implement the functionality I and others need through implementing hook_recurring_fee(), and including fee-altering logic in there. His example code would need to be modified, though, since it does not check which # in the series of recurrences you are on currently (as I would need to), and it explicitly returns a call to the Auth.net ARB handler instead of Auth.net CIM handler. (I wonder if that could be made generic, though.)

mattcasey’s picture

Any news, Evan or anyone else?

EvanDonovan’s picture

Delivery date to client was pushed back so haven't worked on this yet. May have something by early June.

EvanDonovan’s picture

I am currently working on code to add promotional pricing capability to the recurring fee feature for a given product.

This will be in a separate module from the main uc_recurring_product.module, at least as I've coded it.

As stated above, I should be able to share this code by early June.

Note that it will not be an integration with the uc_coupon.module, but the code from Ryan Szama in the post I linked in #11 should work for that use case:

function MODULENAME_recurring_fee($order, $fee) {
  $fee->fee_amount = $order->order_total;
  return uc_authorizenet_arb_create($order, $fee);
}

That would be a custom recurring fee handler that you would define in a custom module, and then select in the feature settings for the recurring fee. You would need the return to be for whichever recurring fee handler you ultimately want to use.

I think alternatively, you could implement it as:

/**
 * Change the recurring fee if a coupon code has been applied to an order.
 *
 * @param $order
 *   The order object.
 * @param $fee
 *   The recurring Fee object.
 */
function MODULENAME_recurring_renewal_pending(&$order, &$fee) {
  $fee->fee_amount = $order->order_total;
}

Note that might cause issues if there were multiple products on the same order, though, since then the recurring fee would be getting increased to the cost of all the products taken together, rather than just the individual product on which the coupon code had been applied. You could probably load the product information from the $fee->original_product_id, though, and re-run the coupon code logic to get it to work properly.

EvanDonovan’s picture

For the client project I mentioned, I've created a "promotional discount" module that lets you define a promotional discount amount which is typically less than the usual recurring order cost, and set how many times to charge the discount amount prior to going to the regular fee amount.

This is not quite the same as uc_coupon integration, since I believe that would require actually applying the coupon discount and recalculating the order total each time. But the code below shows how in uc_recurring 2.0 something like this could be hooked in. (Note that the code from Ryan Szama on the Ubercart forum doesn't appear to work with uc_recurring 2.0).

/** 
 * Implementation of hook_recurring_info_alter, from uc_recurring.module.
 * Changes the renew callback for the CIM recurring fee handler.
 *
 * @param $info
 *   Array of the recurring fee handlers.
 */
function MODULENAME_recurring_info_alter(&$info) {
  if (!empty($info['authorizenet_cim'])) {
    $info['authorizenet_cim']['renew callback'] = 'MODULENAME_promo_cim_renew';
  }
}

/**
 * Custom recurring fee renew callback.
 * Charges the promotional amount if still within the promotional period.
 *
 */ 
function MODULENAME_promo_cim_renew($order, $fee) {
  // clone the fee object, since we don't want the changes passed back into the calling function
  $new_fee = clone $fee;
  // If there is a promotional period on the recurring fee
  $promo = my_recurring_product_promo_load($fee->pfid);
  if(is_object($promo)) {
    // If the charged intervals < the intervals for the promotional period
    if($fee->charged_intervals < $promo->intervals) {
      // Set the fee amount to the promotional amount
      $new_fee->fee_amount = $promo->amount;
      // Set the order amount to the promotional amount
      $order->order_total = $promo->amount;
    }
  }
  // Call the regular CIM recurring fee handler
  return uc_recurring_authorizenet_cim_renew($order, $new_fee);
}
univate’s picture

It should be possible to do this more generally in a module for any payment gateway with the following hook:

From uc_recurring.api.php:

 /**
 * Act on recurring renewal event.
 *
 * @param $order
 * The order object.
 * @param $fee
 * The recurring Fee object.
 */
function hook_recurring_renewal_pending(&$order, &$fee) {
}

There is also a $data variable which can store details like this if you want to add information on a per recurring fee.

EvanDonovan’s picture

@univate: I was using hook_recurring_renewal_pending, but if I changed the $fee->fee_amount it made the fee_amount lower permanently, rather than simply for a single recurrence (as you would need to do with a promotional period). This is because $fee is being passed by reference, and it gets saved back to the database after the recurrence is created. Let me know if I missed something.

univate’s picture

You shouldn't need to change the recurring fee amount, you should be able to just change the $order->order_total as that is the amount that will actually be charged through CIM.

mattcasey’s picture

I am working on a custom solution using the hook in #16, but after some debugging, I found that when UC Recurring calls "$new_order = uc_order_load($new_id);" after the hook runs, Ubercart pulls the total in from the database and overwrites the value set in $order->order_total, without my adjustment.

I am going to try adding a line item instead, because this looks similar to a previous issue that was never fixed in UC: http://drupal.org/node/1021940

[Update]

Adding a line item works using uc_order_line_item_add(). I think this is a better solution, because you can also store a description, and other information about the coupon with the line item.

univate’s picture

Adding a line item is what I would suggest and is also the reason the order object is passed by reference into that hook. That way you can add or removing things from the order.

EvanDonovan’s picture

@univate: Thanks for the info, in re: #18. That sounds like the way to go, then.

lonehorseend’s picture

Subscribing. @mattwad, any progress on your code that you mentioned in #19? Would you mind posting what you have?

mattcasey’s picture

@lonehorseend, my case is kind of exceptional, we want to reward people for inviting new subscribers so I used a modified version of the uc_invite_discount and userpoints modules. Points are awarded when someone you invite makes a purchase on the site, and can also be given by an administrator. The only downside is the points cannot be tracked back to the specific friends you invited.

This example gives a discount depending on how many userpoints you have, and only includes hook_recurring_renewal_pending(). It will not work without further modifying the uc_invite_discount.module, but if there's any interest, I'd be willing to work on and submit a patch to uc_invite_discount

/**
 * Act on recurring renewal event.
 *
 * @param $order
 *   The order object.
 * @param $fee
 *   The recurring Fee object.
 */
function uc_invite_discount_recurring_renewal_pending(&$order, &$fee)
{
	$global_discount = variable_get('uc_invite_discount_value',10);
	$type = variable_get('uc_invite_discount_type','price');
	$order_user = user_load(array('uid' => $order->uid));
	// TODO: Add support for price and percentage discount types
	if($type == 'userpoints')
	{
		$tid = 1; // TODO: Get term ID created automatically by Userpoints
		$userpoints = userpoints_get_current_points($uid, $tid);
		$total_discount = 0;
		$comment = '';
		if(($userpoints * $global_discount) > $order->order_total)
		{
			// This loop makes sure total discount does not to exceed order total
			for($i = 1; $i <= userpoints_get_current_points($uid, $tid); $i++)
			{
				if (($total_discount + $global_discount) > $order->order_total)
				{
					break;
				}
				$params = array
				(
				  'uid' => $order-uid,
				  'points' => -1,
				  'tid' => $tid,
				  'description' => 'Referral discount',
				);
				userpoints_userpointsapi($params);
				$total_discount += $global_discount;
				$comment = ($comment == '') ? t('Thank you discount for referring us') : t('Thank you discount for referring us to ') . $i . t(' customers');
			}
			// Add the total discount as a line item
			uc_order_line_item_add
			(
				$order->order_id, // This is the order ID.
				'uc_invite_sender', // This is the line item ID.
				$comment, // This is the text that will be shown on the order review page to describe what the line item is
				-($total_discount), // This is the value of the line item.
				NULL, // This is the weight of the line item
			);
			watchdog('uc_invite_discount', 'Gave <a href="/user/' . $order->uid . '">' . $order_user->name . '</a> a $' . $total_discount . ' discount on recurring order # <a href="' . $base_path . '/admin/store/orders/' . $order->order_id . '">' . $order->order_id . '</a>');
		}
	}
}
mattcasey’s picture

Here's a stripped-down version of what I'm using now, working with uc_coupon.module. Works using uc_recurring 2.x-dev and uc_coupon 1.7
- create coupons with uc_coupon.module
- coupons are included in renewal orders by selecting the uc_recurring_subscription product type
- works with multiple coupons

/**
 * Act on recurring renewal event.
 *
 * @param $order
 *   The order object.
 * @param $fee
 *   The recurring Fee object.
 */
function mymodule_recurring_renewal_pending(&$order, &$fee) {
	$order_user = user_load(array('uid' => $order->uid));

	while ($row = db_fetch_object(db_query("SELECT * FROM {uc_coupons} WHERE status = 1") {
        	$coupon = uc_coupon_validate($row->code, $order, $order_user);
        	if (!$coupon->valid) {
			continue;
        	}
		if (isset($coupon->data['product_types']['uc_recurring_subscription'])) {
			db_query("INSERT INTO {uc_coupons_orders} (cid, oid, code, value) VALUES (%d, %d, '%s', %f)", $coupon->cid, $order->order_id, $coupon->code, $coupon->amount);
			uc_order_line_item_add($order->order_id, 'coupon', $coupon->title, -$coupon->amount);
		}
	}
}
shaundychko’s picture

Thanks a lot mattcasey for the inspiration, and showing where to look. I couldn't get the code to work since the coupon would never validate. Also, with more than 1000 coupons in the system, loading all codes brings cron to it's knees. The following is my version of applying coupons to recurring fees:

/**
* Act on recurring renewal event.
*
* @param $order
*   The order object.
* @param $fee
*   The recurring Fee object.
*
*   - create coupons with uc_coupon.module
*/
function ucrc_recurring_renewal_pending(&$order, &$fee) {
  $order_user = user_load(array('uid' => $order->uid));

  //proceed if a coupon was used in the original order
  if(!empty($order->data['coupon'])) {
    //uc_coupon_validate returns the message "You have no applicable items in
    //this order", even if the product is added to the order using
    //uc_order_product_save. Using FALSE skips validation of the product.
    $coupon = uc_coupon_validate($order->data['coupon'], FALSE, $order_user);
    //proceed if coupon is valid
    if($coupon->valid === TRUE) {
      //calculate value of the discount. uc_coupon_validate normally does this,
      //but not when called with FALSE instead of an $order object. Using the 
      //$fee object to calculate the discount also has the advantage that the
      //product price can change without affecting a prior subscribers fee.
      $discount = ucrc_calculate_discount($fee, $coupon);
      if($discount) {
         $coupon->amount = $discount;
         db_query("INSERT INTO {uc_coupons_orders} (cid, oid, code, value) VALUES (%d, %d, '%s', %f)",
          $coupon->cid, $order->order_id, $coupon->code, $coupon->amount);
         uc_order_line_item_add($order->order_id, 'coupon', $coupon->title, -$coupon->amount);
      }
    }
  }
}

/**
 * helper function to calculate the discount for a recurring fee
 *
 * @param $fee
 *    the Recurring Fee object
 * @param $coupon
 *   The coupon object
 *
 * See line 710 of uc_coupon.module for other coupon types
 */
function ucrc_calculate_discount($fee, $coupon) {
  switch($coupon->type) {
    case 'percentage':
      $discount = $fee->fee_amount * $coupon->value / 100;
      break;
    default:
      $discount = FALSE;
  }
  return $discount;
}

And the following was added to themename_uc_recurring_fee_table($uid) in template.php

 //display discounts if applicable
    $order_user = user_load($uid);
    $original_order = uc_order_load($fee->order_id);
    if(!empty($original_order->data['coupon'])) {
      $coupon = uc_coupon_validate($original_order->data['coupon'], FALSE, $order_user);
      //proceed if coupon is valid
      if($coupon->valid === TRUE) {
        //calculate value of the discount. uc_coupon_validate normally does this,
        //but not when called with FALSE instead of an $order object. Using the 
        //$fee object to calculate the discount also has the advantage that the
        //product price can change without affecting a prior subscribers fee.
        $discount = number_format(round(ucrc_calculate_discount($fee, $coupon), 2),2);
        if($discount) {
          $rows[] = array(
            'data' => array(
              '0' => array(
                'data' => 'Coupon ' . $coupon->code . ' discount: $' . $discount,
                'colspan' => 7,
              ),
            ),
          );
          $rows[] = array(
            'data' => array(
              '0' => array(
                'data' => 'Next charge: $' . number_format(round($fee->fee_amount - $discount, 2), 2),
                'colspan' => 7,
              ),
            ),
          );
        }
      }
    }