BxStripeConnectModule.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup StripeConnect Stripe Connect
  7. * @ingroup TridentModules
  8. *
  9. * @{
  10. */
  11. define('BX_STRIPE_CONNECT_MODE_LIVE', 'live');
  12. define('BX_STRIPE_CONNECT_MODE_TEST', 'test');
  13. define('BX_STRIPE_CONNECT_PMODE_DIRECT', 'direct');
  14. define('BX_STRIPE_CONNECT_PMODE_PLATFORM', 'platform');
  15. class BxStripeConnectModule extends BxBaseModConnectModule
  16. {
  17. function __construct(&$aModule)
  18. {
  19. parent::__construct($aModule);
  20. $this->_oConfig->init($this->_oDb);
  21. }
  22. /**
  23. * @page service Service Calls
  24. * @section bx_stripe_connect Stripe Connect
  25. * @subsection bx_stripe_connect-other Other
  26. * @subsubsection bx_stripe_connect-get_options_mode get_options_mode
  27. *
  28. * @code bx_srv('bx_stripe_connect', 'get_options_mode', [...]); @endcode
  29. *
  30. * Get an array with available modes. Is used in forms.
  31. *
  32. * @return an array with available modes represented as key => value pairs.
  33. *
  34. * @see BxStripeConnectModule::serviceGetOptionsMode
  35. */
  36. /**
  37. * @ref bx_stripe_connect-get_options_mode "get_options_mode"
  38. */
  39. public function serviceGetOptionsMode()
  40. {
  41. return array(
  42. BX_STRIPE_CONNECT_MODE_LIVE => _t('_bx_stripe_connect_option_mode_live'),
  43. BX_STRIPE_CONNECT_MODE_TEST => _t('_bx_stripe_connect_option_mode_test'),
  44. );
  45. }
  46. /**
  47. * @page service Service Calls
  48. * @section bx_stripe_connect Stripe Connect
  49. * @subsection bx_stripe_connect-other Other
  50. * @subsubsection bx_stripe_connect-get_options_api_scope get_options_api_scope
  51. *
  52. * @code bx_srv('bx_stripe_connect', 'get_options_api_scope', [...]); @endcode
  53. *
  54. * Get an array with available API scopes. Is used in forms.
  55. *
  56. * @return an array with available scopes represented as key => value pairs.
  57. *
  58. * @see BxStripeConnectModule::serviceGetOptionsApiScope
  59. */
  60. /**
  61. * @ref bx_stripe_connect-get_options_api_scope "get_options_api_scope"
  62. */
  63. public function serviceGetOptionsApiScope()
  64. {
  65. return array(
  66. 'read_only' => _t('_bx_stripe_connect_option_api_scope_read_only'),
  67. 'read_write' => _t('_bx_stripe_connect_option_api_scope_read_write'),
  68. );
  69. }
  70. /**
  71. * @page service Service Calls
  72. * @section bx_stripe_connect Stripe Connect
  73. * @subsection bx_stripe_connect-other Other
  74. * @subsubsection bx_stripe_connect-get_options_pmode_single get_options_pmode_single
  75. *
  76. * @code bx_srv('bx_stripe_connect', 'get_options_pmode_single', [...]); @endcode
  77. *
  78. * Get an array with available processing modes for single time payments. Is used in forms.
  79. *
  80. * @return an array with available modes represented as key => value pairs.
  81. *
  82. * @see BxStripeConnectModule::serviceGetOptionsPmodeSingle
  83. */
  84. /**
  85. * @ref bx_stripe_connect-get_options_pmode_single "get_options_pmode_single"
  86. */
  87. public function serviceGetOptionsPmodeSingle()
  88. {
  89. return array(
  90. BX_STRIPE_CONNECT_PMODE_DIRECT => _t('_bx_stripe_connect_option_pmode_direct'),
  91. BX_STRIPE_CONNECT_PMODE_PLATFORM => _t('_bx_stripe_connect_option_pmode_platform'),
  92. );
  93. }
  94. /**
  95. * @page service Service Calls
  96. * @section bx_stripe_connect Stripe Connect
  97. * @subsection bx_stripe_connect-other Other
  98. * @subsubsection bx_stripe_connect-get_options_pmode_recurring get_options_pmode_recurring
  99. *
  100. * @code bx_srv('bx_stripe_connect', 'get_options_pmode_recurring', [...]); @endcode
  101. *
  102. * Get an array with available processing modes for recurring payments. Is used in forms.
  103. * Note. Subscriptions (recurring payments) do not currently support the Platform mode.
  104. *
  105. * @return an array with available modes represented as key => value pairs.
  106. *
  107. * @see BxStripeConnectModule::serviceGetOptionsPmodeRecurring
  108. */
  109. /**
  110. * @ref bx_stripe_connect-get_options_pmode_recurring "get_options_pmode_recurring"
  111. */
  112. public function serviceGetOptionsPmodeRecurring()
  113. {
  114. return array(
  115. BX_STRIPE_CONNECT_PMODE_DIRECT => _t('_bx_stripe_connect_option_pmode_direct'),
  116. );
  117. }
  118. /**
  119. * @page service Service Calls
  120. * @section bx_stripe_connect Stripe Connect
  121. * @subsection bx_stripe_connect-page_blocks Page Blocks
  122. * @subsubsection bx_stripe_connect-get_block_connect get_block_connect
  123. *
  124. * @code bx_srv('bx_stripe_connect', 'get_block_connect', [...]); @endcode
  125. *
  126. * Get page block with necessary info and actions to connect/disconnect.
  127. *
  128. * @return HTML string with block content to display on the site, all necessary CSS and JS files are automatically added to the HEAD section of the site HTML.
  129. *
  130. * @see BxStripeConnectModule::serviceGetBlockConnect
  131. */
  132. /**
  133. * @ref bx_stripe_connect-get_block_connect "get_block_connect"
  134. */
  135. public function serviceGetBlockConnect()
  136. {
  137. return $this->_oTemplate->displayBlockConnect();
  138. }
  139. public function actionDelete()
  140. {
  141. $sError = _t('_bx_stripe_connect_err_perform');
  142. $iId = (int)bx_get('id');
  143. if(empty($iId))
  144. return echoJson(array('code' => 1, 'message' => $sError));
  145. if(!$this->deleteAccount($iId))
  146. return echoJson(array('code' => 2, 'message' => $sError));
  147. echoJson(array('code' => 0, 'id' => $iId, 'reload' => 1));
  148. }
  149. public function actionResult()
  150. {
  151. if(bx_get('error') !== false)
  152. $this->_oTemplate->displayErrorOccured(bx_process_input(bx_get('error_description')));
  153. $mixedResult = $this->createAccount(bx_process_input(bx_get('code')));
  154. if($mixedResult !== true)
  155. $this->_oTemplate->displayErrorOccured($mixedResult);
  156. $this->_oTemplate->displayMsg('_bx_stripe_connect_msg_connected', true);
  157. }
  158. public function actionNotify()
  159. {
  160. $iResult = $this->_processEvent();
  161. http_response_code($iResult);
  162. }
  163. public function createAccount($sCode)
  164. {
  165. $CNF = $this->_oConfig->CNF;
  166. $oResult = $this->_request($CNF['URL_API_TOKEN'], array(
  167. 'client_secret' => $this->_oConfig->getApiSecretKey(),
  168. 'code' => $sCode,
  169. 'grant_type' => 'authorization_code'
  170. ));
  171. if(!empty($oResult->error))
  172. return bx_process_input($oResult->error_description);
  173. $bResult = $this->_oDb->insertAccount(array(
  174. 'author' => bx_get_logged_profile_id(),
  175. 'added' => time(),
  176. 'user_id' => $oResult->stripe_user_id,
  177. 'public_key' => $oResult->stripe_publishable_key,
  178. 'access_token' => $oResult->access_token,
  179. 'refresh_token' => $oResult->refresh_token
  180. ));
  181. return $bResult ? $bResult : _t('_bx_stripe_connect_err_perform');
  182. }
  183. public function deleteAccount($iId)
  184. {
  185. $CNF = $this->_oConfig->CNF;
  186. $aAccount = $this->_oDb->getAccount(array('type' => 'id', 'id' => $iId));
  187. if(empty($aAccount) || !is_array($aAccount))
  188. return false;
  189. $oResult = $this->_request($CNF['URL_API_DEAUTHORIZE'], array(
  190. 'client_id' => $this->_oConfig->getApiId(),
  191. 'client_secret' => $this->_oConfig->getApiSecretKey(),
  192. 'stripe_user_id' => $aAccount['user_id'],
  193. ));
  194. if(!empty($oResult->error))
  195. return false;
  196. return $this->_oDb->deleteAccount(array('id' => $iId));
  197. }
  198. public function processAlertStripeGetButton($iObject, $iSender, $aParams)
  199. {
  200. if(empty($aParams['type']))
  201. return false;
  202. $sType = $aParams['type'];
  203. $sPayMode = $this->_oConfig->getPayMode($sType);
  204. switch($sType) {
  205. case BX_PAYMENT_TYPE_SINGLE:
  206. if($sPayMode == BX_STRIPE_CONNECT_PMODE_PLATFORM)
  207. $aParams['public_key'] = $this->_oConfig->getApiPublicKey();
  208. break;
  209. case BX_PAYMENT_TYPE_RECURRING:
  210. //TODO: Perform necessary actions here !WHEN! 'Platform' mode will be available for recurring payments.
  211. break;
  212. }
  213. return !empty($aParams['public_key']);
  214. }
  215. public function processAlertStripeCreateCustomer($iObject, $iSender, $aParams)
  216. {
  217. if(empty($aParams['type']) || empty($aParams['customer_params']['card']))
  218. return false;
  219. $sPayMode = $this->_oConfig->getPayMode($aParams['type']);
  220. try {
  221. switch($sPayMode) {
  222. case BX_STRIPE_CONNECT_PMODE_DIRECT:
  223. /*
  224. * Do nothing in this case because a Customer
  225. * should be created in Connected Stripe account.
  226. */
  227. break;
  228. case BX_STRIPE_CONNECT_PMODE_PLATFORM:
  229. \Stripe\Stripe::setApiKey($this->_oConfig->getApiSecretKey());
  230. $aParams['customer_object'] = \Stripe\Customer::create($aParams['customer_params']);
  231. break;
  232. }
  233. }
  234. catch (Exception $oException) {
  235. $aParams['customer_object'] = null;
  236. return $this->_processException('Create Customer Error: ', $oException);
  237. }
  238. return !empty($aParams['customer_object']);
  239. }
  240. public function processAlertStripeRetrieveCustomer($iObject, $iSender, $aParams)
  241. {
  242. if(empty($aParams['type']) || empty($aParams['customer_id']))
  243. return false;
  244. $sPayMode = $this->_oConfig->getPayMode($aParams['type']);
  245. try {
  246. switch($sPayMode) {
  247. case BX_STRIPE_CONNECT_PMODE_DIRECT:
  248. /*
  249. * Do nothing in this case because a Customer
  250. * should be retreived from Connected Stripe account.
  251. */
  252. break;
  253. case BX_STRIPE_CONNECT_PMODE_PLATFORM:
  254. \Stripe\Stripe::setApiKey($this->_oConfig->getApiSecretKey());
  255. $aParams['customer_object'] = \Stripe\Customer::retrieve($aParams['customer_id']);
  256. break;
  257. }
  258. }
  259. catch (Exception $oException) {
  260. $aParams['customer_object'] = null;
  261. return $this->_processException('Retrieve Customer Error: ', $oException);
  262. }
  263. return !empty($aParams['customer_object']);
  264. }
  265. public function processAlertStripeCreateCharge($iObject, $iSender, $aParams)
  266. {
  267. if(empty($iObject) || empty($aParams['charge_params']['amount']))
  268. return false;
  269. $aAccount = $this->_getAccount($iObject, BX_PAYMENT_TYPE_SINGLE);
  270. if($aAccount === false)
  271. return false;
  272. $sPayMode = $this->_oConfig->getPayMode(BX_PAYMENT_TYPE_SINGLE);
  273. $aParams['charge_params']['application_fee'] = $this->_oConfig->getFee(BX_PAYMENT_TYPE_SINGLE, (float)$aParams['charge_params']['amount']);
  274. \Stripe\Stripe::setApiKey($this->_oConfig->getApiSecretKey());
  275. try {
  276. switch($sPayMode) {
  277. case BX_STRIPE_CONNECT_PMODE_DIRECT:
  278. $aParams['charge_object'] = \Stripe\Charge::create($aParams['charge_params'], array('stripe_account' => $aAccount['user_id']));
  279. break;
  280. case BX_STRIPE_CONNECT_PMODE_PLATFORM:
  281. $aParams['charge_params']['destination'] = $aAccount['user_id'];
  282. $aParams['charge_object'] = \Stripe\Charge::create($aParams['charge_params']);
  283. break;
  284. }
  285. }
  286. catch (Exception $oException) {
  287. $aParams['charge_object'] = null;
  288. return $this->_processException('Create Charge Error: ', $oException);
  289. }
  290. return !empty($aParams['charge_object']);
  291. }
  292. public function processAlertStripeRetrieveCharge($iObject, $iSender, $aParams)
  293. {
  294. if(empty($aParams['charge_id']))
  295. return false;
  296. $sPayMode = $this->_oConfig->getPayMode(BX_PAYMENT_TYPE_SINGLE);
  297. try {
  298. switch($sPayMode) {
  299. case BX_STRIPE_CONNECT_PMODE_DIRECT:
  300. /*
  301. * Do nothing in this case because a Charge
  302. * should be stored on Connected Stripe account.
  303. */
  304. break;
  305. case BX_STRIPE_CONNECT_PMODE_PLATFORM:
  306. \Stripe\Stripe::setApiKey($this->_oConfig->getApiSecretKey());
  307. $aParams['charge_object'] = \Stripe\Charge::retrieve($aParams['charge_id']);
  308. break;
  309. }
  310. }
  311. catch (Exception $oException) {
  312. $aParams['charge_object'] = null;
  313. return $this->_processException('Retrieve Customer Error: ', $oException);
  314. }
  315. return !empty($aParams['charge_object']);
  316. }
  317. public function processAlertStripeCreateSubscription($iObject, $iSender, $aParams)
  318. {
  319. if(empty($iObject) || empty($aParams['subscription_params']['plan']))
  320. return false;
  321. $aAccount = $this->_getAccount($iObject, BX_PAYMENT_TYPE_RECURRING);
  322. if($aAccount === false)
  323. return false;
  324. $aParams['subscription_params']['application_fee_percent'] = $this->_oConfig->getFee(BX_PAYMENT_TYPE_RECURRING);
  325. \Stripe\Stripe::setApiKey($this->_oConfig->getApiSecretKey());
  326. try {
  327. $aParams['subscription_object'] = $aParams['customer']->subscriptions->create($aParams['subscription_params'], array('stripe_account' => $aAccount['user_id']));
  328. }
  329. catch (Exception $oException) {
  330. $aParams['subscription_object'] = null;
  331. return $this->_processException('Create Subscription Error: ', $oException);
  332. }
  333. return !empty($aParams['subscription_object']);
  334. }
  335. protected function _request($sUrl, $aParams = array())
  336. {
  337. $sResult = '';
  338. if(!function_exists('curl_init'))
  339. return false;
  340. $rConnect = curl_init();
  341. curl_setopt($rConnect, CURLOPT_URL, $sUrl);
  342. curl_setopt($rConnect, CURLOPT_HEADER, 0);
  343. curl_setopt($rConnect, CURLOPT_RETURNTRANSFER, 1);
  344. curl_setopt($rConnect, CURLOPT_FOLLOWLOCATION, 1);
  345. curl_setopt($rConnect, CURLOPT_POST, 1);
  346. curl_setopt($rConnect, CURLOPT_POSTFIELDS, http_build_query($aParams));
  347. if(bx_mb_strpos($sUrl, 'https') !== false) {
  348. curl_setopt($rConnect, CURLOPT_SSL_VERIFYPEER, 0);
  349. curl_setopt($rConnect, CURLOPT_SSL_VERIFYHOST, 0);
  350. }
  351. $sResult = curl_exec($rConnect);
  352. curl_close($rConnect);
  353. return json_decode($sResult);
  354. }
  355. protected function _processEvent()
  356. {
  357. $sInput = @file_get_contents("php://input");
  358. $aEvent = json_decode($sInput, true);
  359. if(empty($aEvent) || !is_array($aEvent))
  360. return 404;
  361. $sType = $aEvent['type'];
  362. if(!in_array($sType, array('account.application.deauthorized')))
  363. return 200;
  364. $this->_log('Webhooks: ' . (!empty($sType) ? $sType : ''));
  365. $this->_log($aEvent);
  366. $sMethod = '_processEvent' . bx_gen_method_name($sType, array('.', '_', '-'));
  367. if(!method_exists($this, $sMethod))
  368. return 200;
  369. return $this->$sMethod($aEvent) ? 200 : 403;
  370. }
  371. protected function _processEventAccountApplicationDeauthorized(&$aEvent)
  372. {
  373. $CNF = $this->_oConfig->CNF;
  374. $sApiId = bx_process_input($aEvent['data']['object']['id']);
  375. if(strcmp($sApiId, $this->_oConfig->getApiId()) != 0)
  376. return false;
  377. $sUserId = bx_process_input($aEvent['user_id']);
  378. if(empty($sUserId))
  379. return false;
  380. return $this->_oDb->deleteAccount(array('user_id' => $sUserId));
  381. }
  382. protected function _processException($sMessage, &$oException)
  383. {
  384. $aError = $oException->getJsonBody();
  385. $sMessage = $aError['error']['message'];
  386. if(empty($sMessage))
  387. $sMessage = $oException->getMessage();
  388. $this->_log($sMessage . $aError['error']['message']);
  389. $this->_log($aError);
  390. return false;
  391. }
  392. protected function _getAccount($iPending, $sType)
  393. {
  394. $aPending = BxDolPayments::getInstance()->getPendingOrdersInfo(array('id' => $iPending));
  395. if(!empty($aPending) && is_array($aPending) && count($aPending) == 1)
  396. $aPending = array_shift($aPending);
  397. if(empty($aPending) || !is_array($aPending) || $aPending['type'] != $sType)
  398. return false;
  399. $iSeller = (int)$aPending['seller_id'];
  400. $oSeller = BxDolProfile::getInstance($iSeller);
  401. if(!$oSeller)
  402. return false;
  403. $aAccount = $this->_oDb->getAccount(array('type' => 'author', 'author' => $iSeller));
  404. if(empty($aAccount) || !is_array($aAccount))
  405. return false;
  406. return $aAccount;
  407. }
  408. protected function _log($sContents)
  409. {
  410. if (is_array($sContents))
  411. $sContents = var_export($sContents, true);
  412. else if (is_object($sContents))
  413. $sContents = json_encode($sContents);
  414. bx_log('bx_stripe_connect', $sContents);
  415. }
  416. }
  417. /** @} */