BxSitesPaypal.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
  4. * CC-BY License - http://creativecommons.org/licenses/by/3.0/
  5. *
  6. * @defgroup Sites Sites
  7. * @ingroup TridentModules
  8. *
  9. * @{
  10. */
  11. require_once(BX_SITES_PP_DIRECTORY_PATH_API . 'PPBootStrap.php');
  12. class BxSitesPaypal
  13. {
  14. protected $_oModule;
  15. protected $bLogNote = true;
  16. protected $bLogError = true;
  17. protected $bLogException = true;
  18. protected $bDemo = true;
  19. protected $bSsl = true;
  20. protected $bIpn = true;
  21. protected $sBusiness = '';
  22. protected $sCurrencyCode = '';
  23. protected $sUrlReturn = '';
  24. protected $sUrlCancel = '';
  25. protected $fAmountTrial = 0.00;
  26. protected $fAmountRegular = 0.00;
  27. protected $sDescription = "";
  28. protected $sActivationType = BX_SITES_PP_ACTIVATION_TRIAL_PERIOD;
  29. protected $bTrial = false;
  30. protected $iTrialMaxNumber = 0;
  31. //Period values: Day, Week, SemiMonth, Month, Year
  32. protected $sPeriodTrial = '';
  33. protected $sPeriodRegular = '';
  34. protected $iFrequencyTrial = 0;
  35. protected $iFrequencyRegular = 0;
  36. protected $iBillingCyclesTrial = 0;
  37. protected $iBillingCyclesRegular = 0;
  38. public $bProfileCreated = false;
  39. public $bProfileCanceled = false;
  40. public $bPaymentDone = false;
  41. public $fPaymentAmout = 0;
  42. public $bPaymentRefund = false;
  43. public $sResultMessage = '';
  44. function __construct(&$oModule)
  45. {
  46. $this->_oModule = $oModule;
  47. $this->bDemo = $this->_oModule->_oConfig->isPaymentDemo();
  48. $this->sCurrencyCode = $this->_oModule->_oConfig->getCurrencyCode();
  49. $this->sBusiness = $this->_oModule->_oConfig->getPaymentEmail();
  50. $this->fAmountTrial = $this->_oModule->_oConfig->getPaymentPrice(BX_SITES_PP_PERIOD_TRIAL);
  51. $this->fAmountRegular = $this->_oModule->_oConfig->getPaymentPrice(BX_SITES_PP_PERIOD_REGULAR);
  52. $this->iTrialMaxNumber = $this->_oModule->_oConfig->getTrialMaxNumber();
  53. $this->sPeriodTrial = $this->_oModule->_oConfig->getPaymentPeriod(BX_SITES_PP_PERIOD_TRIAL);
  54. $this->sPeriodRegular = $this->_oModule->_oConfig->getPaymentPeriod(BX_SITES_PP_PERIOD_REGULAR);
  55. $this->iFrequencyTrial = $this->_oModule->_oConfig->getPaymentFrequency(BX_SITES_PP_PERIOD_TRIAL);
  56. $this->iFrequencyRegular = $this->_oModule->_oConfig->getPaymentFrequency(BX_SITES_PP_PERIOD_REGULAR);
  57. $this->iBillingCyclesTrial = $this->_oModule->_oConfig->getPaymentBillingCycles(BX_SITES_PP_PERIOD_TRIAL);
  58. $this->iBillingCyclesRegular = $this->_oModule->_oConfig->getPaymentBillingCycles(BX_SITES_PP_PERIOD_REGULAR);
  59. $oPermalinks = BxDolPermalinks::getInstance();
  60. $this->sUrlReturn = BX_DOL_URL_ROOT . $oPermalinks->permalink('page.php?i=site-subscribe');
  61. $this->sUrlCancel = BX_DOL_URL_ROOT . $oPermalinks->permalink('page.php?i=site-create');
  62. }
  63. function setBusiness($sValue)
  64. {
  65. $this->sBusiness = $sValue;
  66. }
  67. function setCurrency($sValue)
  68. {
  69. $this->sCurrencyCode = $sValue;
  70. }
  71. function start($iAccountId)
  72. {
  73. $this->checkTrialAllowed(array('type' => 'id', 'value' => $iAccountId));
  74. $oBADetails = new BillingAgreementDetailsType();
  75. $oBADetails->BillingType = 'RecurringPayments';
  76. $oBADetails->BillingAgreementDescription = $this->sDescription;
  77. $oBAReqDetails = new SetCustomerBillingAgreementRequestDetailsType();
  78. $oBAReqDetails->BillingAgreementDetails = $oBADetails;
  79. $oBAReqDetails->ReturnURL = $this->sUrlReturn;
  80. $oBAReqDetails->CancelURL = $this->sUrlCancel;
  81. $oBARequest = new SetCustomerBillingAgreementRequestType();
  82. $oBARequest->SetCustomerBillingAgreementRequestDetails = $oBAReqDetails;
  83. $oBAReq = new SetCustomerBillingAgreementReq();
  84. $oBAReq->SetCustomerBillingAgreementRequest = $oBARequest;
  85. $oPaypalService = new PayPalAPIInterfaceServiceService();
  86. try {
  87. $oBAResponse = $oPaypalService->SetCustomerBillingAgreement($oBAReq);
  88. } catch (Exception $oException) {
  89. $this->_logException($oException);
  90. exit;
  91. }
  92. if(!isset($oBAResponse)) {
  93. $sLog = "---\n";
  94. $sLog .= "--- Get Token: {date}\n";
  95. $sLog .= "--- Request: " . $oPaypalService->getLastRequest() . "\n";
  96. $sLog .= "--- Response: " . $oPaypalService->getLastResponse() . "\n";
  97. $sLog .= "---\n";
  98. $this->_logError($sLog);
  99. return;
  100. }
  101. $sLog = "---\n";
  102. $sLog .= "--- Get Token: {date}\n";
  103. $sLog .= "--- Status: " . $oBAResponse->Ack . "\n";
  104. $sLog .= "--- Token: " . $oBAResponse->Token;
  105. $this->_logNote($sLog);
  106. $this->_logNote($oBAResponse);
  107. $this->_logNote("---\n");
  108. if($oBAResponse->Ack == 'Success') {
  109. $sToken = $oBAResponse->Token;
  110. $oAccount = $this->_oModule->getObject('Account');
  111. $oAccount->onTokenReceived($sToken, $iAccountId);
  112. $sUrl = $this->bDemo ? 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
  113. return $sUrl . 'webscr?cmd=_customer-billing-agreement&token=' . $sToken;
  114. }
  115. }
  116. function subscribe($sTocken)
  117. {
  118. $this->checkTrialAllowed(array('type' => 'token', 'value' => $sTocken));
  119. $oRPProfileDetails = new RecurringPaymentsProfileDetailsType();
  120. $oRPProfileDetails->BillingStartDate = date(DATE_ATOM);
  121. $oPaymentBillingPeriod = new BillingPeriodDetailsType();
  122. $oPaymentBillingPeriod->BillingPeriod = $this->sPeriodRegular;
  123. $oPaymentBillingPeriod->BillingFrequency = $this->iFrequencyRegular;
  124. $oPaymentBillingPeriod->TotalBillingCycles = $this->iBillingCyclesRegular;
  125. $oPaymentBillingPeriod->Amount = new BasicAmountType($this->sCurrencyCode, $this->fAmountRegular);
  126. $oScheduleDetails = new ScheduleDetailsType();
  127. $oScheduleDetails->Description = $this->sDescription;
  128. switch($this->sActivationType) {
  129. case BX_SITES_PP_ACTIVATION_INIT_AMOUNT:
  130. $oActivationDetails = new ActivationDetailsType();
  131. $oActivationDetails->InitialAmount = new BasicAmountType($this->sCurrencyCode, $this->fAmountTrial);
  132. $oActivationDetails->FailedInitialAmountAction = 'CancelOnFailure';
  133. $oScheduleDetails->ActivationDetails = $oActivationDetails;
  134. break;
  135. case BX_SITES_PP_ACTIVATION_TRIAL_PERIOD:
  136. if(!$this->bTrial)
  137. break;
  138. $oTrialBillingPeriod = new BillingPeriodDetailsType();
  139. $oTrialBillingPeriod->BillingPeriod = $this->sPeriodTrial;
  140. $oTrialBillingPeriod->BillingFrequency = $this->iFrequencyTrial;
  141. $oTrialBillingPeriod->TotalBillingCycles = $this->iBillingCyclesTrial;
  142. $oTrialBillingPeriod->Amount = new BasicAmountType($this->sCurrencyCode, $this->fAmountTrial);
  143. $oScheduleDetails->TrialPeriod = $oTrialBillingPeriod;
  144. break;
  145. }
  146. $oScheduleDetails->PaymentPeriod = $oPaymentBillingPeriod;
  147. $oScheduleDetails->MaxFailedPayments = 1;
  148. $oScheduleDetails->AutoBillOutstandingAmount = 'NoAutoBill'; // 'AddToNextBilling'
  149. $oRPProfileRequestDetail = new CreateRecurringPaymentsProfileRequestDetailsType();
  150. $oRPProfileRequestDetail->Token = $sTocken;
  151. $oRPProfileRequestDetail->ScheduleDetails = $oScheduleDetails;
  152. $oRPProfileRequestDetail->RecurringPaymentsProfileDetails = $oRPProfileDetails;
  153. $oRPProfileRequest = new CreateRecurringPaymentsProfileRequestType();
  154. $oRPProfileRequest->CreateRecurringPaymentsProfileRequestDetails = $oRPProfileRequestDetail;
  155. $oRPProfileReq = new CreateRecurringPaymentsProfileReq();
  156. $oRPProfileReq->CreateRecurringPaymentsProfileRequest = $oRPProfileRequest;
  157. $oPaypalService = new PayPalAPIInterfaceServiceService();
  158. try {
  159. $oRPProfileResponse = $oPaypalService->CreateRecurringPaymentsProfile($oRPProfileReq);
  160. } catch (Exception $oException) {
  161. $this->_logException($oException);
  162. exit;
  163. }
  164. $sResultMessage = '_bx_sites_paypal_err_subscribe';
  165. if(!isset($oRPProfileResponse)) {
  166. $sLog = "---\n";
  167. $sLog .= "--- Create Recurring: {date}\n";
  168. $sLog .= "--- Request: " . $oPaypalService->getLastRequest() . "\n";
  169. $sLog .= "--- Response: " . $oPaypalService->getLastResponse() . "\n";
  170. $sLog .= "---\n";
  171. $this->_logError($sLog);
  172. return $sResultMessage;
  173. }
  174. $sLog = "---\n";
  175. $sLog .= "--- Create Recurring: {date}\n";
  176. $sLog .= "--- Status: " . $oRPProfileResponse->Ack . "\n";
  177. $sLog .= "--- Profile ID: " . $oRPProfileResponse->CreateRecurringPaymentsProfileResponseDetails->ProfileID;
  178. $this->_logNote($sLog);
  179. $this->_logNote($oRPProfileResponse);
  180. $this->_logNote("---\n");
  181. if($oRPProfileResponse->Ack == 'Success') {
  182. $sProfileId = $oRPProfileResponse->CreateRecurringPaymentsProfileResponseDetails->ProfileID;
  183. $oAccount = $this->_oModule->getObject('Account');
  184. $oAccount->onProfileCreated($sProfileId, $sTocken);
  185. $sResultMessage = '_bx_sites_paypal_msg_subscribe';
  186. }
  187. return $sResultMessage;
  188. }
  189. function process(&$aData)
  190. {
  191. $sLog = "---\n";
  192. $sLog .= "--- IPN Received: {date}\n";
  193. $this->_logNote($sLog);
  194. $this->_logNote($aData);
  195. $this->_logNote("---\n");
  196. $bResult = $this->processData($aData);
  197. if($this->sResultMessage != '') {
  198. $sLog = "---\n";
  199. $sLog .= "--- IPN Result Message: " . $this->sResultMessage . "\n";
  200. $sLog .= "---\n";
  201. $sMethod = $bResult ? '_logNote' : '_logError';
  202. $this->$sMethod($sLog);
  203. }
  204. return $bResult;
  205. }
  206. function performAction($sProfileId, $sAction = BX_SITES_PP_RPA_CANCEL)
  207. {
  208. if(empty($sProfileId))
  209. return false;
  210. $oRPPStatusReqestDetails = new ManageRecurringPaymentsProfileStatusRequestDetailsType();
  211. $oRPPStatusReqestDetails->Action = $sAction;
  212. $oRPPStatusReqestDetails->ProfileID = $sProfileId;
  213. $oRPPStatusReqest = new ManageRecurringPaymentsProfileStatusRequestType();
  214. $oRPPStatusReqest->ManageRecurringPaymentsProfileStatusRequestDetails = $oRPPStatusReqestDetails;
  215. $oRPPStatusReq = new ManageRecurringPaymentsProfileStatusReq();
  216. $oRPPStatusReq->ManageRecurringPaymentsProfileStatusRequest = $oRPPStatusReqest;
  217. $oPaypalService = new PayPalAPIInterfaceServiceService();
  218. try {
  219. $oRPPStatusResponse = $oPaypalService->ManageRecurringPaymentsProfileStatus($oRPPStatusReq);
  220. } catch (Exception $oException) {
  221. $this->_logException($oException);
  222. return false;
  223. }
  224. if(!isset($oRPPStatusResponse)) {
  225. $sLog = "---\n";
  226. $sLog .= "--- Perform Action: {date}\n";
  227. $sLog .= "--- Request: " . $oPaypalService->getLastRequest() . "\n";
  228. $sLog .= "--- Response: " . $oPaypalService->getLastResponse() . "\n";
  229. $sLog .= "---\n";
  230. $this->_logError($sLog);
  231. return false;
  232. }
  233. if($oRPPStatusResponse->Ack == 'Success') {
  234. $oAccount = $this->_oModule->getObject('Account');
  235. $oAccount->onActionPerformed($sProfileId, $sAction);
  236. }
  237. return true;
  238. }
  239. protected function processData(&$aData)
  240. {
  241. if($aData['txn_type'] == 'recurring_payment_profile_created') {
  242. $this->bProfileCreated = true;
  243. $this->sResultMessage = 'Recurring profile was confirmed: ' . $aData['recurring_payment_id'];
  244. //TODO: Check for Initial Payment ('initial_payment_status', 'initial_payment_amount') if it will be used.
  245. return true;
  246. }
  247. if($aData['txn_type'] == 'recurring_payment_profile_cancel') {
  248. $this->bProfileCanceled = true;
  249. $this->sResultMessage = 'Recurring profile was canceled: ' . $aData['recurring_payment_id'];
  250. return true;
  251. }
  252. if(in_array($aData['payment_status'], array('Reversed', 'Refunded'))) {
  253. $this->bPaymentRefund = true;
  254. $this->sResultMessage = 'Order canceled: ' . $aData['reason_code'];
  255. return true;
  256. }
  257. if($aData['payment_status'] != 'Completed') {
  258. $this->sResultMessage = 'Payment is not completed';
  259. return false;
  260. }
  261. if($aData['business'] != $this->sBusiness) {
  262. $this->sResultMessage = 'Wrong receiver email (required: ' . $this->sBusiness . ' received: ' . $aData['business'] . ')';
  263. return false;
  264. }
  265. $bResult = $this->verifyData($aData);
  266. if(!$bResult)
  267. return false;
  268. $sAmountVar = '';
  269. if(in_array($aData['period_type'], array(BX_SITES_PP_PERIOD_TRIAL, BX_SITES_PP_PERIOD_REGULAR)))
  270. $sAmountVar = 'fAmount' . ucfirst($aData['period_type']);
  271. if($sAmountVar == '' || !isset($this->$sAmountVar)) {
  272. $this->sResultMessage = 'Cannot determine payment period';
  273. return false;
  274. }
  275. $fAmount = $this->getAmount($aData);
  276. if($this->$sAmountVar != $fAmount) {
  277. $this->sResultMessage = 'Wrong price ' . $this->$sAmountVar . ' != ' . $fAmount;
  278. return false;
  279. }
  280. $this->fPaymentAmout = $fAmount;
  281. $this->bPaymentDone = true;
  282. $this->sResultMessage = 'Payment was successfully processed';
  283. return true;
  284. }
  285. protected function verifyData(&$aData)
  286. {
  287. $sRequest = 'cmd=_notify-validate';
  288. foreach($aData as $sKey => $sValue) {
  289. if(in_array($sKey, array('cmd', 'email', 'fullname', 'username', 'term', 'boonex_price')))
  290. continue;
  291. $sRequest .= '&'. $sKey .'='. urlencode(get_magic_quotes_gpc() ? stripslashes($sValue) : $sValue);
  292. }
  293. //--- Post back to PayPal system to validate ---//
  294. $sHeader = "POST /cgi-bin/webscr HTTP/1.0\r\n";
  295. $sHeader .= $this->bDemo ? "Host: www.sandbox.paypal.com\r\n" : "Host: www.paypal.com\r\n";
  296. $sHeader .= "Content-Type: application/x-www-form-urlencoded\r\n";
  297. $sHeader .= "Content-Length: " . strlen($sRequest) . "\r\n";
  298. $sHeader .= "Connection: close\r\n\r\n";
  299. //--- open socket ---//
  300. $iSockError = 0;
  301. $sSockError = '';
  302. $sUrl = $this->bDemo ? 'www.sandbox.paypal.com' : 'www.paypal.com';
  303. if($this->bSsl)
  304. $rHandle = fsockopen("ssl://" . $sUrl, 443, $iSockError, $sSockError, 60);
  305. else
  306. $rHandle = fsockopen("tcp://" . $sUrl, 80, $iSockError, $sSockError, 60);
  307. if(!$rHandle) {
  308. $this->sResultMessage = 'Cannot connect to remote host for validation (' . $sSockError . ')';
  309. return false;
  310. }
  311. //--- Send data ---//
  312. fputs($rHandle, $sHeader);
  313. fputs($rHandle, $sRequest);
  314. //--- Read the body data ---//
  315. $sResponse = '';
  316. while (!feof($rHandle))
  317. $sResponse .= fread($rHandle, 1024);
  318. fclose($rHandle);
  319. //--- Parse the data ---//
  320. list($sResponseHeader, $sResponseContent) = explode("\r\n\r\n", $sResponse);
  321. $aLines = explode("\n", $sResponseContent);
  322. array_walk($aLines, create_function('&$arg', "\$arg = trim(\$arg);") );
  323. if(strcmp($aLines[0], "INVALID") === 0) {
  324. $this->sResultMessage = 'Transaction verification failed: ' . $sResponse;
  325. return false;
  326. }
  327. if(strcmp($aLines[0], "VERIFIED") !== 0) {
  328. $this->sResultMessage = 'No verification status received (' . $aLines[0] . '): ' . $sResponse;
  329. return false;
  330. }
  331. return true;
  332. }
  333. protected function getAmount(&$aData)
  334. {
  335. $sCurrencyCode = $this->sCurrencyCode;
  336. $bQuantity = array_key_exists('quantity', $aData) && $aData['quantity'] > 1;
  337. $fResult = 0;
  338. if($aData['mc_currency'] == $sCurrencyCode && $sCurrencyCode == 'USD' && strlen($aData['payment_gross']))
  339. $fResult = $bQuantity ? $aData['payment_gross']/$aData['quantity'] : $aData['payment_gross'];
  340. else if($aData['mc_currency'] == $sCurrencyCode && strlen($aData['mc_gross']))
  341. $fResult = $bQuantity ? $aData['mc_gross']/$aData['quantity'] : $aData['mc_gross'];
  342. else if($aData['settle_currency'] == $sCurrencyCode && strlen($aData['settle_amount']))
  343. $fResult = $bQuantity ? $aData['settle_amount']/$aData['quantity'] : $aData['settle_amount'];
  344. return (float)$fResult;
  345. }
  346. protected function getDescription($bTrial = false)
  347. {
  348. $sCurrency = $this->sCurrencyCode;
  349. $sDuration = _t('_bx_sites_paypal_duration_' . strtolower($this->sPeriodRegular));
  350. $sAmountRegular = number_format($this->fAmountRegular, 2);
  351. if(!$bTrial)
  352. return _t('_bx_sites_paypal_product_description_wo_trial', $sDuration, $sAmountRegular, $sCurrency);
  353. if($this->fAmountTrial == 0)
  354. return _t('_bx_sites_paypal_product_description_free_trial', $sDuration, $sAmountRegular, $sCurrency);
  355. $sAmountTrial = number_format($this->fAmountTrial, 2);
  356. return _t('_bx_sites_paypal_product_description', $sDuration, $sAmountRegular, $sCurrency, $sAmountTrial, $sCurrency);
  357. }
  358. protected function checkTrialAllowed($aParams)
  359. {
  360. $aAccount = $this->_oModule->_oDb->getAccount($aParams);
  361. $iAccountTrials = isset($aAccount['owner_trials']) ? (int)$aAccount['owner_trials'] : 0;
  362. $this->bTrial = $this->iTrialMaxNumber == 0 || $this->iTrialMaxNumber > $iAccountTrials;
  363. $this->sDescription = $this->getDescription($this->bTrial);
  364. }
  365. protected function _logNote($mixedValue)
  366. {
  367. if(!$this->bLogNote)
  368. return;
  369. $this->_log($mixedValue);
  370. }
  371. protected function _logError($mixedValue)
  372. {
  373. if(!$this->bLogError)
  374. return;
  375. $sMessage = "--- Error Occured: {date}";
  376. $this->_log($sMessage);
  377. $this->_log($mixedValue);
  378. $this->_log($sMessage);
  379. }
  380. protected function _logException($oException)
  381. {
  382. if(!$this->bLogException)
  383. return;
  384. $sExType = "Unknown";
  385. $sExMessage = "";
  386. $sExMessageDetailed = "";
  387. if(isset($oException)) {
  388. $sExMessage = $oException->getMessage();
  389. $sExType = get_class($oException);
  390. if($oException instanceof PPConnectionException)
  391. $sExMessageDetailed = "Error connecting to " . $oException->getUrl();
  392. else if($oException instanceof PPMissingCredentialException || $oException instanceof PPInvalidCredentialException)
  393. $sExMessageDetailed = $oException->errorMessage();
  394. else if($oException instanceof PPConfigurationException)
  395. $sExMessageDetailed = "Invalid configuration. Please check your configuration file";
  396. }
  397. $sLog = "---\n";
  398. $sLog .= "--- PayPal SDK Exception: {date}\n";
  399. $sLog .= "--- Type: " . $sExType . "\n";
  400. $sLog .= "--- Message: " . $sExMessage . "\n";
  401. $sLog .= "--- Details: " . $sExMessageDetailed . "\n";
  402. $sLog .= "---\n";
  403. $sMessage = "--- Exception Occured: {date}\n";
  404. $this->_log($sMessage);
  405. $this->_log($sLog);
  406. $this->_log($sMessage);
  407. }
  408. protected function _log($mixedValue)
  409. {
  410. bx_import('Log', $this->_oModule->_aModule);
  411. $oLog = new BxSitesLog($this->_oModule->_oConfig->getHomePath() . 'log/pp.log');
  412. $oLog->log($mixedValue);
  413. }
  414. }
  415. /** @} */