. July 2006 * This is a Drupal 4.7 module to handle PayPal subscriptions. * * It requires the lm_paypal module to be installed, enabled and configured. * * This module is licensed under Gnu General Public License Version 2 * see the LICENSE.txt file for more details. */ //TODO: // Admin option to add a user to a sub with an auto unsub at end of period // probably best to do this with an option under edit user otherwise hard to // find the user to add // Add a subscr_modify based option to create buttons to extend a subscription // find a way to export all subscriptions from paypal and consolidate them // against those in this module // change all the output blocks so they can be themed more easily // go thru all the fields in the ips table and see what is actually needed // On deleting a user issue a cancel on any subscriptions - can't do that from // here, can only cancel on PayPal define(LM_PAYPAL_SUBSCRIPTIONS, 'LM_PayPal_Subs'); // Don't change these here! Use the admin interface at admin/lm_paypal_subscriptions define(LM_PAYPAL_SUBSCRIPTIONS_INPROGRESS_DEFAULT, '/lm_paypal/subscriptions_inprogress'); define(LM_PAYPAL_SUBSCRIPTIONS_MENU_REBUILD_DEFAULT, FALSE); define(LM_PAYPAL_SUBSCRIPTIONS_UID_ADMIN_DEFAULT, 1); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONSUB_DEFAULT, 1); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONSUB_SUBJECT, 'New subscriber %Username to %Subscription%Node on %Site'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONSUB_BODY, 'The user %Username has subscribed to %Subscription%Node on %Site on %Date'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONEND_DEFAULT, 1); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONEND_SUBJECT, 'User %Username leaves %Subscription%Node on %Site'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONEND_BODY, 'The user %Username has ended their subscription to %Subscription%Node on %Site on %Date by %End'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONSUB_DEFAULT, 0); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONSUB_SUBJECT_DEFAULT, 'Welcome to %Subscription%Node'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONSUB_BODY_DEFAULT, 'Hello %Username and welcome to your new subscription %Subscription%Node on %Site'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONNEAREND_DEFAULT, 0); define(LM_PAYPAL_SUBSCRIPTIONS_NEAREND_DAYS_DEFAULT, 5); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONNEAREND_SUBJECT_DEFAULT, 'Your subscription %Subscription ends soon'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONNEAREND_BODY_DEFAULT, 'Hello %Username your subscription to %Subscription on %Site ends in %Days days.'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONEND_DEFAULT, 0); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONEND_SUBJECT_DEFAULT, 'Goodbye from %Subscription'); define(LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONEND_BODY_DEFAULT, 'Hello %Username and thank you for being a subscriber to %Subscription on %Site. We hope you will join us again.'); define(LM_PAYPAL_SUBSCRIPTIONS_TERMS_DEFAULT, ''); // In theory a subscription will go: // signedup, live, EOT (end of term) or cancelled // however sometimes (often) a subscr_payment will arrive before a subscr_signup // so then you'll get paid, live... instead // If the incoming subscription details dont match those expected it will // be blocked // If the subscription amount is zero (probably for a trial period) then // no payment will arrive define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_DEAD, 0); define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_LIVE, 1); // dont change this one! define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_SIGNEDUP, 2); define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_PAID, 3); define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_CANCELLED, 4); define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_EOT, 5); define(LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED, 6); /** * Initialize global variables. */ function _lm_paypal_subscriptions_ini() { _lm_paypal_ini(); global $_lm_paypal_subscriptions_inprogress; // page user is directed to on subscribe // In theory this shouldn't be necessary but I've had one user require it. global $_lm_paypal_subscriptions_menu_rebuild; // rebuild menu cache on role or group change // Status of subscribers global $_lm_paypal_subscriptions_stati; static $inited = 0; if ($inited) { return; } $inited = 1; $_lm_paypal_subscriptions_inprogress = variable_get('lm_paypal_subscriptions_inprogress', LM_PAYPAL_SUBSCRIPTIONS_INPROGRESS_DEFAULT); $_lm_paypal_subscriptions_menu_rebuild = variable_get('lm_paypal_subscriptions_menu_rebuild', LM_PAYPAL_SUBSCRIPTIONS_MENU_REBUILD_DEFAULT); $_lm_paypal_subscriptions_stati = array( 0 => t('dead'), 1 => t('live'), 2 => t('signedup'), 3 => t('paid'), 4 => t('cancelled'), 5 => t('eot'), 6 => t('blocked'), ); } /** * Implementation of hook_help(). */ function lm_paypal_subscriptions_help($section) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_welcome; // Welcome message global $_lm_paypal_drupal_major; global $base_url; $groups = l(t('create content > group'), 'node/add/og'); $subs = l(t('LM PayPal Subscriptions'), 'admin/lm_paypal/subscriptions'); if ($_lm_paypal_drupal_major > 4) { // Drupal 5 $c = '!'; // t() has changed and to use a link in it use '!' $admin = l('LM PayPal Admin', 'admin/lm_paypal/settings'); $roles = l(t('user management > roles'), 'admin/user/roles'); $access = l(t('user management > access control'), 'admin/user/access'); $blocks = l(t('site building > blocks'), 'admin/build/block'); } else { // Drupal 4 $c = '%'; $admin = l('LM PayPal Admin', 'admin/settings/lm_paypal'); $roles = l(t('access control > roles'), 'admin/access/roles'); $access = l(t('access control'), 'admin/access'); $blocks = l(t('administer blocks'), 'admin/block'); } $help_subs = l(t('LM PayPal Subscriptions Help'), 'admin/help/lm_paypal_subscriptions'); switch ($section) { // admin/help/lm_paypal_subscriptions case 'admin/help#lm_paypal_subscriptions': $output = $_lm_paypal_welcome; $output .= '
'. t('Special Notes') . ':'; $output .= '
' . t('If you are new to this module you need to:'); $output .= '
' . t("For each role or group subscription defined a subscribe block becomes automatically available. These can be configured via ${c}blocks", array("${c}blocks" => $blocks)) . '
'; $output .= '' . t('Special note:') . ' ' . t('subscription blocks are only displayed to logged in users who do not already have that subscription.') . '
'; $output .= ''. t("The following PHP snippet shows how to make a subscribe button appear for the subscription with a subid of 1. Admin's can find the subid via ${c}subs", array("${c}subs" => $subs)) . '
'; $output .= ''. t('Note that nothing will appear if they are either not able to subscribe (not logged in) or if they have already subscribed:') .'
'; $output .= ''. t('<?php
if (function_exists(\'lm_paypal_can_subscribe\')) {
$subid = 1;
if (lm_paypal_can_subscribe ($subid)) {
print \'Why not subscribe now? \' . lm_paypal_subscribe($subid,8);
}
}
?>') .'';
$output .= ''. t('It is best to check that the lm_paypal_can_subscribe function exists before using it just in case the module has been disabled.') . '
'; $output .= ''. t("To view all subscriptions and everyone who has subscribed to them use ${c}subs", array("${c}subs" => $subs)) . '
'; return $output; // This is the brief description of the module displayed on the modules page case 'admin/modules#description': return t('Provides PayPal subscriptions to Drupal roles (requires lm_paypal).'); // This appears at the start of the module admin page case 'admin/settings/lm_paypal_subscriptions': // The admin page under Drupal 5 case 'admin/lm_paypal/subscriptions_settings': // This appears at the start of the admin page case 'admin/lm_paypal/subscriptions': // This appears at the start of the new subscription page case 'admin/lm_paypal/subscriptions/new': $output = $_lm_paypal_welcome; $output .= ''. t("For detailed help please read ${c}help_subs", array("${c}help_subs" => $help_subs)) .'
'; return $output; // This appears at the start of the subscriptions user page case 'lm_paypal/subscribe': $output .= ''. t('The following lists all the subcriptions available via PayPal on this system. To take out one of these subscriptions you will need a login on this web site and a PayPal account. If you do not already have a PayPal account then PayPal will show you how to get an account when you subscribe.') .'
'; return $output; } } /** * Implementation of hook_perm(). * Return a list of the access control permissions that this module defines */ function lm_paypal_subscriptions_perm() { return array('access lm_paypal_subscribe'); } /** * Implementation of hook_menu(). */ function lm_paypal_subscriptions_menu($may_cache) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_drupal_major; $items = array(); if ($may_cache) { // TODO: For Drupal 5 put all the subscription admin menu entries into // their own section if ($_lm_paypal_drupal_major > 4) { // New to Drupal 5 - hook_settings gone so settings is a normal page $items[] = array( 'path' => 'admin/lm_paypal/subscriptions_settings', 'title' => t('LM PayPal Subscriptions Settings'), 'callback' => 'drupal_get_form', 'callback arguments' => array('lm_paypal_subscriptions_admin_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_NORMAL_ITEM, 'weight' => 1, // New to Drupal 5 - every path has a description 'description' => t('PayPal subscriptions interface configuration.'), ); } // Subscription admin $items[] = array( 'path' => 'admin/lm_paypal/subscriptions', 'title' => t('LM PayPal Subscriptions'), 'callback' => 'lm_paypal_view_subscriptions', 'access' => user_access('administer lm_paypal'), 'weight' => -3, // New to Drupal 5 - every path has a description 'description' => t('View PayPal subscriptions.'), ); $items[] = array( 'path' => 'admin/lm_paypal/subscriptions/new', 'title' => t('LM PayPal Create New Subscription'), 'callback' => 'lm_paypal_subscription_edit', 'access' => user_access('administer lm_paypal'), // New to Drupal 5 - every path has a description 'description' => t('Create new PayPal subscriptions.'), ); $items[] = array( 'path' => 'admin/lm_paypal/subscription/delete', 'title' => t('LM PayPal Delete Subscription'), 'type' => MENU_CALLBACK, 'callback' => 'lm_paypal_subscription_delete', 'access' => user_access('administer lm_paypal'), ); $items[] = array( 'path' => 'admin/lm_paypal/subscription', 'title' => t('LM PayPal Show Subscription Details'), 'type' => MENU_CALLBACK, 'callback' => 'lm_paypal_subscription', 'access' => user_access('administer lm_paypal'), ); $items[] = array( 'path' => 'admin/lm_paypal/subscribers', 'title' => t('LM PayPal Show Subscribers'), 'type' => MENU_CALLBACK, 'callback' => 'lm_paypal_subscribers', 'access' => user_access('administer lm_paypal'), ); $items[] = array( 'path' => 'admin/lm_paypal/subscriber_edit', 'title' => t('LM PayPal Edit Subscriber'), 'type' => MENU_CALLBACK, 'callback' => 'lm_paypal_subscriber_edit', 'access' => user_access('administer lm_paypal'), ); // User page to subscribe $items[] = array( 'path' => 'lm_paypal/subscribe', 'title' => t('PayPal Subscribe'), 'callback' => 'lm_paypal_subscribe', 'access' => user_access('access lm_paypal_subscribe'), // New to Drupal 5 - every path has a description 'description' => t('Subscribe using PayPal.'), ); // By default we tell Paypal to redirect users here after subscribing $items[] = array( 'path' => 'lm_paypal/subscriptions_inprogress', 'title' => t('LM PayPal Subscription In Progress'), 'type' => MENU_CALLBACK, 'callback' => 'lm_paypal_subscriptions_inprogress', 'access' => user_access('access lm_paypal_subscribe'), ); } return $items; } // Ugly magic to hide lm_paypal_settings from Drupal 5.0 as it spots its // existance and refuses to access the real page. if (strncmp(VERSION, '4', 1) == 0) { /** * Implementation of hook_settings() * Note: hook_settings not used in Drupal 5. */ function lm_paypal_subscriptions_settings() { return lm_paypal_subscriptions_settings_form(); } } /** */ function lm_paypal_subscriptions_admin_settings() { $form = lm_paypal_subscriptions_settings_form(); return system_settings_form($form); } /** */ function lm_paypal_subscriptions_settings_form() { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_inprogress; global $_lm_paypal_subscriptions_menu_rebuild; if (!user_access('administer lm_paypal')) { drupal_access_denied(); return; } $form['lm_paypal_subscriptions_inprogress'] = array( '#type' => 'textfield', '#title' => t('LM PayPal subscription in progress page'), '#default_value' => $_lm_paypal_subscriptions_inprogress, '#maxlength' => 100, '#required' => TRUE, '#description' => t('The page the user is sent to by PayPal after a subscribe. The default is ') . LM_PAYPAL_SUBSCRIPTIONS_INPROGRESS_DEFAULT . t(' but you might want to point it at a page you have created yourself.'), ); $form['lm_paypal_subscriptions_menu_rebuild'] = array( '#type' => 'checkbox', '#title' => t('Rebuild menu cache on subscription change'), '#default_value' => $_lm_paypal_subscriptions_menu_rebuild, '#description' => t('On a user being added to/removed from a subscription rebuild the menu cache. Needed only if user logout/login fails to get the right menu items.'), ); return $form; } /** * Find all live node subscriptions. * * @return * An array of of live node subscriptions */ function lm_paypal_subscription_node_subs() { $node_subs = array(); $output = ''; $sql = 'SELECT subid, item_name FROM {lm_paypal_subscriptions} WHERE status = 1 AND kind = 1'; $subs = db_query($sql); if (db_num_rows($subs) <= 0) { return $node_subs; } while ($so = db_fetch_object($subs)) { $node_subs[$so->subid] = $so->item_name; } return $node_subs; } /** * Tests if the given node has a live subscription. * * @param $nid * The node to check for * * @return * TRUE only if the node has a live subscription */ function lm_paypal_node_subscribed($nid,&$subid) { $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE status = 1 AND nid = %d"; $sbs = db_query($sql, $nid); if (db_num_rows($sbs) <= 0) { // No subscribers return FALSE; } if (!($sb = db_fetch_object($sbs))) { // TODO: No object.. is this an error? return FALSE; } $subid = $sb->subid; return TRUE; } /** * Display either all live subscriptions or a single one with a link to PayPal. * * @param $subid * If given then a subscribe page is returned otherwise a list of * available subscriptions is returned. * @param $display * If just showing a single subscription then $diplay lists what to show. * 1 = item_name * 2 = description * 4 = human readable details of subscription * 8 = button * 16 = brief * 32 = no section header (applies to list of subscriptions, subid=null) * These can be added to get combinations (eg: 11 = 1 + 2 + 8) * 64 = output a comma seperated list the current users subscriptions * (or just none) * @param button_url * The url of the button to display * @param nid * For node subscriptions this is the node id */ function lm_paypal_subscribe($subid = null, $display = 15, $button_url = '', $nid = null, $account = null) { _lm_paypal_subscriptions_ini(); global $user; global $_lm_paypal_debug; global $_lm_paypal_host; global $_lm_paypal_business; global $_lm_paypal_subscriptions_inprogress; global $_lm_paypal_drupal_major; global $_lm_paypal_js_hide_email; // Drupal 5 - t() has changed and to use a link in it use '!' $c = ($_lm_paypal_drupal_major > 4 ? '!' : '%'); if (!is_null($account)) { // I am looking at an account other than current user. // I cannot tell if they are logged in or not .. so presume they are. $logged_in = true; } else { $logged_in = ($user->uid != 0); } $subid = check_plain($subid); // Print out the details of just one subscription if ($subid != '') { if (!is_numeric($subid) || intval($subid) != $subid) { $err = t('lm_paypal_subscribe requires integer subid: %subid', array('%subid' => $subid)); watchdog( LM_PAYPAL_SUBSCRIPTIONS, $err, WATCHDOG_ERROR); return $err; } if (!$logged_in) { $login = l(t('login'), 'user'); $register = l(t('create new account'), 'user/register'); return t("You must ${c}login or ${c}register to subscribe", array("${c}login" => $login, "${c}register" => $register)); } $sql = "SELECT * FROM {lm_paypal_subscriptions} WHERE subid = %d AND status = 1"; $subs = db_query($sql, $subid); $so = db_fetch_object($subs); if (! $so) { $err = t('lm_paypal_subscribe cannot find subscription: %subid', array('%subid' => $subid)); watchdog( LM_PAYPAL_SUBSCRIPTIONS, $err, WATCHDOG_ERROR); return $err; } $output = ''; $output .= '' . lm_paypal_subscription($subid, $display) . '
'; if (($display & 8) == 0) { // If I am not displaying a button then I am done. return $output; } $i_agree = $_SESSION['lm_paypal_I_agree']; if ($so->terms != '' && $i_agree != $subid) { $output .= lm_paypal_subscription_terms($subid, $so->terms); return $output; } $_SESSION['lm_paypal_I_agree'] = ''; if ($button_url == '') { // This is the default paypal subscribe button $button_url = 'http://images.paypal.com/images/x-click-but20.gif'; $button_url = 'http://images.paypal.com/images/x-click-butcc-subscribe.gif'; } else { $button_url = check_url($button_url); } if ($so->return_url != '') { $ret_url = $so->return_url; } else { $ret_url = $_lm_paypal_subscriptions_inprogress; } if (variable_get('clean_url', 0)) { $return_url = url(check_url($ret_url), null, null, TRUE); } else { $return_url = url(null, 'q=' . check_url($ret_url), null, TRUE); } $notify_url = url('lm_paypal/ipn', null, null, TRUE); $biz = check_plain($_lm_paypal_business); $so->item_name = check_plain($so->item_name); // Output a form that will redirect the user to PayPal - note all the fields // are hidden so only the submit appears // If email hiding is enabled then turn on this javascript // and add an onsubmit action $onsub = ''; if ($_lm_paypal_js_hide_email) { lm_paypal_add_js(); $at = strpos($biz, '@'); $person = substr($biz, 0, $at); $host = substr($biz, $at + 1, strlen($biz)); $biz = ''; $onsub = "onsubmit=\"lm_paypal_setbiz(this,'$person','$host')\""; } $form = "\n\n"; $output .= $form; return $output; } // Output a list of all the live role or group subscriptions $output = ''; $sql = 'SELECT subid, item_name, description, status FROM {lm_paypal_subscriptions} WHERE status = 1 AND (kind = 0 OR kind = 2)'; $subs = db_query($sql); if (db_num_rows($subs) <= 0) { $output .= '' . t('No subscriptions currently defined') . '
'; return $output; } if (($display & 64) != 0) { $nSubs = 0; while ($so = db_fetch_object($subs)) { $already = lm_paypal_user_subscribed($so->subid, $account); if (!$already) { continue; } $item_name = check_plain($so->item_name); if ($nSubs > 0) { $output .= ", "; } $output .= '"' . $item_name . '"'; $nSubs ++; } if ($nSubs == 0) { $output .= t('none'); } return $output; } if (($display & 32) == 0) { $output .= ''; $output .= t('Click unsubscribe to log in to your PayPal account. Click the Details of the subscription in question. Click Cancel Subscription.'); $output .= "
" . t('Unsubscribe') . ''; $output .= '
'; return $output; } /** * Finds if a user is already subscribed to this subid * * @param $subid * The subscription to check * @param $account * The user to check, if null then the current user will be checked * * @return * TRUE only for role subscriptions and if the current user is subscribed */ function lm_paypal_user_subscribed($subid, $account = null) { global $user; if (is_null($account)) { $uid = $user->uid; } else { $uid = $account->uid; } $subid = check_plain($subid); if (!is_numeric($subid) || intval($subid) != $subid) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('lm_paypal_user_subscribed requires integer subid: %subid', array('%subid' => $subid)), WATCHDOG_ERROR); return FALSE; } // kind 0 is a role subscription, kind 2 is a group $sql = "SELECT uid, subid, status, kind FROM {lm_paypal_subscribers} WHERE uid = %d AND subid = %d AND status = 1 AND (kind = 0 OR kind = 2)"; $subs = db_query($sql, $uid, $subid); if (db_num_rows($subs) <= 0) { return FALSE; } $ss = db_fetch_object($subs); if (! $ss) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Cannot find the subscriber (uid %uid, subid %subid)', array('%uid' => $uid, '%subid' => $subid)), WATCHDOG_ERROR); return FALSE; } return TRUE; } /** * Tests if the current user, if any, can subscribe to a given subscription. * * @param $subid * The subscription the user is trying to subscribe to. * * @return * TRUE only if there is a user and they have not already subscribed */ function lm_paypal_can_subscribe($subid) { global $user; if ($user->uid == 0 ) { return FALSE; } if (lm_paypal_user_subscribed($subid)) { return FALSE; } return TRUE; } /** * The user has to agree to the terms and conditions */ function lm_paypal_subscription_terms($subid, $terms) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_drupal_major; if ($_lm_paypal_drupal_major > 4) { return drupal_get_form('lm_paypal_subscription_terms_form', $subid, $terms); } else { $form = lm_paypal_subscription_terms_form($subid, $terms); // Note: I've chosen a form_id the same as this function return drupal_get_form('lm_paypal_subscription_terms', $form); } } function lm_paypal_subscription_terms_form($subid, $terms) { // Under Drupal 5 this form has the following base name and all the // processing is done by calling #base_validate, ... $form['#base'] = 'lm_paypal_subscription_terms'; $_SESSION['lm_paypal_I_agree'] = ''; $terms = check_plain($terms); $form['terms'] = array( '#value' => t('') . $terms . t('
Before you can subscribe you must agree to these terms and conditions
'), ); $form['terms_subid'] = array( '#type' => 'hidden', '#value' => $subid, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('I agree'), ); return $form; } /** * Process "I Agree" to terms form * * @param $form_id * The form_id that caused this _submit function to be called * @pararm $values * The array of name,value pairs from the form * * @return * The url to go to. * * The user has agreed to the terms and conditions. Allow them to subscribe. */ function lm_paypal_subscription_terms_submit($form_id, $values) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_debug; $subid = ($values ['terms_subid']); $_SESSION['lm_paypal_I_agree'] = $subid; return 'lm_paypal/subscribe/' . $subid; } /** * Creates or edits a subscription. * * @param $subid * If provided then edit this subscription instead creating a new one */ function lm_paypal_subscription_edit($subid = '') { _lm_paypal_subscriptions_ini(); global $_lm_paypal_drupal_major; if ($_lm_paypal_drupal_major > 4) { return drupal_get_form('lm_paypal_subscription_edit_form', $subid); } else { $form = lm_paypal_subscription_edit_form($subid); // Note: I've chosen a form_id the same as this function return drupal_get_form('lm_paypal_subscription_edit', $form); } } function lm_paypal_subscription_edit_form($subid = '') { _lm_paypal_subscriptions_ini(); global $_lm_paypal_period_units_option; global $_lm_paypal_currency_option; // Under Drupal 5 this form has the following base name and all the // processing is done by calling #base_validate, ... $form['#base'] = 'lm_paypal_subscription_edit'; $subid = check_plain($subid); if ($subid != '' && (!is_numeric($subid) || intval($subid) != $subid)) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('lm_paypal_subscription_edit requires empty or integer subid: %subid', array('%subid' => $subid)), WATCHDOG_ERROR); return ''; } $edit = ($subid != ''); if ($edit) { // Look up the default values $sql = "SELECT * FROM {lm_paypal_subscriptions} WHERE subid = %d"; $subs = db_query($sql, $subid); if (db_num_rows($subs) <= 0) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to edit non existant subscription subid: %subid', array('%subid' => $subid)), WATCHDOG_ERROR); return ''; } $so = db_fetch_object($subs); if (! $so) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Cannot find the subscription subid: %subid', array('%subid' => $subid)), WATCHDOG_ERROR); return ''; } $form['edit_subid'] = array( '#type' => 'hidden', '#value' => $subid, ); } else { // Initialise a new subscription $so->src = 0; $so->uid_admin = LM_PAYPAL_SUBSCRIPTIONS_UID_ADMIN_DEFAULT; $so->send_admin_onsub = LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONSUB_DEFAULT; $so->send_admin_onend = LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONEND_DEFAULT; $so->send_user_onsub = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONSUB_DEFAULT; $so->send_user_onsub_subject = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONSUB_SUBJECT_DEFAULT; $so->send_user_onsub_body = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONSUB_BODY_DEFAULT; $so->send_user_onnearend = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONNEAREND_DEFAULT; $so->nearend_days = LM_PAYPAL_SUBSCRIPTIONS_NEAREND_DAYS_DEFAULT; $so->send_user_onnearend_subject = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONNEAREND_SUBJECT_DEFAULT; $so->send_user_onnearend_body = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONNEAREND_BODY_DEFAULT; $so->send_user_onend = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONEND_DEFAULT; $so->send_user_onend_subject = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONEND_SUBJECT_DEFAULT; $so->send_user_onend_body = LM_PAYPAL_SUBSCRIPTIONS_SEND_USER_ONEND_BODY_DEFAULT; $so->terms = LM_PAYPAL_SUBSCRIPTIONS_TERMS_DEFAULT; } $form['item_name'] = array( '#type' => 'textfield', '#title' => t('Subscription Name'), '#maxlength' => 127, '#default_value' => $so->item_name, '#required' => TRUE, '#description' => t('The name of the subscription'), ); $form['description'] = array( '#type' => 'textfield', '#title' => t('Description'), '#maxlength' => 127, '#default_value' => $so->description, '#required' => TRUE, ); $kinds = array(0 => 'Role', 1 => 'Node'); $descr = t('Kind of subscription. Role subscriptions for users to gain an role. Node subscription to make a private node public.'); $og = (function_exists('og_subscribe_user')); if ($og) { $kinds[2] = 'Organic Group'; $descr .= t(' Organic Group for users to gain a group'); $og = true; } $form['kind'] = array( '#type' => 'select', '#title' => t('Subscription Kind'), '#options' => $kinds, '#default_value' => $so->kind, '#required' => TRUE, '#description' => $descr, ); $form['rid'] = array( '#type' => 'select', '#title' => t('Subscribers Role'), '#options' => lm_paypal_subscribable_roles(TRUE), '#default_value' => $so->rid, //'#required' => TRUE, - not required for node-subs only role-subs '#description' => t('The role subscribers become members of. Only used when kind is Role'), ); if ($og) { // NOTE: Reusing the so->rid field for the Organic Group id $form['gid'] = array( '#type' => 'select', '#title' => t('Subscribers Organic Group'), '#options' => lm_paypal_subscribable_groups(), '#default_value' => $so->rid, //'#required' => TRUE, - not required for node-subs only role-subs '#description' => t('The Organic Group subscribers become members of. Only used when kind is Organic Group. Only "Invite Only" groups can be used in subcsriptions.'), ); } $form['a3'] = array( '#type' => 'textfield', '#title' => t('Regular rate'), '#maxlength' => 10, '#default_value' => $so->a3, '#required' => TRUE, '#description' => t('This is the price of the subscription. (The currency is specified below.)'), ); $form['p3'] = array( '#type' => 'textfield', '#title' => t('Regular billing cycle'), '#maxlength' => 10, '#default_value' => $so->p3, '#required' => TRUE, '#description' => t('This is the length of the billing cycle. The number is modified by the regular billing cycle units'), ); $form['t3'] = array( '#type' => 'select', '#title' => t('Regular billing cycle units'), '#options' => $_lm_paypal_period_units_option, '#default_value' => $so->t3, '#required' => TRUE, '#description' => t('This is the units of the regular billing cycle'), ); $form['src'] = array( '#type' => 'checkbox', '#title' => t('Recurring payments'), '#default_value' => $so->src, '#description' => t('If set the payment will recur unless your customer cancels the subscription before the end of the billing cycle. If omitted, the subscription payment will not recur at the end of the billing cycle'), ); $form['srt'] = array( '#type' => 'textfield', '#title' => t('Recurring Times'), '#maxlength' => 10, '#default_value' => $so->srt, '#description' => t('This is the number of payments which will occur at the regular rate. If omitted, payment will continue to recur at the regular rate until the subscription is cancelled. Requires Recurring payments to be set. If set it must be at least 2.'), ); $form['currency_code'] = array( '#type' => 'select', '#title' => t('The currency of the payment(s)'), '#default_value' => $so->currency_code, '#options' => $_lm_paypal_currency_option, '#description' => t('The currency to use in all payments relating to this subscription.'), ); $form['return_url'] = array( '#type' => 'textfield', '#title' => t('Return URL'), '#maxlength' => 200, '#default_value' => $so->return_url, '#description' => t('If set this is the URL the user is returned to after completing the transaction at PayPal. If can be used to override the default "thank you" page with a page of your own.'), ); $form['trial_header'] = array( '#value' => '' . t("LM PayPal not yet configured! An administrator needs to do this at ${c}settings", array("${c}settings" => $settings)). '.
'; return $output; } $link = l(t('View All Subscribers to All Subscriptions'), 'admin/lm_paypal/subscribers/0'); $view_all = "$link
"; $link = l(t('Create New Subscription'), 'admin/lm_paypal/subscriptions/new'); $create = "$link
"; // Output a list of all the existing subscriptions $sql = 'SELECT subid, item_name, description, status, kind, rid FROM {lm_paypal_subscriptions}'; $subs = db_query($sql); if (db_num_rows($subs) <= 0) { $output .= '' . t('No subscriptions currently defined') . '
'; $output .= $create; return $output; } $output .= '' . check_plain($so->description) . '
'; } } if (($display & 4) != 0) { if (($display & 16) != 0) { $output .= " "; } else { $output .= ''; } if ($so->p1 != '' && $so->p1 > 0) { $tu = lm_paypal_unit2str($so->t1); $amount = lm_paypal_nccc2str($so->a1, $so->currency_code); $duration = check_plain($so->p1) . ' ' . $tu; $output .= t('First trial period %amount for %duration.', array('%amount' => $amount, '%duration' => $duration)); } if ($so->p2 != '' && $so->p2 > 0) { $tu = lm_paypal_unit2str($so->t2); $amount = lm_paypal_nccc2str($so->a2, $so->currency_code); $duration = check_plain($so->p2) . ' ' . $tu; $output .= t('Second trial period %amount for %duration.', array('%amount' => $amount, '%duration' => $duration)); } $tu = lm_paypal_unit2str($so->t3); $amount = lm_paypal_nccc2str($so->a3, $so->currency_code); $duration = check_plain($so->p3) . ' ' . $tu; $output .= t('Rate %amount for %duration. ', array('%amount' => $amount, '%duration' => $duration)); if ($so->src) { if ($so->srt != '') { $times = $so->srt; $output .= t('Recurs %times times.', array('%times' => $times)) ; } else { $output .= t('Recurs till cancelled') . '. '; } } if ($so->kind == 0) { $role = $roles[$so->rid]; $output .= t('User becomes member of role %role. ', array('%role' => $role)); } else if ($so->kind == 1) { $output .= t('Node will become viewable by others. '); } else if ($so->kind == 2) { $group = $groups[$so->rid]; $output .= t('User becomes member of Organic Group %group. ', array('%group' => $group)); } // Calculate the length of the subscription - just for debugging for now $duration = lm_paypal_subscription_days($so); if ($duration == 0) { $output .= t('(Duration - till cancelled.)'); } else { $output .= t('(Duration %duration days.)', array('%duration' => $duration)); } if (($display & 16) == 0) { $output .= '
'; } } return $output; } // Output the subscription as a table of fields/values (skip the empty ones) $output = '' . t('Editing the subscription here will only effect new subscriptions.') . '
'; $output .= l(t('edit'), "admin/lm_paypal/subscriptions/new/$subid"); $output .= '' . t('Be very careful deleting subscriptions because they cannot be recovered. It is much better to edit them and change their status to defunct. Also if users are still subscribed at PayPal to a deleted subscription incoming payments will be problematic.') . '
'; $output .= l(t('delete'), "admin/lm_paypal/subscription/delete/$subid"); return $output; } /** * Find the duration of a subscription. * * @param $so * A subscription object * @return * 0 if recurrs till cancelled, otherwise the number of days duration */ function lm_paypal_subscription_days($so) { if ($so->src && $so->srt == '') { return 0; } $multiply = 1; if ($so->src && $so->srt > 1) { $multiply = $so->srt; } return (lm_paypal_period_unit2days($so->p1, $so->t1) + lm_paypal_period_unit2days($so->p2, $so->t2) + (lm_paypal_period_unit2days($so->p3, $so->t3) * $multiply)); } /** * Delete a subscription definition. * * @param $subid * Required. The subid of the definition to delete. * @return * A string describing the result of the deletion. */ function lm_paypal_subscription_delete($subid) { $sql = "DELETE FROM {lm_paypal_subscriptions} WHERE subid = %d"; db_query($sql, $subid); $msg = t('Deleted subscription %subid', array('%subid' => $subid)); watchdog(LM_PAYPAL_SUBSCRIPTIONS, $msg); //drupal_set_message($msg); return t('Deleted subscription'); } /** * Process a newly arrived subscription ipn message * * @param $ipn * The ipn containing a subscription message. */ function lm_paypal_process_in_subscr($ipn) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_debug; global $_lm_paypal_drupal_major; // Drupal 5 - t() has changed and to use a link/literal in it use '!' $c = ($_lm_paypal_drupal_major > 4 ? '!' : '%'); if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, 'in_subscr'); } $link = l(t('view'), "admin/lm_paypal/id/$ipn->id"); if ($ipn->payment_status == 'Pending') { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Ignoring IPN with status: Pending. Check your PayPal account to see why it is pending. Note: pending_reason: %reason', array('%reason' => check_plain($ipn->pending_reason))), WATCHDOG_ERROR, $link); return; } if ($ipn->item_number == '' || !is_numeric($ipn->item_number) || $ipn->custom == '' || !is_numeric($ipn->custom)) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Subscription ipn invalid item_number or custom (item_number %item_number, custom %custom)', array('%item_number' => check_plain($ipn->item_number), '%custom' => check_plain($ipn->custom))), WATCHDOG_ERROR, $link); return; } $subid = $ipn->item_number; $custom = $ipn->custom; $uid = $custom & 0xFFFF; $other = ($custom >> 16) & 0xFFFF; $nid = $other; // This is usually true and doesn't hurt otherwise // Useful string to add to node related subscription related emails $Node = t(" on node ${c}nid", array("${c}nid" => $nid)); // Look up the subscription $sql = "SELECT * FROM {lm_paypal_subscriptions} WHERE subid = %d AND status = 1"; $r = db_query($sql, $subid); $so = db_fetch_object($r); if (! $so) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('IPN uses unknown or defunct subscription, ignored: %subid', array('%subid' => $subid)), WATCHDOG_ERROR, $link); return; } // Is this uid valid? $users = db_query("SELECT * FROM {users} WHERE uid = %d", $uid); if (db_num_rows($users) <= 0) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('IPN subscribing unknown uid, ignored: %uid', array('%uid' => $uid)), WATCHDOG_ERROR, $link); return; } if ($ipn->txn_type == 'subscr_signup') { // Note: a signup doesn't have a transaction id! if (!_lm_paypal_subscriptions_validate($ipn, $so, $link)){ return; } // Is this an existing subscription (sometimes payments come before // signups so this is possible). $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE subscr_id = '%s'"; $subs = db_query($sql, $ipn->subscr_id); if (db_num_rows($subs) <= 0) { // Try and track problem maybe caused by multiple subscr_signups watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('new subscr_signup subscr_id %sid', array('%sid' => $ipn->subscr_id))); // Not already present. // Create an entry in the subscribers table. lm_paypal_add_subscriber($so->kind, $uid, $other, $subid, $ipn->subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_SIGNEDUP); } else { // Try and track problem maybe caused by multiple subscr_signups watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('repeat subscr_signup subscr_id %sid', array('%sid' => $ipn->subscr_id))); // It is already present $ss = db_fetch_object($subs); if (!$ss) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Unable to fetch the subscriber object'), WATCHDOG_ERROR); // Maybe its a temporary error? Reject this and when PayPal retries // it might work! drupal_set_header('HTTP/1.0 404 Not Found'); return; } switch ($ss->status) { case LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to signup to blocked subscription, ignored'), WATCHDOG_ERROR, $link); return; case LM_PAYPAL_SUBSCRIPTIONS_STATUS_DEAD: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_CANCELLED: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_EOT: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to signup to ended subscription, ignored %status', array('%status' => $ss->status)), WATCHDOG_ERROR, $link); return; // Some users are reporting symptoms that suggest multiple // subscr_signup's are arriving. Ignore the duplicates here. case LM_PAYPAL_SUBSCRIPTIONS_STATUS_LIVE: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_SIGNEDUP: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Signup received but subscription live or signedup, ignoring'), WATCHDOG_ERROR, $link); return; // A payment arrived first. Now obey the signup case LM_PAYPAL_SUBSCRIPTIONS_STATUS_PAID: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Signup received after payment, obeying it anyway')); break; } } // A signup alone is enough to go live. A payment IPN may not arrive at all // if the subsciption amount is zero (usually a trial period) lm_paypal_subscriber_set_status($ipn->subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_LIVE); if ($so->kind == 0) { lm_paypal_user_gain_role($uid, $so->rid); } else if ($so->kind == 2) { lm_paypal_user_gain_group($uid, $so->rid); } if ($so->send_admin_onsub && is_numeric($so->uid_admin)) { // Email admin to let them know of a new subscriber // note: t() will be called inside lm_paypal_mail_user // Extra variables (in addition to the default ones provided) if ($so->kind == 0 || $so->kind == 2) { $variables = array('%Subscription' => $so->item_name, '%Node' => ''); } else if ($so->kind == 1) { $variables = array('%Subscription' => $so->item_name, '%Node' => $Node); } lm_paypal_mail_user( $so->uid_admin, $uid, LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONSUB_SUBJECT, LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONSUB_BODY, $variables); } if ($so->send_user_onsub) { // Email user to confirm a new subscriber // note: t() will be called inside lm_paypal_mail_user // Extra variables (in addition to the default ones provided) if ($so->kind == 0 || $so->kind == 2) { $variables = array('%Subscription' => $so->item_name, '%Node' => ''); } else if ($so->kind == 1) { $variables = array('%Subscription' => $so->item_name, '%Node' => $Node); } lm_paypal_mail_user($uid, $uid, $so->send_user_onsub_subject, $so->send_user_onsub_body, $variables); } return; } else if ($ipn->txn_type == 'subscr_payment' && $ipn->payment_status == 'Completed') { if (lm_paypal_already_processed($ipn->txn_id)) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('This transaction has already been processed, ignored: %id', array('%id' => check_plain($ipn->txn_id))), WATCHDOG_WARNING, $link); return; } // This should be an existing subscription (but sometimes payments come // before signups). $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE subscr_id = '%s'"; $subs = db_query($sql, $ipn->subscr_id); if (db_num_rows($subs) <= 0) { // Not already present. // Create an entry in the subscribers table. lm_paypal_add_subscriber($so->kind, $uid, $other, $subid, $ipn->subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_PAID); } else { // It is already present $ss = db_fetch_object($subs); if (!$ss) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Unable to fetch subscriber object'), WATCHDOG_ERROR); // Maybe its a temporary error? Reject this and when PayPal retries // it might work! drupal_set_header('HTTP/1.0 404 Not Found'); return; } switch ($ss->status) { case LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to pay to blocked subscription, ignored'), WATCHDOG_ERROR, $link); case LM_PAYPAL_SUBSCRIPTIONS_STATUS_DEAD: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_CANCELLED: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_EOT: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to pay ended subscription, ignored %status', array('%status' => $ss->status)), WATCHDOG_ERROR, $link); } } lm_paypal_mark_processed($ipn); return; } else if ($ipn->txn_type == 'subscr_cancel' || $ipn->txn_type == 'subscr_eot') { // Note: Cancel's dont get a transaction id! $end = check_plain(substr($ipn->txn_type, 7)); if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('User ending subscription (uid %uid, subid %subid, type %type)', array('%uid' => $uid, '%subid' => $subid, '%end' => $end))); } // Check I know this subscr_id $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE subscr_id = '%s'"; $subs = db_query($sql, $ipn->subscr_id); if (db_num_rows($subs) <= 0) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to end unknown subscription, ignored'), WATCHDOG_ERROR, $link); return; } // It is already present $ss = db_fetch_object($subs); if (!$ss) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Unable to fetch the subscriber object'), WATCHDOG_ERROR, $link); // Maybe its a temporary error? Reject this and when PayPal retries // it might work! drupal_set_header('HTTP/1.0 404 Not Found'); return; } switch ($ss->status) { case LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to end to blocked subscription, ignored'), WATCHDOG_ERROR, $link); return; case LM_PAYPAL_SUBSCRIPTIONS_STATUS_DEAD: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_CANCELLED: case LM_PAYPAL_SUBSCRIPTIONS_STATUS_EOT: watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to end ended subscription, ignored %status', array('%status' => $ss->status)), WATCHDOG_ERROR, $link); return; } // Mark the subscription ended $status = ($end == 'eot' ? LM_PAYPAL_SUBSCRIPTIONS_STATUS_EOT : LM_PAYPAL_SUBSCRIPTIONS_STATUS_CANCEL); lm_paypal_subscriber_set_status($ipn->subscr_id, $status); if ($so->kind == 0) { lm_paypal_user_loose_role($uid, $so->rid); } else if ($so->kind == 2) { lm_paypal_user_loose_group($uid, $so->rid); } if ($so->send_admin_onend && is_numeric($so->uid_admin)) { // Email admin to let them know of an eot or cancellation // note: t() will be called inside lm_paypal_mail_user // Extra variables (in addition to the default ones provided) if ($so->kind == 0 || $so->kind == 2) { $variables = array('%Subscription' => $so->item_name, '%End' => $end, '%Node' => ''); } else if ($so->kind == 1) { $variables = array('%Subscription' => $so->item_name, '%End' => $end, '%Node' => $Node); } lm_paypal_mail_user( $so->uid_admin, $uid, LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONEND_SUBJECT, LM_PAYPAL_SUBSCRIPTIONS_SEND_ADMIN_ONEND_BODY, $variables); } if ($so->send_user_onend) { // Email user to confirm an eot or cancellation // note: t() will be called inside lm_paypal_mail_user // Extra variables (in addition to the default ones provided) if ($so->kind == 0 || $so->kind == 2) { $variables = array('%Subscription' => $so->item_name, '%End' => $end, '%Node' => ''); } else if ($so->kind == 1) { $variables = array('%Subscription' => $so->item_name, '%End' => $end, '%Node' => $Node); } lm_paypal_mail_user($uid, $uid, $so->send_user_onend_subject, $so->send_user_onend_body, $variables); } return; } watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Ignoring invalid, unrecognised, or pending subscription ipn type: %type', array('%type' => check_plain($ipn->txn_type))), WATCHDOG_ERROR, $link); return; } /** * Check that the incoming subscription details match those of the subscription. * * @param $ipn * A message to prepend to error output * @param $so * subsciption object * @param $link * A link to this ipn for display in watchdogs * @return * TRUE if a successful match, FALSE otherwise */ function _lm_paypal_subscriptions_validate($ipn, $so, $link) { // note: amount, period, type (a,p,t) // 1 = trial period 1 // 2 = trial period 2 // 3 = regular (so a3,p3,t3 = regular subscription) // annoyinging the incoming period is a combination of period and units if (!_lm_paypal_subscriptions_validate_ap( t('Main'), $link, $ipn->subscr_id, $ipn->mc_amount3, $so->a3, $ipn->mc_currency, $so->currency_code, $ipn->period3, $so->p3, $so->t3)) { return FALSE; } if (!_lm_paypal_subscriptions_validate_ap( t('Trial Period 1'), $link, $ipn->subscr_id, $ipn->mc_amount1, $so->a1, $ipn->mc_currency, $so->currency_code, $ipn->period1, $so->p1, $so->t1)) { return FALSE; } if (!_lm_paypal_subscriptions_validate_ap( t('Trial Period 2'), $link, $ipn->subscr_id, $ipn->mc_amount2, $so->a2, $ipn->mc_currency, $so->currency_code, $ipn->period2, $so->p2, $so->t2)) { return FALSE; } if ($so->src == '') { $so->src = 0; } if ($ipn->recurring != $so->src) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Recurrence mismatch (supplied %supplied,expected %expected), blocking subscription', array('%supplied' => check_plain($ipn->recurring), '%expected' => check_plain($so->src))), WATCHDOG_ERROR, $link); lm_paypal_subscriber_set_status($ipn->subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED); return FALSE; } if ($ipn->recur_times != $so->srt) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Recur times mismatch (supplied %supplied,expected %expected), blocking subscription', array('%supplied' => check_plain($ipn->recur_times), '%expected' => check_plain($so->srt))), check_plain($ipn->recur_times) . ',' . check_plain($so->srt), WATCHDOG_ERROR, $link); lm_paypal_subscriber_set_status($ipn->subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED); return FALSE; } return TRUE; } /** * Check that the incoming subscription amount+period details match those * of the subscription. * * @param $msg * A message to prepend to error output * @param $link * A link to this ipn for display in watchdogs * @param $subscr_id * Subscribers id * @param $ipn_amount * The incoming amount * @param $amount * The subscription amount * @param $ipn_currency * The incoming currency * @param $cc * The subscription currecy * @param $ipn_period * The incoming period+timeunit * @param $period * The subscription period+timeunit * @return * TRUE if a successful match, FALSE otherwise */ function _lm_paypal_subscriptions_validate_ap($msg, $link, $subscr_id, $ipn_amount, $amount, $ipn_currency, $cc, $ipn_period, $period, $units) { $ipn_amount = trim($ipn_amount); $ipn_currency = trim($ipn_currency); $amount = trim($amount); $cc = trim($cc); if ($ipn_amount == '0.00') { $ipn_amount = 0; } if ($amount == '0.00') { $amount = 0; } if ($ipn_amount != $amount || $ipn_currency != $cc) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, $msg . ' ' .t('payment mismatch (supplied %supplied, expected %expected), blocking subscription:', array('%supplied' => check_plain("$ipn_amount $ipn_currency"), '%expected' => check_plain("$amount $cc"))), WATCHDOG_ERROR, $link); lm_paypal_subscriber_set_status($subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED); return FALSE; } // If the amount is zero then ignore the period (this only applies to // trials because the regular period cannot have a zero amount) if ($ipn_amount == 0 || $ipn_amount == '') { return TRUE; } $ipn_period = trim($ipn_period); $period = trim($period); $units = trim($units); if ($period == '' || $period == 0) { $pu = ''; } else { $pu = "$period $units"; } if ($ipn_period != $pu) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, $msg . ' ' . t('period mismatch (supplied %supplied, expected %expected), blocking subscription', array('%supplied' => check_plain($ipn_period), '%expected' => $pu)), WATCHDOG_ERROR, $link); lm_paypal_subscriber_set_status($subscr_id, LM_PAYPAL_SUBSCRIPTIONS_STATUS_BLOCKED); return FALSE; } return TRUE; } /** * Update a subscribers status * * @param $subscr_id * The PayPal subscr_id to have its table entry updated * @param $status * The new status value */ function lm_paypal_subscriber_set_status($subscr_id, $status) { // Already in subscribers: update $sql = 'UPDATE {lm_paypal_subscribers} SET '; if ($status == LM_PAYPAL_SUBSCRIPTIONS_STATUS_LIVE){ $sql .= 'started = ' . time() . ','; } $sql .= "status = %d"; $sql .= " WHERE subscr_id = '%s'"; $update = db_query($sql, $status, $subscr_id); if (!$update) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Failed to update to subscribers status'), WATCHDOG_ERROR); return; } } /** * Add a subscriber to local tables. * * @param $kind * The kind of subscription * @param $uid * The local users uid * @param $nid * The local users nid * @param subid * The subscription id * @param $subscr_id * The PayPal subscr_id to be saved * @param $status * The status value */ function lm_paypal_add_subscriber($kind, $uid, $nid, $subid, $subscr_id, $status) { // Add them to subscribers or update their existing record $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE subscr_id = '%s'"; $subs = db_query($sql, $subscr_id); if (db_num_rows($subs) <= 0) { // Not present in subscribers: insert $sql = "INSERT INTO {lm_paypal_subscribers} SET "; if ($status == LM_PAYPAL_SUBSCRIPTIONS_STATUS_LIVE){ $sql .= 'started = ' . time() . ','; } $sql .= "kind = %d, uid = %d, nid = %d, subid = %d, "; $sql .= "subscr_id = '%s', "; $sql .= "status = %d"; $insert = db_query($sql, $kind, $uid, $nid, $subid, $subscr_id, $status); if (!$insert) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Failed to insert subscribers: %subscr_id', array('%subscr_id' => $subscr_id)), WATCHDOG_ERROR); } } else { // Already in subscribers: update $sql = 'UPDATE {lm_paypal_subscribers} SET '; if ($status == LM_PAYPAL_SUBSCRIPTIONS_STATUS_LIVE){ $sql .= 'started = ' . time() . ','; } $sql .= "status = %d"; $sql .= " WHERE subscr_id = '%s'"; $update = db_query($sql, $status, $subscr_id); if (!$update) { watchdog( LM_PAYPAL_SUBSCRIPTIONS, t('Failed to update subscribers: %subscr_id', array('%subscr_id' => $subscr_id)), WATCHDOG_ERROR); } } } /** * Implementation of hook_user(). */ function lm_paypal_subscriptions_user($op, &$edit, &$account, $category = NULL) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_debug; global $user; // TODO: On deleting a user issue a cancel on any subscriptions // obviously not on PayPal (can't!) just on here. // In the "my account" view area show any role subscriptions // but only for admin looking at other users or the user themself if ($op == 'view' && (user_access('administer lm_paypal') || $user->uid == $account->uid)) { $output = lm_paypal_subscribe(null, 32, '', null, $account); $sitems [] = array( 'title' => '', 'value' => $output); $ret_subs = array(t('Paypal Subscriptions') => $sitems); return $ret_subs; } } /** * Implementation of hook_block() */ function lm_paypal_subscriptions_block($op = 'list', $delta = 0, $edit = array()) { global $user; if ($op == 'list') { // Output a list of all the live subscriptions (Role or Group) $blocks = array(); $sql = "SELECT subid, item_name, status FROM {lm_paypal_subscriptions} WHERE status = 1 AND (kind = 0 OR kind = 2)"; $subs = db_query($sql); while ($so = db_fetch_object($subs)) { $blocks[$so->subid]['info'] = t('PayPal Subscription: %name', array('%name' => $so->item_name)); } return $blocks; } else if ($op == 'view') { $subid = $delta; // Subscriptions are only available to users of the site if ($user->uid == 0) { return; } if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('block view: %subid', array('%subid' => $subid))); } // Dont show a subscription box if they are already subscribed $already = lm_paypal_user_subscribed($subid); if ($already) { return; } $sql = "SELECT * FROM {lm_paypal_subscriptions} WHERE status = 1 AND (kind = 0 OR kind = 2) AND subid = %d"; $subs = db_query($sql, $subid); $so = db_fetch_object($subs); $block['subject'] = t('PayPal Subscription:%name', array('%name' => check_plain($so->item_name))); $output .= lm_paypal_subscribe($subid, 2+8); $block['content'] = $output; return $block; } } /** * Implementation of hook_form_alter(). */ function lm_paypal_subscriptions_form_alter($form_id, $form) { if ($form_id == 'system_modules' && !$_POST) { // Check that lm_paypal is enabled $me = 'lm_paypal_subscriptions'; $dep = array('lm_paypal'); lm_paypal_system_module_validate($form,$me,$dep); } } /** * Returns the default page that users are sent to by PayPal after subscribing. * * @return * A string containing the page contents. */ function lm_paypal_subscriptions_inprogress() { return t('Your subscription is in progress but for it to take effect you will have to logout and login again.
Then you can check your subscriptions by looking in "my account".
Special Note: Although PayPal subscriptions are usually instantaneous sometimes, during really busy periods, it may take a few hours to take effect.
'); } /** * View subscibers * * @param * if a subid is passed then just print out details of that subscription */ function lm_paypal_subscribers($subid = null) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_stati; global $_lm_paypal_drupal_major; $subs_per_page = 50; if ($_lm_paypal_drupal_major > 4) { $output = drupal_get_form('lm_paypal_subscribers_form', $subid); } else { $form = lm_paypal_subscribers_form($subid); // Note: I've chosen a form_id the same as this function $output = drupal_get_form('lm_paypal_subscribers', $form); } $header = array( array('data' => t('Kind'), 'field' => 's.kind'), array('data' => t('User'), 'field' => 'u.name'), array('data' => t('Node'), 'field' => 's.nid'), array('data' => t('Subid'), 'field' => 's.subid'), array('data' => t('Subscription Name'), 'field' => 'd.item_name'), array('data' => t('Started'), 'field' => 's.started', 'sort' => 'desc'), array('data' => t('Status'), 'field' => 's.status'), array('data' => t('PayPal Subscription ID'), 'field' => 's.subscr_id'), ); $sql = "SELECT u.name, d.item_name, s.kind, s.usid, s.uid, s.nid, s.subid, s.started, s.status, s.subscr_id FROM {lm_paypal_subscriptions} d, {lm_paypal_subscribers} s INNER JOIN {users} u ON s.uid = u.uid WHERE u.uid = s.uid AND d.subid = s.subid"; $tablesort = tablesort_sql($header); // If not sorting by started then make that the 2nd field to sort on if (strpos($tablesort,'started') === FALSE) { $tablesort .= ', started DESC'; } if ($subid != 0) { $subid_str = " AND s.subid = '$subid' "; } $status = $_SESSION['lm_paypal_subs_filter']; if ($status != 'all') { $sql .= " AND s.status = '%s'". $subid_str . $tablesort; $result = pager_query($sql, $subs_per_page, 0, NULL, $status); } else { $sql .= $subid_str . $tablesort; $result = pager_query($sql, $subs_per_page); } while ($sub = db_fetch_object($result)) { $ss = $_lm_paypal_subscriptions_stati[$sub->status]; if ($ss == '') { $ss = $sub->status; } if ($sub->nid != 0) { $l = "node/$sub->nid"; $nid = l($l,$l); } else { $nid = ''; } if ($sub->kind == 0) { $kind = t('Role'); } else if ($sub->kind == 1) { $kind = t('Node'); } else if ($sub->kind == 2) { $kind = t('Group'); } $rows[] = array('data' => array( $kind, l(check_plain($sub->name), "user/$sub->uid"), $nid, l($sub->subid, "admin/lm_paypal/subscription/$sub->subid"), check_plain($sub->item_name), format_date($sub->started, 'small'), $ss, $sub->subscr_id, l(t('edit'), "admin/lm_paypal/subscriber_edit/$sub->usid"), ), ); } if (!$rows) { $rows[] = array(array('data' => t('No subs found.'), 'colspan' => 3)); } $output .= theme('table', $header, $rows); $output .= theme('pager', NULL, $subs_per_page, 0); return $output; } function lm_paypal_subscribers_form($subid) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_stati; global $_lm_paypal_drupal_major; $stati = array('all' => t('all subscribers')) + $_lm_paypal_subscriptions_stati; if (!is_numeric($_SESSION['lm_paypal_subs_filter'])) { $_SESSION['lm_paypal_subs_filter'] = 'all'; } if ($subid == null || !is_numeric($subid) || intval($subid) != $subid) { $subid = 0; } $form['filter'] = array( '#type' => 'select', '#title' => t('Filter Subscribers'), '#options' => $stati, '#default_value' => $_SESSION['lm_paypal_subs_filter'], ); $form['#action'] = url("admin/lm_paypal/subscribers/$subid"); $form['submit'] = array('#type' => 'submit', '#value' =>t('Filter')); return $form; } /** * Process the form submission for lm_paypal_subscribers */ function lm_paypal_subscribers_submit($form_id, $form) { global $form_values; $_SESSION['lm_paypal_subs_filter'] = $form_values['filter']; } /** * Process the form submission for lm_paypal_subscribers for Drupal 5 */ function lm_paypal_subscribers_form_submit($form_id, $form) { lm_paypal_subscribers_submit($form_id, $form); } /** * Edit a subscriber. * * @param $usit * The subscriber to edit */ function lm_paypal_subscriber_edit_form($usid = '') { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_stati; $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE usid = %d"; $subs = db_query($sql, $usid); if (db_num_rows($subs) <= 0) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Attempt to edit non existant subscriber: %usid', array('%usid' => $usid)), WATCHDOG_ERROR); return ''; } $sb = db_fetch_object($subs); if (! $sb) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Cannot find subscriber: %usid', array('%usid' => $usid)), WATCHDOG_ERROR); return ''; } // Under Drupal 5 this form has the following base name and all the // processing is done by calling #base_validate, ... $form['#base'] = 'lm_paypal_subscriber_edit'; $form['Edit Subscriber'] = array( '#value' => 'Uid ' . $sb->uid . '
', ); $form['edit_usid'] = array( '#type' => 'hidden', '#default_value' => $usid, ); $form['status'] = array( '#type' => 'select', '#title' => t('Status'), '#options' => $_lm_paypal_subscriptions_stati, '#default_value' => $sb->status, '#required' => TRUE, '#description' => t('The status of this subscriber'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Update subscriber'), ); return $form; } function lm_paypal_subscriber_edit($usid = '') { _lm_paypal_subscriptions_ini(); global $_lm_paypal_drupal_major; if ($_lm_paypal_drupal_major > 4) { return drupal_get_form('lm_paypal_subscriber_edit_form', $usid); } else { $form = lm_paypal_subscriber_edit_form($usid); // Note: I've chosen a form_id the same as this function return drupal_get_form('lm_paypal_subscriber_edit', $form); } } /** * Form processor for edit subscribers. * * @param $form_id * The form_id that caused this _submit function to be called * @pararm $values * The array of name,value pairs from the form * * @return * The url to go to. * * Updates subscribers in the lm_paypal_subscrbers table. */ function lm_paypal_subscriber_edit_submit($form_id, $values) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_debug; $usid = ($values['edit_usid']); $sql = "UPDATE {lm_paypal_subscribers} SET status = '%s' WHERE usid = %d"; $db_res = db_query($sql, $values['status'], $usid); if (!$db_res) { $msg = t('Failed to update subscriber sql: %sql', array('%sql' => check_plain($sql))); watchdog(LM_PAYPAL_SUBSCRIPTIONS, check_plain($msg), WATCHDOG_ERROR); drupal_set_message($msg); // TODO: where to go to on error! return t('Failed to update subscriber'); } if ($_lm_paypal_debug) { $msg = t('Updated subscriber: %usid', array('%usid' => $usid)); watchdog(LM_PAYPAL_SUBSCRIPTIONS, $msg); drupal_set_message($msg); } return "admin/lm_paypal/subscriber_edit/$usid"; } /** * Give the user the extra role if they dont already have it * * @param $uid * uid of the user to gain the role * @param $rid * rid for the user to gain */ function lm_paypal_user_gain_role($uid, $rid) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_menu_rebuild; $sql = "SELECT * FROM {users_roles} WHERE uid = %d AND rid = %d"; $roles = db_query($sql, $uid, $rid); if (db_num_rows($roles) <= 0) { $sql = "INSERT INTO {users_roles} SET uid = %d, rid = %d"; $insert = db_query($sql, $uid, $rid); if (!$insert) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Failed to add user to role (uid %uid, rid %rid)', array('%uid' => $uid, '%rid' => $rid)), WATCHDOG_ERROR); return; } if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Added user to role (uid %uid, rid %rid)', array('%uid' => $uid, '%rid' => $rid))); } if ($_lm_paypal_subscriptions_menu_rebuild) { menu_rebuild(); } } } /** * Drop the role from the user - ONLY if they have no other live * subscriptions that also give them this role. * * @param $uid * uid of the user to loose the role * @param $rid * rid for the user to loose */ function lm_paypal_user_loose_role($uid, $rid) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_menu_rebuild; $sql = "SELECT * FROM {lm_paypal_subscriptions} d INNER JOIN {lm_paypal_subscribers} s ON d.subid = s.subid WHERE uid = %d AND rid = %d AND s.status = 1 AND d.kind = 0"; $also = db_query($sql, $uid, $rid); if (db_num_rows($also) > 0) { if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('User keeps role because of other subscriptions (uid %uid, rid %rid)', array('%uid' => $uid, '%rid' => $rid))); } return; } $sql = "DELETE FROM {users_roles} WHERE uid = %d AND rid = %d"; db_query($sql, $uid, $rid); if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Drop user from role (uid %uid, rid %rid)', array('%uid' => $uid, '%rid' => $rid))); } if ($_lm_paypal_subscriptions_menu_rebuild) { menu_rebuild(); } } /** * Give the user the extra group * * @param $uid * uid of the user to gain the role * @param $gid * gid for the user to gain */ function lm_paypal_user_gain_group($uid, $gid) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_menu_rebuild; og_save_subscription($gid, $uid, array('is_active' => 1)); if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Added user to group (uid %uid, gid %gid)', array('%uid' => $uid, '%gid' => $gid))); } if ($_lm_paypal_subscriptions_menu_rebuild) { menu_rebuild(); } } /** * Drop the group from the user - ONLY if they have no other live * subscriptions that also give them this group. * * @param $uid * uid of the user to loose the role * @param $gid * gid for the user to loose */ function lm_paypal_user_loose_group($uid, $gid) { _lm_paypal_subscriptions_ini(); global $_lm_paypal_subscriptions_menu_rebuild; $sql = "SELECT * FROM {lm_paypal_subscriptions} d INNER JOIN {lm_paypal_subscribers} s ON d.subid = s.subid WHERE uid = %d AND rid = %d AND s.status = 1 AND d.kind = 2"; $also = db_query($sql, $uid, $rid); if (db_num_rows($also) > 0) { if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('User keeps group because of other subscriptions (uid %uid, gid %gid)', array('%uid' => $uid, '%gid' => $gid))); } return; } og_delete_subscription($gid, $uid); if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Drop user from group (uid %uid, gid %gid)', array('%uid' => $uid, '%gid' => $gid))); } if ($_lm_paypal_subscriptions_menu_rebuild) { menu_rebuild(); } } /** * Implementation of hook_cron(). */ function lm_paypal_subscriptions_cron() { _lm_paypal_subscriptions_ini(); global $_lm_paypal_debug; global $_lm_paypal_ipns_max_age; if ($_lm_paypal_debug) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, 'cron'); } // Look for any subscriber who is near the end of their subscription // Find all live subscriptions $sql = 'SELECT * FROM {lm_paypal_subscriptions} WHERE status = 1'; $subs = db_query($sql); if (db_num_rows($subs) <= 0) { // No subscriptions return; } while ($so = db_fetch_object($subs)) { $item_name = $so->item_name; // Should I warn people near the end of this subscription? $nearend_days = $so->nearend_days; if (! ($nearend_days >= 1)) { // Not set, skip this subscription continue; } // Find how many days the subscription is supposed to last $duration = lm_paypal_subscription_days($so); if ($duration == 0) { // Infinite, skip this subscription continue; } // Find the seconds from start till I should warn $from_start = ($duration - $nearend_days) * (24 * 60 * 60); /* OK: This could be negative because cron wasn't fired off for some reason and the email wasn't sent if ($from_start < 0) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, "cron $item_name from_start $from_start < 0"); continue; } */ $subid = $so->subid; // Find all the subscribers to this subscription $sql = "SELECT * FROM {lm_paypal_subscribers} WHERE status = 1 AND subid = %d"; $sbs = db_query($sql, $subid); if (db_num_rows($sbs) <= 0) { // No subscribers, skip this subscription continue; } while ($sb = db_fetch_object($sbs)) { if ($sb->email_sent) { //watchdog(LM_PAYPAL_SUBSCRIPTIONS, "cron email_sent already"); continue; } $usid = $sb->usid; $nid = $sb->nid; $uid = $sb->uid; $started = $sb->started; $send_after = $started + $from_start; $time = time(); if ($send_after < $time) { //watchdog(LM_PAYPAL_SUBSCRIPTIONS, "cron email $item_name $uid $subid, update email_sent: started " . format_date($started, 'small') . ", duration " . $duration . ", nearend_days" . $nearend_days . ", send_after " . format_date($send_after, 'small') . ", now " . format_date($time, 'small')); // Email user to let them know their subscription is near its end // note: t() will be called inside lm_paypal_mail_user // Extra variables (in addition to the default ones provided) if ($so->kind == 0 || $so->kind == 2) { $variables = array('%Subscription' => $so->item_name, '%Days' => $nearend_days, '%Node' => ''); } else if ($so->kind == 1) { $variables = array('%Subscription' => $so->item_name, '%Days' => $nearend_days, '%Node' => $Node); } lm_paypal_mail_user( $uid, $uid, $so->send_user_onnearend_subject, $so->send_user_onnearend_body, $variables); // Remember we've sent them an email! $sql = "UPDATE {lm_paypal_subscribers} SET email_sent = 1 WHERE usid = %d"; $update = db_query($sql, $usid); if (!$update) { watchdog(LM_PAYPAL_SUBSCRIPTIONS, t('Failed to update to subscribers email_sent'), WATCHDOG_ERROR); } } } } }