BxStripeConnectModule.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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 BxBaseModGeneralModule
  16. {
  17. public function __construct(&$aModule)
  18. {
  19. parent::__construct($aModule);
  20. $this->_oConfig->init($this->_oDb);
  21. }
  22. /**
  23. *
  24. * SERVICE METHODS
  25. *
  26. */
  27. public function serviceGetSafeServices()
  28. {
  29. return array_merge(parent::serviceGetSafeServices(), [
  30. 'AccountSessionCreate' => '',
  31. 'AccountCreate' => '',
  32. 'AccountContinue' => '',
  33. 'AccountDelete' => ''
  34. ]);
  35. }
  36. public function serviceAccountCreate($iId)
  37. {
  38. $CNF = &$this->_oConfig->CNF;
  39. $sError = _t('_bx_stripe_connect_err_perform');
  40. if(empty($iId) || $iId != bx_get_logged_profile_id())
  41. return ['code' => 1, 'message' => $sError];
  42. $oProfile = BxDolProfile::getInstance($iId);
  43. if(!$oProfile)
  44. return ['code' => 1, 'message' => $sError];
  45. $sAccIdField = $CNF['FIELD_' . strtoupper($this->_oConfig->getMode()) . '_ACCOUNT_ID'];
  46. $oApi = BxStripeConnectApi::getInstance();
  47. $oAccount = $oApi->createAccount($oProfile->getAccountObject()->getEmail());
  48. if(!$oAccount)
  49. return ['code' => 1, 'message' => $sError];
  50. $this->_oDb->insertAccount([
  51. 'profile_id' => $iId,
  52. $sAccIdField => $oAccount->id
  53. ]);
  54. $sRefreshLink = $this->_oConfig->getRefreshLink();
  55. $sReturnLink = $this->_oConfig->getReturnLink();
  56. $oAccountLink = $oApi->createAccountLinks($oAccount->id, $sRefreshLink, $sReturnLink);
  57. if(!$oAccountLink)
  58. return ['code' => 1, 'message' => $sError];
  59. return ['code' => 0, 'redirect' => $oAccountLink->url];
  60. }
  61. public function serviceAccountContinue($iId)
  62. {
  63. $CNF = &$this->_oConfig->CNF;
  64. $sError = _t('_bx_stripe_connect_err_perform');
  65. if(empty($iId) || $iId != bx_get_logged_profile_id())
  66. return ['code' => 1, 'message' => $sError];
  67. $sModeUc = strtoupper($this->_oConfig->getMode());
  68. $sAccIdField = $CNF['FIELD_' . $sModeUc . '_ACCOUNT_ID'];
  69. $sAccDetailsField = $CNF['FIELD_' . $sModeUc . '_DETAILS'];
  70. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iId]);
  71. if(empty($aAccount) || !is_array($aAccount) || $aAccount[$sAccIdField] == '')
  72. return ['code' => 1, 'message' => $sError];
  73. if((int)$aAccount[$sAccDetailsField] != 0)
  74. return ['code' => 0];
  75. $sRefreshLink = $this->_oConfig->getRefreshLink();
  76. $sReturnLink = $this->_oConfig->getReturnLink();
  77. $oAccountLink = BxStripeConnectApi::getInstance()->createAccountLinks($aAccount[$sAccIdField], $sRefreshLink, $sReturnLink);
  78. if(!$oAccountLink)
  79. return ['code' => 1, 'message' => $sError];
  80. return ['code' => 0, 'redirect' => $oAccountLink->url];
  81. }
  82. public function serviceAccountDelete($iId)
  83. {
  84. $CNF = &$this->_oConfig->CNF;
  85. $sError = _t('_bx_stripe_connect_err_perform');
  86. if(empty($iId) || $iId != bx_get_logged_profile_id())
  87. return ['code' => 1, 'message' => $sError];
  88. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iId]);
  89. if(empty($aAccount) || !is_array($aAccount))
  90. return ['code' => 1, 'message' => $sError];
  91. $sAccIdField = $CNF['FIELD_' . strtoupper($this->_oConfig->getMode()) . '_ACCOUNT_ID'];
  92. $oAccount = BxStripeConnectApi::getInstance()->deleteAccount($aAccount[$sAccIdField]);
  93. if(!$oAccount || !$oAccount->deleted)
  94. return ['code' => 2, 'message' => _t('_bx_stripe_connect_err_delete')];
  95. $this->_oDb->deleteAccount([
  96. 'profile_id' => $iId,
  97. $sAccIdField => $oAccount->id
  98. ]);
  99. return ['code' => 0, 'reload' => 1];
  100. }
  101. public function serviceAccountSessionCreate($iProfileId)
  102. {
  103. $CNF = &$this->_oConfig->CNF;
  104. if(!$iProfileId || ($iProfileId != $this->_iProfileId && !isAdmin()))
  105. $iProfileId = $this->_iProfileId;
  106. if(!$iProfileId)
  107. return ['code' => 1];
  108. $sModeUc = strtoupper($this->_oConfig->getMode());
  109. $sAccIdField = $CNF['FIELD_' . $sModeUc . '_ACCOUNT_ID'];
  110. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iProfileId]);
  111. if(empty($aAccount) || !is_array($aAccount) || $aAccount[$sAccIdField] == '')
  112. return ['code' => 1];
  113. $oAccountSession = BxStripeConnectApi::getInstance()->createAccountSessions($aAccount[$sAccIdField]);
  114. if(!$oAccountSession)
  115. return ['code' => 1];
  116. return [
  117. 'code' => 0,
  118. 'secret' => $oAccountSession->client_secret
  119. ];
  120. }
  121. /**
  122. * @page service Service Calls
  123. * @section bx_stripe_connect Stripe Connect
  124. * @subsection bx_stripe_connect-other Other
  125. * @subsubsection bx_stripe_connect-get_options_mode get_options_mode
  126. *
  127. * @code bx_srv('bx_stripe_connect', 'get_options_mode', [...]); @endcode
  128. *
  129. * Get an array with available modes. Is used in forms.
  130. *
  131. * @return an array with available modes represented as key => value pairs.
  132. *
  133. * @see BxStripeConnectModule::serviceGetOptionsMode
  134. */
  135. /**
  136. * @ref bx_stripe_connect-get_options_mode "get_options_mode"
  137. */
  138. public function serviceGetOptionsMode()
  139. {
  140. return [
  141. BX_STRIPE_CONNECT_MODE_LIVE => _t('_bx_stripe_connect_option_mode_live'),
  142. BX_STRIPE_CONNECT_MODE_TEST => _t('_bx_stripe_connect_option_mode_test'),
  143. ];
  144. }
  145. /**
  146. * @page service Service Calls
  147. * @section bx_stripe_connect Stripe Connect
  148. * @subsection bx_stripe_connect-other Other
  149. * @subsubsection bx_stripe_connect-get_options_pmode get_options_pmode
  150. *
  151. * @code bx_srv('bx_stripe_connect', 'get_options_pmode', [...]); @endcode
  152. *
  153. * Get an array with available processing modes for payments. Is used in forms.
  154. *
  155. * @return an array with available modes represented as key => value pairs.
  156. *
  157. * @see BxStripeConnectModule::serviceGetOptionsPmode
  158. */
  159. /**
  160. * @ref bx_stripe_connect-get_options_pmode "get_options_pmode"
  161. */
  162. public function serviceGetOptionsPmode()
  163. {
  164. return [
  165. BX_STRIPE_CONNECT_PMODE_DIRECT => _t('_bx_stripe_connect_option_pmode_direct'),
  166. //BX_STRIPE_CONNECT_PMODE_PLATFORM => _t('_bx_stripe_connect_option_pmode_platform'),
  167. ];
  168. }
  169. public function serviceGetOptionValueMode($iVendorId, $aParams = [])
  170. {
  171. return _t('_bx_stripe_connect_option_mode_' . $this->_oConfig->getMode());
  172. }
  173. public function serviceGetOptionValueLiveAccountId($iVendorId, $aParams = [])
  174. {
  175. $CNF = &$this->_oConfig->CNF;
  176. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iVendorId]);
  177. if(empty($aAccount) || !is_array($aAccount))
  178. return '';
  179. return $aAccount[$CNF['FIELD_LIVE_ACCOUNT_ID']];
  180. }
  181. public function serviceGetOptionValueTestAccountId($iVendorId, $aParams = [])
  182. {
  183. $CNF = &$this->_oConfig->CNF;
  184. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iVendorId]);
  185. if(empty($aAccount) || !is_array($aAccount))
  186. return '';
  187. return $aAccount[$CNF['FIELD_TEST_ACCOUNT_ID']];
  188. }
  189. public function serviceGetConnectCode($iVendorId, $aParams = [])
  190. {
  191. return $this->_oTemplate->getConnectCode($iVendorId, $aParams);
  192. }
  193. public function serviceGetBlockPayments()
  194. {
  195. if(!isLogged())
  196. return $this->_bIsApi ? [] : '';
  197. if($this->_bIsApi)
  198. return [bx_api_get_block('stripe_connect', [
  199. 'key' => $this->_oConfig->getApiPublicKey(),
  200. 'embed' => 'payments'
  201. ])];
  202. return $this->_oTemplate->getBlockPayments();
  203. }
  204. /*
  205. * Isn't supported with the current SDK. It's still in Beta mode.
  206. * https://docs.stripe.com/connect/supported-embedded-components/reporting-chart
  207. */
  208. public function serviceGetBlockReportingChart()
  209. {
  210. if(!isLogged())
  211. return $this->_bIsApi ? [] : '';
  212. if($this->_bIsApi)
  213. return [bx_api_get_block('stripe_connect', [
  214. 'key' => $this->_oConfig->getApiPublicKey(),
  215. 'embed' => 'reporting-chart'
  216. ])];
  217. return $this->_oTemplate->getBlockReportingChart();
  218. }
  219. public function serviceGetBlockBalances()
  220. {
  221. if(!isLogged())
  222. return $this->_bIsApi ? [] : '';
  223. if($this->_bIsApi)
  224. return [bx_api_get_block('stripe_connect', [
  225. 'key' => $this->_oConfig->getApiPublicKey(),
  226. 'embed' => 'balances'
  227. ])];
  228. return $this->_oTemplate->getBlockBalances();
  229. }
  230. public function serviceGetBlockNotifications()
  231. {
  232. if(!isLogged())
  233. return $this->_bIsApi ? [] : '';
  234. if($this->_bIsApi)
  235. return [bx_api_get_block('stripe_connect', [
  236. 'key' => $this->_oConfig->getApiPublicKey(),
  237. 'embed' => 'notification-banner'
  238. ])];
  239. return $this->_oTemplate->getBlockNotifications();
  240. }
  241. /**
  242. *
  243. * ACTION METHODS
  244. *
  245. */
  246. public function actionNotify()
  247. {
  248. $iResult = $this->_processEvent();
  249. http_response_code($iResult);
  250. }
  251. public function actionAccountCreate()
  252. {
  253. return echoJson($this->serviceAccountCreate((int)bx_get('id')));
  254. }
  255. public function actionAccountContinue()
  256. {
  257. return echoJson($this->serviceAccountContinue((int)bx_get('id')));
  258. }
  259. public function actionAccountDelete()
  260. {
  261. return echoJson($this->serviceAccountDelete((int)bx_get('id')));
  262. }
  263. public function actionAccountSessionCreate($iProfileId)
  264. {
  265. return echoJson($this->serviceAccountSessionCreate($iProfileId));
  266. }
  267. /**
  268. *
  269. * OTHER METHODS
  270. *
  271. */
  272. public function hasAccount($iVendorId)
  273. {
  274. return $this->_oDb->hasAccount($iVendorId, $this->_oConfig->getMode());
  275. }
  276. public function processAlertStripeV3GetButton($iObject, $iSender, $aParams)
  277. {
  278. $CNF = &$this->_oConfig->CNF;
  279. $iVendorId = 0;
  280. if(empty($aParams['vendor_id']) || ($iVendorId = (int)$aParams['vendor_id']) == $this->_oConfig->getMainSeller())
  281. return false;
  282. $sModeUc = strtoupper($this->_oConfig->getMode());
  283. $sAccIdField = $CNF['FIELD_' . $sModeUc . '_ACCOUNT_ID'];
  284. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iVendorId]);
  285. if(empty($aAccount) || !is_array($aAccount) || empty($aAccount[$sAccIdField]))
  286. return false;
  287. $aParams['public_key'] = $this->_oConfig->getApiPublicKey();
  288. $aParams['connected_account_id'] = $aAccount[$sAccIdField];
  289. return !empty($aParams['public_key']);
  290. }
  291. public function processAlertStripeV3CreateSession($iObject, $iSender, $aParams)
  292. {
  293. $CNF = &$this->_oConfig->CNF;
  294. if(empty($aParams['session_params']))
  295. return false;
  296. try {
  297. $sModeUc = strtoupper($this->_oConfig->getMode());
  298. $sAccIdField = $CNF['FIELD_' . $sModeUc . '_ACCOUNT_ID'];
  299. $iVendorId = (int)$aParams['session_params']['metadata']['vendor'];
  300. if($iVendorId == $this->_oConfig->getMainSeller())
  301. return false;
  302. $aAccount = $this->_oDb->getAccount(['sample' => 'profile_id', 'profile_id' => $iVendorId]);
  303. if(empty($aAccount) || !is_array($aAccount) || empty($aAccount[$sAccIdField]))
  304. return false;
  305. switch($aParams['session_params']['mode']) {
  306. case 'payment':
  307. $fAmount = 0;
  308. foreach($aParams['session_params']['line_items'] as $aLineItem)
  309. $fAmount += (float)$aLineItem['price_data']['unit_amount'] * (int)$aLineItem['quantity'];
  310. if($fAmount && ($fAmount = $this->_oConfig->getFee(BX_PAYMENT_TYPE_SINGLE, $iVendorId, $fAmount))) {
  311. if(!isset($aParams['session_params']['payment_intent_data']))
  312. $aParams['session_params']['payment_intent_data'] = [];
  313. $aParams['session_params']['payment_intent_data']['application_fee_amount'] = $fAmount;
  314. }
  315. break;
  316. case 'subscription':
  317. $fAmount = 0;
  318. foreach($aParams['session_params']['line_items'] as $aLineItem)
  319. $fAmount += (float)$aLineItem['price_data']['unit_amount'] * (int)$aLineItem['quantity'];
  320. if($fAmount && ($fAmount = $this->_oConfig->getFee(BX_PAYMENT_TYPE_RECURRING, $iVendorId, $fAmount))) {
  321. if(!isset($aParams['session_params']['subscription_data']))
  322. $aParams['session_params']['subscription_data'] = [];
  323. $aParams['session_params']['subscription_data']['application_fee_percent'] = $fAmount;
  324. }
  325. break;
  326. }
  327. $aParams['session_object'] = BxStripeConnectApi::getInstance()->getStripe()->checkout->sessions->create($aParams['session_params'], [
  328. 'stripe_account' => $aAccount[$sAccIdField]
  329. ]);
  330. }
  331. catch (Exception $oException) {
  332. $aParams['session_object'] = null;
  333. return $this->_processException('Create Session Error: ', $oException);
  334. }
  335. return !empty($aParams['session_object']);
  336. }
  337. protected function _processEvent()
  338. {
  339. $sInput = @file_get_contents("php://input");
  340. $aEvent = json_decode($sInput, true);
  341. if(empty($aEvent) || !is_array($aEvent))
  342. return 404;
  343. $sType = $aEvent['type'];
  344. if(!in_array($sType, ['account.updated']))
  345. return 200;
  346. $this->_log('Webhooks: ' . (!empty($sType) ? $sType : ''));
  347. $this->_log($aEvent);
  348. $sMethod = '_processEvent' . bx_gen_method_name($sType, array('.', '_', '-'));
  349. if(!method_exists($this, $sMethod))
  350. return 200;
  351. return $this->$sMethod($aEvent) ? 200 : 403;
  352. }
  353. protected function _processEventAccountUpdated(&$aEvent)
  354. {
  355. $CNF = $this->_oConfig->CNF;
  356. $sAccountId = '';
  357. if(!isset($aEvent['data']['object']['id']) || !($sAccountId = bx_process_input($aEvent['data']['object']['id'])))
  358. return false;
  359. $sMode = $this->_oConfig->getMode();
  360. $sAccDetailsField = $CNF['FIELD_' . strtoupper($sMode) . '_DETAILS'];
  361. $aAccount = $this->_oDb->getAccount(['sample' => 'account_id', $sMode . '_account_id' => $sAccountId]);
  362. if((int)$aAccount[$sAccDetailsField] == 0 && $aEvent['data']['object']['details_submitted'])
  363. return $this->_oDb->updateAccount([$sAccDetailsField => 1], [$CNF['FIELD_ID'] => $aAccount[$CNF['FIELD_ID']]]);
  364. return true;
  365. }
  366. protected function _processException($sMessage, &$oException)
  367. {
  368. $aError = $oException->getJsonBody();
  369. $sMessage = $aError['error']['message'];
  370. if(empty($sMessage))
  371. $sMessage = $oException->getMessage();
  372. $this->_log($sMessage . $aError['error']['message']);
  373. $this->_log($aError);
  374. return false;
  375. }
  376. protected function _log($sContents)
  377. {
  378. if (is_array($sContents))
  379. $sContents = var_export($sContents, true);
  380. else if (is_object($sContents))
  381. $sContents = json_encode($sContents);
  382. bx_log('bx_stripe_connect', $sContents, BX_LOG_ERR);
  383. }
  384. }
  385. /** @} */