PayService.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <?php
  2. namespace App\Services\Pay;
  3. use App\Jobs\ReportDy;
  4. use App\Models\Product\Product;
  5. use App\Models\User\User;
  6. use App\Models\User\UserEarningsLogs;
  7. use App\Models\User\UserPeroidOrderLog;
  8. use App\Services\Order\OrderService;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Redis;
  11. use Vinkla\Hashids\Facades\Hashids;
  12. use Ycpay\Factory as PayFactory;
  13. use Log;
  14. use App\Services\User\UserService;
  15. use App\Models\Channel\Channel;
  16. use App\Models\Order\Order;
  17. use App\Models\Pay\PayMerchant;
  18. use App\Services\OpenApi\OpenService;
  19. class PayService
  20. {
  21. private $openService;
  22. public function __construct(
  23. OpenService $openService
  24. )
  25. {
  26. $this->openService = $openService;
  27. }
  28. public function create_order($data)
  29. {
  30. #
  31. $channel = Channel::find($data['distribution_channel_id']);
  32. \Log::info('$channel:'.$data['distribution_channel_id'].' param:'.json_encode($channel));
  33. $pay_merchant = PayMerchant::find($channel->pay_merchant_id);
  34. if(empty($pay_merchant)){
  35. \Log::info('create_order_pay_merchant_null:'.$channel->pay_merchant_id);
  36. return $this->error('50013:参数异常');
  37. }
  38. $pay_config_info = $pay_merchant->config_info;
  39. $pay_config_infos = json_decode($pay_config_info,true);
  40. \Log::info('create_order_pay_merchant:'.json_encode($pay_merchant));
  41. \Log::info('$pay_config_infos:');\Log::info($pay_config_infos);
  42. $insert_data = [];
  43. $insert_data['uid'] = isset($data['uid'])?$data['uid']:-1;
  44. $insert_data['price'] = isset($data['price'])?$data['price']:-1;
  45. $insert_data['status'] = 'UNPAID';
  46. $insert_data['pay_num'] = OrderService::getChargeNum($data['uid']);
  47. $insert_data['product_id'] = isset($data['product_id'])?$data['product_id']:-1;
  48. $insert_data['distribution_channel_id'] = isset($data['distribution_channel_id'])?$data['distribution_channel_id']:-1;
  49. $insert_data['trade_no'] = isset($data['trade_no'])?$data['trade_no']:'';
  50. $insert_data['pay_merchant_source'] = $pay_merchant->source;
  51. $insert_data['pay_merchant_id'] = isset($pay_merchant->id)?$pay_merchant->id:0;
  52. $insert_data['transaction_id'] = isset($data['transaction_id'])?$data['transaction_id']:'';
  53. $insert_data['from_bid'] = isset($data['from_bid'])?$data['from_bid']:'';
  54. $insert_data['pay_end_at'] = null;
  55. $insert_data['order_type'] = isset($data['order_type'])?$data['order_type']:'';
  56. $insert_data['create_ip'] = isset($data['create_ip'])?$data['create_ip']:'';
  57. $insert_data['send_order_id'] = isset($data['send_order_id'])?$data['send_order_id']:-1;
  58. // 生成订单
  59. OrderService::create_order($insert_data);
  60. $pay_type = $data['pay_type'];
  61. if(in_array($pay_type, ['Ali','Weixin','Byte'])){
  62. $config = [];
  63. if($pay_type == 'Byte'){
  64. $config = [
  65. 'app_id'=>isset($pay_config_infos['appid'])?$pay_config_infos['appid']:'',
  66. 'secret'=>isset($pay_config_infos['secret'])?$pay_config_infos['secret']:'',
  67. 'notify_url'=>isset($pay_config_infos['notify_url'])?$pay_config_infos['notify_url']:'',
  68. 'token'=>isset($pay_config_infos['token'])?$pay_config_infos['token']:'',
  69. 'salt'=>isset($pay_config_infos['salt'])?$pay_config_infos['salt']:'',
  70. 'store_uid'=>isset($pay_config_infos['store_uid'])?$pay_config_infos['store_uid']:'',// 收款商户号,1个主体下面有多个商户号
  71. 'merchant_id'=>$channel->pay_merchant_id,
  72. 'out_order_no'=>$data['trade_no'],
  73. 'money'=>intval($data['price']*100),// 单位是分
  74. 'title'=>isset($pay_config_infos['title'])?$pay_config_infos['title']:'',
  75. 'desc'=>isset($pay_config_infos['desc'])?$pay_config_infos['desc']:'',
  76. ];
  77. \Log::info('$config:'.json_encode($config));
  78. $pay_client = PayFactory::getInstance($pay_type);
  79. $order_create_res = $pay_client->init($config)->applyOrderCreate($config['out_order_no'],$config['money'],$config['store_uid'],$config['title'],$config['desc'],$data['phone_type']);
  80. \Log::info('applyOrderCreateRes:'.json_encode($order_create_res));
  81. $this->report_to_douyin($data['trade_no'],$data['openid'],0);
  82. return $order_create_res;
  83. }
  84. }
  85. }
  86. function orderCallBack($trade_no, $transaction_id,$order_id)
  87. {
  88. $order_info = OrderService::getByTradeNo($trade_no);
  89. if(empty($order_info)){
  90. \Log::info('orderCallBack_orderinfo_isnull:'.$trade_no);
  91. return false;
  92. }
  93. // 防止重复回调
  94. if($order_info->status == 'PAID'){
  95. \Log::info('orderCallBack_orderinfo_hascallback_return:'.$trade_no);
  96. return false;
  97. }
  98. if(empty($transaction_id)){
  99. \Log::info('orderCallBack_transaction_id_isnull:'.$transaction_id.' trade_no:'.$trade_no);
  100. return false;
  101. }
  102. # 签名校验
  103. if(! $this->sign_check($order_info->pay_merchant_id)){
  104. \Log::info('orderCallBack_sign_check_false:'.$order_info->pay_merchant_id.' trade_no:'.$trade_no);
  105. return false;
  106. }
  107. //跨日期订单处理
  108. $is_change = $this->orderAcrossDay($order_info);
  109. DB::beginTransaction();
  110. $uid = $order_info->uid;
  111. $user = User::find($uid);
  112. $openid = $user->openid;
  113. $distribution_channel_id = $order_info->distribution_channel_id;
  114. $product_id = $order_info->product_id;
  115. $send_order_id = $order_info->send_order_id;
  116. $fee = $order_info->price;
  117. try {
  118. //获取用户充值次数
  119. $order_info->pay_num = $this->getChargeTimes($uid);
  120. $time_type_days = [
  121. 'YEAR'=>365,
  122. 'HALF_YEAR'=>180,
  123. 'QUATER'=>92,
  124. 'MONTH'=>30,
  125. 'WEEK'=>7,
  126. ];
  127. if (in_array($order_info->order_type ,['YEAR','HALF_YEAR','QUATER','MONTH','WEEK'])) {
  128. $period_order_param = [
  129. 'uid'=>$uid,
  130. 'distribution_channel_id'=>$distribution_channel_id,
  131. 'fee'=>$fee,
  132. 'send_order_id' => $send_order_id,
  133. 'add_days' => $time_type_days[$order_info->order_type],
  134. 'trade_no' => $trade_no,
  135. 'order_type' => $order_info->order_type,
  136. ];
  137. $this->vipPeriodOrder($period_order_param);
  138. }
  139. elseif ($order_info->order_type == 'RECHARGE') {
  140. \Log::info('RECHARGE_product_type:'.$order_info->order_type.' trade_no:'.$trade_no);
  141. $product = Product::detail($product_id);
  142. $this->userCharge($product, $uid, $fee, $trade_no);
  143. }
  144. else {
  145. \Log::info('invalid_product_type:'.$order_info->order_type.' trade_no:'.$trade_no);
  146. DB::rollback();
  147. return false;
  148. }
  149. $order_info->status = 'PAID';
  150. $order_info->pay_end_at = date('Y-m-d H:i:s');
  151. $order_info->transaction_id = $transaction_id;
  152. // $order_info->tiktok_order_id = $order_id;
  153. if ($is_change) {
  154. $order_info->updated_at = $order_info->pay_end_at = date('Y-m-d H:i:s', time() + 5);
  155. }
  156. $order_info->save();
  157. DB::commit();
  158. } catch (\Exception $e) {
  159. \Log::error($e);
  160. DB::rollback();
  161. return false;
  162. }
  163. // 已支付状态回传给抖音
  164. $this->report_to_douyin($order_info->trade_no,$openid,1);
  165. return true;
  166. }
  167. function report_to_douyin($trade_no,$openid,$order_status){
  168. /**
  169. 0:待支付
  170. 1:已支付
  171. 2:已取消(用户主动取消或者超时未支付导致的关单)
  172. 4:已核销(核销状态是整单核销,即一笔订单买了 3 个券,核销是指 3 个券核销的整单)
  173. 5:退款中
  174. 6:已退款
  175. 8:退款失败
  176. **/
  177. $order_detail = [
  178. 'order_id'=>$trade_no,
  179. ];
  180. \Log::info('report_to_douyin:trade_no:'.$trade_no.' openid:'.$openid.' order_status:'.$order_status);
  181. $push_res = $this->openService->getInstance()->pushOrder($openid,$order_status, $order_detail);
  182. \Log::info('report_to_douyin_res:'.json_encode($push_res,JSON_UNESCAPED_UNICODE));
  183. }
  184. function sign_check($pay_merchant_id){
  185. $pay_merchant = PayMerchant::find($pay_merchant_id);
  186. if(empty($pay_merchant)){
  187. \Log::info('orderCallBack_pay_merchant_null:'.$pay_merchant_id);
  188. return false;
  189. }
  190. $pay_config_info = $pay_merchant->config_info;
  191. $pay_config_infos = json_decode($pay_config_info,true);
  192. \Log::info('orderCallBack_pay_merchant:'.json_encode($pay_merchant));
  193. \Log::info('$pay_config_infos:');\Log::info($pay_config_infos);
  194. $config = [
  195. 'app_id'=>isset($pay_config_infos['appid'])?$pay_config_infos['appid']:'',
  196. 'secret'=>isset($pay_config_infos['secret'])?$pay_config_infos['secret']:'',
  197. 'token'=>isset($pay_config_infos['token'])?$pay_config_infos['token']:'',
  198. 'salt'=>isset($pay_config_infos['salt'])?$pay_config_infos['salt']:'',
  199. ];
  200. \Log::info('$config:'.json_encode($config));
  201. $pay_client = PayFactory::getInstance($pay_config_infos['pay_type']);
  202. $sign_check = $pay_client->init($config)->notifyCheck();
  203. if(!$sign_check){
  204. \Log::info('orderCallBack_sign_check_false:'.$pay_merchant_id);
  205. return false;
  206. }
  207. \Log::info('orderCallBack_sign_check_true:'.$pay_merchant_id);
  208. return true;
  209. }
  210. function vipPeriodOrder($data)
  211. {
  212. \Log::info('start_vipPeriodOrder:uid:'.$data['uid'].' trade_no:'.$data['trade_no']);
  213. $user = User::find($data['uid']);
  214. if(empty($user)){
  215. \Log::info('vipPeriodOrder_user_isnull:'.$data['uid']);
  216. return false;
  217. }
  218. $data['origin_end_time'] = $user->vip_limit_date;
  219. // 没设置就默认当前时间
  220. if($user->vip_limit_date == '' || $user->vip_limit_date == '0000 00:00'){
  221. $data['origin_end_time'] = date('Y-m-d H:i:s');
  222. }
  223. $insert_data = [];
  224. $insert_data['uid'] = $data['uid'];
  225. $insert_data['distribution_channel_id'] = $data['distribution_channel_id'];
  226. $insert_data['fee'] = $data['fee'];
  227. $insert_data['send_order_id'] = $data['send_order_id'];
  228. $insert_data['add_days'] = $data['add_days'];
  229. $insert_data['trade_no'] = $data['trade_no'];
  230. $insert_data['begin_time'] = $data['origin_end_time'];// 原先结束的时间作为开始
  231. $insert_data['origin_end_time'] = $data['origin_end_time'];
  232. $insert_data['end_time'] = date('Y-m-d H:i:s', strtotime($data['origin_end_time'])+$data['add_days']*86400);
  233. $insert_data['vip_type'] = $data['order_type'];
  234. // 如果已经加过时间了,就不再加、
  235. $old = UserPeroidOrderLog::getByUidAndTradeNo($data['uid'],$data['trade_no']);
  236. \Log::info('vipPeriodOrder_old:'.json_encode($old));
  237. if (!empty($old)) {
  238. \Log::info('vipPeriodOrder_old_is_not_null:');
  239. return $old;
  240. } else {
  241. \Log::info('vipPeriodOrder_old_isnull:');
  242. # 给用户延长结束时间
  243. $user->vip_limit_date = $insert_data['end_time'];
  244. $user->save();
  245. // 上报支付
  246. $this->reportPay($data['uid'], $data['trade_no']);
  247. // 给邀请者发放分成收益
  248. $this->giveRewardToFromUser($user->id, $user->from_uid, $data['fee']);
  249. # 插入记录
  250. return UserPeroidOrderLog::firstOrCreate($insert_data);
  251. }
  252. }
  253. private function userCharge($product, $uid, $fee, $trade_no)
  254. {
  255. // 获取充值配置信息
  256. $price = getProp($product, 'price');
  257. $coin = (int)$price * 100; // 充值币
  258. $given = (int)getProp($product, 'given'); // 奖励币
  259. // 更新用户书币余额
  260. $user = User::find($uid);
  261. if(empty($user)){
  262. \Log::info('userCharge_user_isnull:'.$uid);
  263. return false;
  264. }
  265. $user->balance += $coin + $given;
  266. $user->charge_balance += $coin;
  267. $user->reward_balance += $given;
  268. $user->save();
  269. // 上报支付
  270. $this->reportPay($uid, $trade_no);
  271. // 给邀请者发放分成收益
  272. $this->giveRewardToFromUser($user->id, $user->from_uid, $fee);
  273. return true;
  274. }
  275. public function reportPay($uid, $trade_no): bool
  276. {
  277. // 上报前判断是否符合条件(当日注册当日首充)
  278. if ((int)DB::table('orders')->where('uid', $uid)->where('status', 'PAID')->count('id') !== 0) {
  279. // 用户当天已有充值成功订单则跳过
  280. return false;
  281. }
  282. if (DB::table('users')->where('id', $uid)->value('created_at') < date('Y-m-d 00:00:00')) {
  283. // 用户不是当天注册则跳过
  284. return false;
  285. }
  286. # 付费回传逻辑(丢入队列)
  287. $clickid = Redis::lpop($uid.'-clickids');
  288. if ($clickid) {
  289. $report_log = DB::table('dy_report_logs')->where(['clickid'=>$clickid, 'uid'=>$uid])->first();
  290. if ($report_log) { // 有上报初始记录则继续
  291. $send_order_id = getProp($report_log, 'send_order_id');
  292. $send_order = DB::table('send_orders')->where('id', $send_order_id)->first();
  293. if ($send_order) { // 有派单信息则继续
  294. // 先将派单总数+1
  295. DB::table('send_orders')->where('id', $send_order_id)->increment('report_receive_num');
  296. // 获取当前比例和上报总数|成功数
  297. $report_percent = getProp($send_order, 'report_percent');
  298. $report_receive_num = (int)getProp($send_order, 'report_receive_num') + 1;
  299. $report_post_num = getProp($send_order, 'report_post_num');
  300. if ($report_post_num/$report_receive_num*100 < $report_percent) { // 未达到上报比例才上报
  301. $params = [
  302. 'clickid' => $clickid,
  303. 'event_type' => 'active_pay',
  304. 'context' => [
  305. 'ad' => [
  306. 'callback' => $clickid,
  307. ]
  308. ],
  309. 'timestamp' => (int)(microtime(true)*1000),
  310. 'trade_no' => $trade_no,
  311. ];
  312. ReportDy::dispatch($params)->onConnection('redis');
  313. }else {
  314. DB::table('dy_report_logs')->where(['clickid'=>$clickid, 'event_type'=>''])->update([
  315. 'trade_no' => $trade_no,
  316. 'event_type' => 'active_pay',
  317. 'callback_result' => '失败',
  318. 'remark' => '比例过滤'
  319. ]);
  320. }
  321. return true;
  322. }
  323. }
  324. }
  325. return false;
  326. }
  327. /**
  328. * 给邀请者发放分成收益
  329. * @param $uid
  330. * @param $fromUid
  331. * @param $fee
  332. * @return false|void
  333. */
  334. private function giveRewardToFromUser($uid, $fromUid, $fee)
  335. {
  336. # 给邀请者发放分成收益
  337. $from_user = User::leftJoin('user_share_configs', 'user_share_configs.level', 'users.share_level')
  338. ->where('users.id', $fromUid)->select('users.total_earnings', 'users.enable_withdraw_earnings',
  339. 'user_share_configs.share_percent')->first();
  340. if ($from_user) {
  341. $change_amount = round($fee * $from_user->share_percent, 2);
  342. // 写入收益明细表
  343. $insert_log = [
  344. 'uid' => $fromUid,
  345. 'from_uid' => $uid,
  346. 'charge_price' => $fee,
  347. 'share_percent' => $from_user->share_percent,
  348. 'withdraw_info' => '',
  349. 'charge_type' => 1,
  350. 'before_amount' => $from_user->enable_withdraw_earnings,
  351. 'change_amount' => $change_amount,
  352. 'after_amount' => $from_user->enable_withdraw_earnings + $change_amount,
  353. 'created_at' => date('Y-m-d H:i:s'),
  354. 'updated_at' => date('Y-m-d H:i:s'),
  355. ];
  356. $boolen1 = UserEarningsLogs::create($insert_log);
  357. if (!$boolen1) {
  358. \Log::error('写入收益明细失败: '.json_encode($insert_log, 256));
  359. DB::rollBack();
  360. return false;
  361. }
  362. $from_user->total_earnings += $change_amount;
  363. $from_user->enable_withdraw_earnings += $change_amount;
  364. $boolen2 = User::where('id', $fromUid)->update([
  365. 'total_earnings' => $from_user->total_earnings,
  366. 'enable_withdraw_earnings' => $from_user->enable_withdraw_earnings,
  367. 'updated_at' => date('Y-m-d H:i:s'),
  368. ]);
  369. if (!$boolen2) {
  370. \Log::error('给邀请者发放分成收益失败: from_uid: '.$fromUid);
  371. DB::rollBack();
  372. return false;
  373. }
  374. }
  375. }
  376. # 处理跨天订单,将第二天到账的订单的创建时间改为第二天,保证created_at和pay_end_at同一天,统计口径一致
  377. function orderAcrossDay($order_info)
  378. {
  379. if (date('Y-m-d', strtotime($order_info->created_at)) == date('Y-m-d')) return false;
  380. $created_at = date('Y-m-d H:i:s', strtotime($order_info->created_at));
  381. $trade_no = $order_info->trade_no;
  382. $init_order = [
  383. 'distribution_channel_id' => $order_info->distribution_channel_id,
  384. 'uid' => $order_info->uid,
  385. 'product_id' => $order_info->product_id,
  386. 'price' => $order_info->price,
  387. 'pay_type' => $order_info->pay_type,
  388. 'trade_no' => 'cd-' . $trade_no,
  389. 'pay_merchant_source' => $order_info->pay_merchant_source,
  390. 'pay_merchant_id' => $order_info->pay_merchant_id,
  391. 'create_ip' => $order_info->create_ip,
  392. 'send_order_id' => $order_info->send_order_id,
  393. 'send_order_name' => $order_info->send_order_name,
  394. 'order_type' => $order_info->order_type,
  395. 'from_bid' => $order_info->from_bid,
  396. 'from_type' => $order_info->from_type,
  397. 'activity_id' => $order_info->activity_id,
  398. 'inner_send_order_id' => $order_info->inner_send_order_id,
  399. 'status' => 'UNPAID',
  400. 'transaction_id' => '',
  401. 'pay_end_at' => '0000-00-00 00:00:00',
  402. 'created_at' => $created_at,
  403. 'updated_at' => $created_at
  404. ];
  405. try {
  406. DB::table('orders')->where('id', $order_info->id)->update([
  407. 'created_at' => date('Y-m-d H:i:s'),
  408. 'pay_end_at' => date('Y-m-d H:i:s', time() + 5),
  409. 'updated_at' => date('Y-m-d H:i:s', time() + 5),
  410. ]);
  411. //$order_info->created_at = Carbon::now();
  412. $order_info->created_at = date('Y-m-d H:i:s');
  413. DB::table('orders')->insert($init_order);
  414. } catch (\Exception $e) {
  415. \Log::error('orderAcrossDay_ept:'.$e);
  416. return false;
  417. }
  418. return true;
  419. }
  420. /**
  421. * 获取用户第几次充值
  422. * @param $uid
  423. * @return int
  424. */
  425. function getChargeTimes($uid)
  426. {
  427. $count = OrderService::getUserChargeTimes($uid);
  428. return $count + 1;
  429. }
  430. function getByTradeNo($trade_no){
  431. return Order::getByTradeNo($trade_no);
  432. }
  433. }