PayService.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <?php
  2. namespace addons\HuifuPay\common\service;
  3. use addons\HuifuPay\common\models\HuifuPayOrderModel;
  4. use addons\HuifuPay\common\service\HuifuService;
  5. use addons\LuckyGroup\common\models\LuckyGroupOrder;
  6. use addons\Store\common\models\StoreCashierOrder;
  7. use addons\Store\common\models\StoreOrder;
  8. use addons\Store\common\models\StoreSettings;
  9. use api\modules\v1\forms\user\WechatForm;
  10. use common\enums\AddonsEnum;
  11. use common\enums\PayTypeEnum;
  12. use common\helpers\ArrayHelper;
  13. use common\helpers\FileHelper;
  14. use common\enums\StatusEnum;
  15. use common\models\common\PaymentTrade;
  16. use common\models\common\PaymentTradeLog;
  17. use common\models\common\PaymentTradeOrder;
  18. use common\models\common\UserBankCard;
  19. use common\models\mall\Mall;
  20. use common\models\mall\MallAddons;
  21. use common\models\order\Order;
  22. use common\models\user\UserInfo;
  23. use yii\db\Exception;
  24. use \Yii;
  25. use common\helpers\LockHelper;
  26. class PayService
  27. {
  28. const LOCK = 'update_user_huifu_order_status';
  29. public $mall_id = null;
  30. public function __construct($mall_id = null)
  31. {
  32. if (!empty($mall_id)) {
  33. $this->mall_id = $mall_id;
  34. }
  35. }
  36. // 下单
  37. /**
  38. * 作者:zengfanhua
  39. * 创建时间:2023/12/28 15:10:04
  40. * @trade Object
  41. */
  42. public function create(PaymentTrade $trade, $trade_type, $openid,$smsCode, $extra_config = [])
  43. {
  44. $trade_type = trim($trade_type);
  45. $config = \Yii::$app->services->setting->addons->get($trade->mall_id, AddonsEnum::ADDONS_NAME_HUIFU_PAY, 'basic');
  46. if (!empty($extra_config)) {
  47. foreach ($extra_config as $config_key => $config_value) {
  48. isset($config[$config_key]) && $config[$config_key] = $config_value;
  49. }
  50. }
  51. new WechatForm(['mall_id' => $trade->mall_id]);
  52. $order_setting = \Yii::$app->services->setting->mall->get($trade->mall_id, 'order');
  53. $order_auto_handle_time = 30;
  54. if (!empty($order_setting)) {
  55. $order_auto_handle_time = $order_setting['payment_expire_time'];
  56. }
  57. $expireDate = time() + ($order_auto_handle_time * 60);
  58. $huifuService = new HuifuService($trade->mall_id);
  59. $notify_url = \Yii::$app->services->setting->platform->get('system.api_url') . "/huifu-pay/notify/notify";
  60. $result = [];
  61. //分账对象
  62. $splitAccount = [];
  63. $total_price=0;
  64. if ($config['huifupay_ledger_status'] && !empty(json_decode($config['huifupay_ledger_list'], true))) {
  65. $sList = json_decode($config['huifupay_ledger_list'], true);
  66. foreach ($sList as $key => $value) {
  67. $scale = bcdiv($value['num'], 100,4);
  68. $price = bcmul($trade->getOnlinePayFee(), $scale, 2);
  69. $item = [
  70. 'num' => $price,
  71. 'id' => $value['id']
  72. ];
  73. $splitAccount[] = $item;
  74. $total_price=bcadd($price,$total_price,2);
  75. }
  76. //精度损失处理
  77. if($total_price<$trade->getOnlinePayFee())
  78. {
  79. $no_price = bcsub($trade->getOnlinePayFee(),$total_price,2);
  80. $base_price=$splitAccount[count($splitAccount)-1]['num'];
  81. $splitAccount[count($splitAccount)-1]['num']=bcadd($base_price,$no_price,2);
  82. }
  83. }
  84. // 判断是否需要延迟分账 delay_acct_flag
  85. $t_data['delay_acct_flag'] = self::getSplitAccount($trade); // 是否延迟分账
  86. // 判断是否为门店订单,如果是门店订单,那么就判断是否需要延迟分账
  87. // try {
  88. switch ($trade_type) {
  89. case 'app_alipay':
  90. case 'h5_alipay':
  91. $data = [
  92. 'goods_title' => $trade->title,
  93. 'amount' => $trade->getOnlinePayFee(),
  94. 'huifu_id' => $config['huifupay_huifu_id'],
  95. 'delay_acct_flag'=>$t_data['delay_acct_flag'],
  96. ];
  97. $result = $huifuService->preAliOrder($data, $notify_url, $expireDate, $splitAccount);
  98. break;
  99. case 'app_wechat':
  100. case 'h5_wechat':
  101. case 'wechat':
  102. $data = [
  103. 'goods_title' => $trade->title,
  104. 'amount' => $trade->getOnlinePayFee(),
  105. 'huifu_id' => $config['huifupay_huifu_id'],
  106. 'delay_acct_flag'=>$t_data['delay_acct_flag'],
  107. ];
  108. $resultPre = $huifuService->preWxOrder($data, $notify_url, $expireDate, $splitAccount);
  109. //除了小程序,获取openid,生成一个中间页打开微信小程序
  110. $out_trade_no = $trade->out_trade_no;
  111. $data = [
  112. 'path' => '/plugins4/HuifuPay/call-pay',
  113. 'query' => 'trade_sn=' . $out_trade_no . '&pre_order_id=' . $resultPre['pre_order_id'],
  114. 'env_version' => 'trial',
  115. ];
  116. $wechatService = new WechatService();
  117. $res = $wechatService->getScheme($data);
  118. if (!empty($res['errcode'])) {
  119. throw new Exception($res['errmsg']);
  120. }
  121. $result = ['openlink' => $res['openlink'], 'req_seq_id' => $resultPre['req_seq_id']];
  122. break;
  123. case 'mp_wechat':
  124. //$mp_wechat_appid = \Yii::$app->params['wechatMiniProgramConfig']['app_id'];
  125. $data = [
  126. 'goods_title' => $trade->title,
  127. 'amount' => $trade->getOnlinePayFee(),
  128. 'huifu_id' => $config['huifupay_huifu_id'],
  129. 'delay_acct_flag'=>$t_data['delay_acct_flag'],
  130. ];
  131. //$result = $huifuService->preAliOrder($data, $notify_url, $expireDate,$splitAccount);
  132. $result = $huifuService->preWxOrder($data, $notify_url, $expireDate, $splitAccount);
  133. break;
  134. case 'big_pay':
  135. $result = ['trade_no' => $trade->out_trade_no];
  136. return ['result' => $result];
  137. case 'balance_pay':
  138. $data = [
  139. 'goods_title' => $trade->title,
  140. 'amount' => $trade->getOnlinePayFee(),
  141. 'huifu_id' => $config['huifupay_huifu_id'],
  142. 'delay_acct_flag'=>$t_data['delay_acct_flag'],
  143. ];
  144. if(empty($splitAccount))
  145. {
  146. throw new Exception('余额支付必须分账,请在后台设置');
  147. }
  148. $result = $huifuService->balancePay($data, $splitAccount);
  149. if ($result['trans_stat'] == 'S') {
  150. //修改交易状态
  151. $trade->pay_type = PayTypeEnum::HUIFUPAY;
  152. $trade->is_pay = 1;
  153. $trade->pay_time = time();
  154. $trade->third_trade_no=$result['req_seq_id'];
  155. if (!$trade->save()) {
  156. throw new Exception('支付流水修改失败');
  157. }
  158. $orders = Yii::$app->services->pay->changePaymentTradeOrder($trade, PayTypeEnum::HUIFUPAY, 'balance_pay');
  159. Yii::$app->services->pay->notify($orders);
  160. return ['result' => ['trade_no' => $trade->out_trade_no]];
  161. }
  162. return ['result' => 'fail'];
  163. case 'fast_pay':
  164. if(empty($smsCode))
  165. {
  166. throw new Exception('请输入短信验证码');
  167. }
  168. $data = [
  169. 'goods_title' => $trade->title,
  170. 'sms_code' => $smsCode,
  171. 'huifu_id' => $config['huifupay_huifu_id'],
  172. 'delay_acct_flag'=>$t_data['delay_acct_flag'],
  173. ];
  174. $payOrder = HuifuPayOrderModel::find()
  175. ->where(['trade_no' => $trade->out_trade_no])
  176. ->orderBy('id desc')
  177. ->one();
  178. $data['order_id']=$payOrder['hf_order_no'];
  179. $result = $huifuService->sendPaySmsConfirm($data, $notify_url);
  180. return ['amount' => $trade->total_fee, 'order_no' => $trade->out_trade_no];
  181. case 'T_MINIAPP':
  182. //查询用户open_id
  183. $user_info = UserInfo::findOne(['user_id' => $trade['user_id'], 'status' => StatusEnum::ENABLED, 'platform' => 'mp-wx']);
  184. $data = [
  185. 'goods_title' => $trade->title,
  186. 'amount' => sprintf('%.2f', $trade->getOnlinePayFee()),
  187. 'huifu_id' => $config['huifupay_huifu_id'],
  188. 'delay_acct_flag'=>$t_data['delay_acct_flag'],
  189. 'trade_type'=>$trade_type,
  190. 'mall_id'=>$trade['mall_id'],
  191. 'open_id'=>!empty($user_info) ? $user_info['openid'] : '',
  192. ];
  193. $result = $huifuService->preWxOrder2($data, $notify_url, $expireDate, $splitAccount);
  194. if(!empty($result['resp_code']!='00000100')){
  195. throw new Exception($result['resp_desc']);
  196. }
  197. break;
  198. }
  199. // } catch (\Throwable $e) {
  200. // dd($e->getMessage());
  201. // }
  202. //创建本地关联订单
  203. try {
  204. if ($result) {
  205. //添加记录
  206. $order = new HuifuPayOrderModel();
  207. $order->mall_id = $trade->mall_id;
  208. $order->hf_order_no = $result['req_seq_id']; //汇付流水号
  209. $order->trade_no = $trade->out_trade_no;
  210. $order->created_at = time();
  211. $order->updated_at = time();
  212. $order->payment_id = $trade->id;
  213. $order->trade_type = $trade_type;
  214. $order->save();
  215. $trade->trade_type=$trade_type;
  216. $trade->save();
  217. }
  218. } catch (\Exception $e) {
  219. throw new Exception($e->getMessage());
  220. }
  221. switch ($trade_type){
  222. case 'T_MINIAPP':
  223. $pay_info = json_decode($result['pay_info'],true);
  224. $pay_info['timestamp'] = $pay_info['timeStamp'];
  225. return $pay_info;
  226. default:
  227. return ['result' => $result];
  228. }
  229. }
  230. /**
  231. * 分账
  232. * @param PaymentTrade $trade
  233. * @return array|array[]|string
  234. */
  235. public static function getSplitAccount(PaymentTrade $trade)
  236. {
  237. // 判断是否为门店订单
  238. $to = PaymentTradeOrder::findOne(['trade_id' => $trade->id]);
  239. if (strpos($to->order_no, 'C') === 0) {
  240. $order = StoreCashierOrder::findOne(['order_no' => $to->order_no]);
  241. } else {
  242. $order = Order::findOne(['order_no' => $to->order_no]);
  243. }
  244. if (!empty($order->store_id)) {
  245. // 判断是否有设置分账
  246. $storeSetting = StoreSettings::findOne(['store_id' => $order->store_id]);
  247. if ($storeSetting->store_service_fee == 2 && !empty($storeSetting->huifu_mch_no)) { // 需要分账
  248. // 这里写分账数据
  249. // return [
  250. // [
  251. // 'huifu_id' => $storeSetting->huifu_mch_no,
  252. // 'div_amt' => bcmul($trade->getOnlinePayFee(), bcsub(1,bcmul($storeSetting->store_service_ratio, 0.01, 2), 2), 2),
  253. // ]
  254. // ];
  255. return 'Y';
  256. }
  257. }
  258. return 'N';
  259. }
  260. public function bigPay(PaymentTrade $trade, $cardId)
  261. {
  262. $huifuService = new HuifuService($trade->mall_id);
  263. $config = \Yii::$app->services->setting->addons->get($trade->mall_id, AddonsEnum::ADDONS_NAME_HUIFU_PAY, 'basic');
  264. $data = [
  265. 'goods_title' => $trade->title,
  266. 'amount' => $trade->getOnlinePayFee(),
  267. 'huifu_id' => $config['huifupay_huifu_id'],
  268. ];
  269. //获得银行卡信息
  270. $card = UserBankCard::findOne(['id' => $cardId]);
  271. if (!$card) {
  272. throw new Exception('找不到银行卡');
  273. }
  274. $data['real_name'] = $card->payer_name;
  275. $data['bank_card_no'] = $card->bank_card_no;
  276. $notify_url = \Yii::$app->services->setting->platform->get('system.api_url') . "/huifu-pay/notify/notify";
  277. $result = $huifuService->bigPay($notify_url, $data);
  278. //创建本地关联订单
  279. try {
  280. if ($result) {
  281. //添加记录
  282. $order = new HuifuPayOrderModel();
  283. $order->mall_id = $trade->mall_id;
  284. $order->hf_order_no = $result['req_seq_id']; //汇付流水号
  285. $order->trade_no = $trade->out_trade_no;
  286. $order->created_at = time();
  287. $order->updated_at = time();
  288. $order->payment_id = $trade->id;
  289. $order->trade_type = 'big_pay';
  290. $order->save();
  291. $trade->trade_type='big_pay';
  292. $trade->save();
  293. }
  294. } catch (\Exception $e) {
  295. throw new Exception($e->getMessage());
  296. }
  297. return $result;
  298. }
  299. /**
  300. * 支付回调
  301. *
  302. * @param PaymentTrade $trade
  303. * @param array $params 回调参数
  304. * @return boolean
  305. */
  306. public function notify(PaymentTrade $trade, array $params)
  307. {
  308. $lock_key = self::LOCK . $trade['out_trade_no'];
  309. $identification = uniqid();
  310. $transaction = Yii::$app->db->beginTransaction();
  311. try {
  312. //加锁
  313. $res = LockHelper::lock($lock_key, $identification, 100, 100);
  314. if (!$res) throw new \PHPUnit\Util\Exception('无法获取锁');
  315. // 支付完成
  316. if ($trade->is_pay == 1) return true;
  317. // 支付金额验证
  318. if (bccomp($params['trans_amt'], $trade->getOnlinePayFee()) !== 0) throw new Exception('支付金额不一致');
  319. // 修改支付流水
  320. $trade->pay_type = $trade->isMixedPay() ? PayTypeEnum::MIXED : PayTypeEnum::HUIFUPAY;
  321. $trade->is_pay = 1;
  322. $trade->pay_time = time();
  323. $trade->third_trade_no = $params['req_seq_id'] ?? '';
  324. $trade->second_trade_no = $params['out_ord_id'] ?? '';
  325. if (!$trade->save()) throw new Exception('支付流水修改失败');
  326. // 业务回调
  327. $orders = Yii::$app->services->pay->changePaymentTradeOrder($trade, PayTypeEnum::HUIFUPAY);
  328. $transaction->commit();
  329. Yii::$app->services->pay->notify($orders);
  330. LockHelper::uLock($lock_key, $identification);
  331. return true;
  332. } catch (\Exception $e) {
  333. $transaction->rollBack();
  334. LockHelper::uLock($lock_key, $identification);
  335. // 记录报错日志
  336. FileHelper::writeLog($this->getLogPath('error'), json_encode(ArrayHelper::toArray([
  337. 'message' => $e->getMessage(),
  338. 'line' => $e->getLine(),
  339. 'file' => method_exists($e, 'getFile') ? $e->getFile() : '',
  340. 'trace' => $e->getTrace(),
  341. 'code' => $e->getCode(),
  342. ])));
  343. return false;
  344. }
  345. }
  346. /**
  347. * @param $type
  348. * @return string
  349. */
  350. protected function getLogPath($type)
  351. {
  352. return Yii::getAlias('@runtime') . "/huifu/" . date('Y_m_d') . '/' . $type . '.txt';
  353. }
  354. public function refund($params, $extra_config = [])
  355. {
  356. try {
  357. $mall_id = $this->mall_id;
  358. $refund = $params['refund'];
  359. $order_id = $refund['order_id'];
  360. $refund_source = $params['refund_source'] ?? 'Mall';
  361. $pay_type = $params['pay_type'];
  362. if ($pay_type != PayTypeEnum::HUIFUPAY) return false;
  363. if ($refund_source === 'Mall') {
  364. $order = Order::findOne(['id' => $order_id, 'mall_id' => $mall_id]);
  365. if (!empty($params['order_type']) && $params['order_type'] == 'LuckyGroup') {
  366. if (MallAddons::isInstall($mall_id, 'LuckyGroup')) {
  367. $order = LuckyGroupOrder::findOne(['id' => $order_id, 'mall_id' => $mall_id, 'status' => StatusEnum::ENABLED]);
  368. }
  369. }
  370. if (empty($order)) {
  371. throw new Exception('订单不存在');
  372. }
  373. }
  374. $payment_trade = PaymentTrade::findOne(['id' => $params['payment_trade_id'],'is_pay' => 1]);
  375. $third_trade_no = $payment_trade['third_trade_no'];
  376. $config = \Yii::$app->services->setting->addons->get($this->mall_id, AddonsEnum::ADDONS_NAME_HUIFU_PAY, 'basic');
  377. if (!empty($extra_config)) {
  378. foreach ($extra_config as $config_key => $config_value) {
  379. isset($config[$config_key]) && $config[$config_key] = $config_value;
  380. }
  381. }
  382. //分账对象
  383. $splitAccount = [];
  384. $total_price=0;
  385. if ($config['huifupay_ledger_status'] && !empty(json_decode($config['huifupay_ledger_list'], true))) {
  386. $sList = json_decode($config['huifupay_ledger_list'], true);
  387. foreach ($sList as $key => $value) {
  388. $scale = bcdiv($value['num'], 100,4);
  389. $price = bcmul($payment_trade->getOnlinePayFee(), $scale, 2);
  390. $item = [
  391. 'num' => $price,
  392. 'id' => $value['id']
  393. ];
  394. $splitAccount[] = $item;
  395. $total_price=bcadd($price,$total_price,2);
  396. }
  397. //精度损失处理
  398. if($total_price<$payment_trade->getOnlinePayFee())
  399. {
  400. $no_price = bcsub($payment_trade->getOnlinePayFee(),$total_price,2);
  401. $base_price=$splitAccount[count($splitAccount)-1]['num'];
  402. $splitAccount[count($splitAccount)-1]['num']=bcadd($base_price,$no_price,2);
  403. }
  404. }
  405. if ($payment_trade['trade_type'] == 'big_pay') {
  406. throw new Exception('大额支付不支持在线退款');
  407. }
  408. $huifuService = new HuifuService($mall_id);
  409. $data = [
  410. 'huifu_id' => $config['huifupay_huifu_id'],
  411. 'amount' => sprintf('%.2f', $params['refund_price']),
  412. 'old_req_date' => date("Ymd", $payment_trade['created_at']),
  413. 'org_req_seq_id' => $third_trade_no,
  414. ];
  415. if ($payment_trade['trade_type'] == 'balance_pay') {
  416. $result=$huifuService->balanceReturn($data,$splitAccount);
  417. }
  418. else if($payment_trade['trade_type'] == 'big_pay'||$payment_trade['trade_type'] == 'fast_pay')
  419. {
  420. $result=$huifuService->onlineRefund($data,$splitAccount);
  421. }
  422. else
  423. {
  424. $result=$huifuService->refund($data,$splitAccount);
  425. }
  426. if ($result['trans_stat']=='S'||$result['trans_stat']=='P') {
  427. return [true, 'ok'];
  428. }
  429. return [false, '退款失败'];
  430. } catch (\Exception $e) {
  431. return [false, $e->getMessage()];
  432. }
  433. }
  434. }