subscriptions.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /**
  3. * This file is the file which all subscription gateways should call
  4. * when a payment has been received - it sorts out the user status.
  5. *
  6. * Simple Machines Forum (SMF)
  7. *
  8. * @package SMF
  9. * @author Simple Machines http://www.simplemachines.org
  10. * @copyright 2012 Simple Machines contributors
  11. * @license http://www.simplemachines.org/about/smf/license.php BSD
  12. *
  13. * @version 2.1 Alpha 1
  14. */
  15. // Start things rolling by getting SMF alive...
  16. $ssi_guest_access = true;
  17. if (!file_exists(dirname(__FILE__) . '/SSI.php'))
  18. die('Cannot find SSI.php');
  19. require_once(dirname(__FILE__) . '/SSI.php');
  20. require_once($sourcedir . '/ManagePaid.php');
  21. // For any admin emailing.
  22. require_once($sourcedir . '/Subs-Admin.php');
  23. loadLanguage('ManagePaid');
  24. // If there's literally nothing coming in, let's take flight!
  25. if (empty($_POST))
  26. {
  27. header('Content-Type: text/html; charset=' . (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set']));
  28. die($txt['paid_no_data']);
  29. }
  30. // I assume we're even active?
  31. if (empty($modSettings['paid_enabled']))
  32. exit;
  33. // If we have some custom people who find out about problems load them here.
  34. $notify_users = array();
  35. if (!empty($modSettings['paid_email_to']))
  36. {
  37. foreach (explode(',', $modSettings['paid_email_to']) as $email)
  38. $notify_users[] = array(
  39. 'email' => $email,
  40. 'name' => $txt['who_member'],
  41. 'id' => 0,
  42. );
  43. }
  44. // We need to see whether we can find the correct payment gateway,
  45. // we'll going to go through all our gateway scripts and find out
  46. // if they are happy with what we have.
  47. $txnType = '';
  48. $gatewayHandles = loadPaymentGateways();
  49. foreach ($gatewayHandles as $gateway)
  50. {
  51. $gatewayClass = new $gateway['payment_class']();
  52. if ($gatewayClass->isValid())
  53. {
  54. $txnType = $gateway['code'];
  55. break;
  56. }
  57. }
  58. if (empty($txnType))
  59. generateSubscriptionError($txt['paid_unknown_transaction_type']);
  60. // Get the subscription and member ID amoungst others...
  61. @list($subscription_id, $member_id) = $gatewayClass->precheck();
  62. // Integer these just in case.
  63. $subscription_id = (int) $subscription_id;
  64. $member_id = (int) $member_id;
  65. // This would be bad...
  66. if (empty($member_id))
  67. generateSubscriptionError($txt['paid_empty_member']);
  68. // Verify the member.
  69. $request = $smcFunc['db_query']('', '
  70. SELECT id_member, member_name, real_name, email_address
  71. FROM {db_prefix}members
  72. WHERE id_member = {int:current_member}',
  73. array(
  74. 'current_member' => $member_id,
  75. )
  76. );
  77. // Didn't find them?
  78. if ($smcFunc['db_num_rows']($request) === 0)
  79. generateSubscriptionError(sprintf($txt['paid_could_not_find_member'], $member_id));
  80. $member_info = $smcFunc['db_fetch_assoc']($request);
  81. $smcFunc['db_free_result']($request);
  82. // Get the subscription details.
  83. $request = $smcFunc['db_query']('', '
  84. SELECT cost, length, name
  85. FROM {db_prefix}subscriptions
  86. WHERE id_subscribe = {int:current_subscription}',
  87. array(
  88. 'current_subscription' => $subscription_id,
  89. )
  90. );
  91. // Didn't find it?
  92. if ($smcFunc['db_num_rows']($request) === 0)
  93. generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription'], $member_id, $subscription_id));
  94. $subscription_info = $smcFunc['db_fetch_assoc']($request);
  95. $smcFunc['db_free_result']($request);
  96. // We wish to check the pending payments to make sure we are expecting this.
  97. $request = $smcFunc['db_query']('', '
  98. SELECT id_sublog, payments_pending, pending_details, end_time
  99. FROM {db_prefix}log_subscribed
  100. WHERE id_subscribe = {int:current_subscription}
  101. AND id_member = {int:current_member}
  102. LIMIT 1',
  103. array(
  104. 'current_subscription' => $subscription_id,
  105. 'current_member' => $member_id,
  106. )
  107. );
  108. if ($smcFunc['db_num_rows']($request) === 0)
  109. generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription_log'], $member_id, $subscription_id));
  110. $subscription_info += $smcFunc['db_fetch_assoc']($request);
  111. $smcFunc['db_free_result']($request);
  112. // Is this a refund etc?
  113. if ($gatewayClass->isRefund())
  114. {
  115. // If the end time subtracted by current time, is not greater
  116. // than the duration (ie length of subscription), then we close it.
  117. if ($subscription_info['end_time'] - time() < $subscription_info['length'])
  118. {
  119. // Delete user subscription.
  120. removeSubscription($subscription_id, $member_id);
  121. $subscription_act = time();
  122. $status = 0;
  123. }
  124. else
  125. {
  126. loadSubscriptions();
  127. $subscription_act = $subscription_info['end_time'] - $context['subscriptions'][$subscription_id]['num_length'];
  128. $status = 1;
  129. }
  130. // Mark it as complete so we have a record.
  131. $smcFunc['db_query']('', '
  132. UPDATE {db_prefix}log_subscribed
  133. SET end_time = {int:current_time}
  134. WHERE id_subscribe = {int:current_subscription}
  135. AND id_member = {int:current_member}
  136. AND status = {int:status}',
  137. array(
  138. 'current_time' => $subscription_act,
  139. 'current_subscription' => $subscription_id,
  140. 'current_member' => $member_id,
  141. 'status' => $status,
  142. )
  143. );
  144. // Receipt?
  145. if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2)
  146. {
  147. $replacements = array(
  148. 'NAME' => $subscription_info['name'],
  149. 'REFUNDNAME' => $member_info['member_name'],
  150. 'REFUNDUSER' => $member_info['real_name'],
  151. 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member_id,
  152. 'DATE' => timeformat(time(), false),
  153. );
  154. emailAdmins('paid_subscription_refund', $replacements, $notify_users);
  155. }
  156. }
  157. // Otherwise is it what we want, a purchase?
  158. elseif ($gatewayClass->isPayment() || $gatewayClass->isSubscription())
  159. {
  160. $cost = unserialize($subscription_info['cost']);
  161. $total_cost = $gatewayClass->getCost();
  162. $notify = false;
  163. // For one off's we want to only capture them once!
  164. if (!$gatewayClass->isSubscription())
  165. {
  166. $real_details = @unserialize($subscription_info['pending_details']);
  167. if (empty($real_details))
  168. generateSubscriptionError(sprintf($txt['paid_count_not_find_outstanding_payment'], $member_id, $subscription_id));
  169. // Now we just try to find anything pending.
  170. // We don't really care which it is as security happens later.
  171. foreach ($real_details as $id => $detail)
  172. {
  173. unset($real_details[$id]);
  174. if ($detail[3] == 'payback' && $subscription_info['payments_pending'])
  175. $subscription_info['payments_pending']--;
  176. break;
  177. }
  178. $subscription_info['pending_details'] = empty($real_details) ? '' : serialize($real_details);
  179. $smcFunc['db_query']('', '
  180. UPDATE {db_prefix}log_subscribed
  181. SET payments_pending = {int:payments_pending}, pending_details = {string:pending_details}
  182. WHERE id_sublog = {int:current_subscription_item}',
  183. array(
  184. 'payments_pending' => $subscription_info['payments_pending'],
  185. 'current_subscription_item' => $subscription_info['id_sublog'],
  186. 'pending_details' => $subscription_info['pending_details'],
  187. )
  188. );
  189. }
  190. // Is this flexible?
  191. if ($subscription_info['length'] == 'F')
  192. {
  193. $found_duration = 0;
  194. // This is a little harder, can we find the right duration?
  195. foreach ($cost as $duration => $value)
  196. {
  197. if ($duration == 'fixed')
  198. continue;
  199. elseif ((float) $value == (float) $total_cost)
  200. $found_duration = strtoupper(substr($duration, 0, 1));
  201. }
  202. // If we have the duration then we're done.
  203. if ($found_duration !== 0)
  204. {
  205. $notify = true;
  206. addSubscription($subscription_id, $member_id, $found_duration);
  207. }
  208. }
  209. else
  210. {
  211. $actual_cost = $cost['fixed'];
  212. // It must be at least the right amount.
  213. if ($total_cost != 0 && $total_cost >= $actual_cost)
  214. {
  215. // Add the subscription.
  216. $notify = true;
  217. addSubscription($subscription_id, $member_id);
  218. }
  219. }
  220. // Send a receipt?
  221. if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2 && $notify)
  222. {
  223. $replacements = array(
  224. 'NAME' => $subscription_info['name'],
  225. 'SUBNAME' => $member_info['member_name'],
  226. 'SUBUSER' => $member_info['real_name'],
  227. 'SUBEMAIL' => $member_info['email_address'],
  228. 'PRICE' => sprintf($modSettings['paid_currency_symbol'], $total_cost),
  229. 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member_id,
  230. 'DATE' => timeformat(time(), false),
  231. );
  232. emailAdmins('paid_subscription_new', $replacements, $notify_users);
  233. }
  234. }
  235. else
  236. {
  237. // Some other "valid" transaction such as:
  238. //
  239. // subscr_cancel: This IPN response (txn_type) is sent only when the subscriber cancels his/her
  240. // current subscription or the merchant cancels the subscribers subscription. In this event according
  241. // to Paypal rules the subscr_eot (End of Term) IPN response is NEVER sent, and it is up to you to
  242. // keep the subscription of the subscriber active for remaining days of subscription should they cancel
  243. // their subscription in the middle of the subscription period.
  244. //
  245. // subscr_signup: This IPN response (txn_type) is sent only the first time the user signs up for a subscription.
  246. // It then does not fire in any event later. This response is received somewhere before or after the first payment of
  247. // subscription is received (txn_type=subscr_payment) which is what we do process
  248. //
  249. // Should we log any of these ...
  250. }
  251. // In case we have anything specific to do.
  252. $gatewayClass->close();
  253. /**
  254. * Log an error then exit
  255. *
  256. * @param string $text
  257. */
  258. function generateSubscriptionError($text)
  259. {
  260. global $modSettings, $notify_users, $smcFunc;
  261. // Send an email?
  262. if (!empty($modSettings['paid_email']))
  263. {
  264. $replacements = array(
  265. 'ERROR' => $text,
  266. );
  267. emailAdmins('paid_subscription_error', $replacements, $notify_users);
  268. }
  269. // Maybe we can try to give them the post data?
  270. if (!empty($_POST))
  271. {
  272. foreach ($_POST as $key => $val)
  273. $text .= '<br />' . $smcFunc['htmlspecialchars']($key) . ': ' . $smcFunc['htmlspecialchars']($val);
  274. }
  275. // Then just log and die.
  276. log_error($text);
  277. exit;
  278. }
  279. ?>