PaymentOrderService.php 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702
  1. <?php
  2. namespace App\Services;
  3. use App\Constants\HttpStatus;
  4. use App\Models\PaymentOrder;
  5. use App\Models\Agent\AgentPaymentOrder;
  6. use App\Services\Agent\AgentPaymentService;
  7. use App\Models\RechargeChannel;
  8. use App\Models\User;
  9. use App\Models\Wallet as WalletModel;
  10. use Exception;
  11. use Illuminate\Support\Facades\DB;
  12. use Illuminate\Support\Facades\Log;
  13. use App\Services\Payment\JdPayService;
  14. use App\Services\Payment\NoPayService;
  15. use App\Services\Payment\QianBaoService;
  16. use App\Services\Payment\SanJinService;
  17. use App\Services\Payment\ZimuPayService;
  18. use App\Services\Payment\PaymentProviderService;
  19. use App\Services\ConfigService;
  20. use App\Services\AdminRealtime\AdminRealtime;
  21. /**
  22. * 投注
  23. */
  24. class PaymentOrderService extends BaseService
  25. {
  26. const TYPE_PAY = 1; // 代收
  27. const TYPE_PAYOUT = 2; // 代付
  28. const TYPE_SELF_PAY = 3; // 手动充值
  29. const TYPE_SELF_PAYOUT = 4; // 手动提现打款
  30. const STATUS_STAY = 0; // 待处理
  31. const STATUS_PROCESS = 1; // 处理中
  32. const STATUS_SUCCESS = 2; // 成功
  33. const STATUS_FAIL = 3; // 失败
  34. const STATUS_USER = 4; // 待用户提交凭证
  35. const STATUS_AUDIT = 5; //待人工审核
  36. const STATUS_CANCEL = 6; //已撤回
  37. const STATUS_TIMEOUT = 7; //超时(在线充值处理中超过 PAY_TIMEOUT_MINUTES 未支付,仍可接收迟到的回调)
  38. const PAY_TIMEOUT_MINUTES = 30;
  39. const PAY_OPEN_STATUSES = [self::STATUS_PROCESS, self::STATUS_TIMEOUT]; // 在线充值可接收回调/查询入账的状态
  40. public static string $MODEL = PaymentOrder::class;
  41. /**
  42. * @description: 模型
  43. * @return {string}
  44. */
  45. public static function model(): string
  46. {
  47. return PaymentOrder::class;
  48. }
  49. /**
  50. * @description: 枚举
  51. * @return {*}
  52. */
  53. public static function enum(): string
  54. {
  55. return '';
  56. }
  57. /**
  58. * @description: 获取查询条件
  59. * @param {array} $search 查询内容
  60. * @return {array}
  61. */
  62. public static function getWhere(array $search = []): array
  63. {
  64. $where = [];
  65. if (isset($search['member_id']) && !empty($search['member_id'])) {
  66. $where[] = ['payment_orders.member_id', '=', $search['member_id']];
  67. }
  68. if (isset($search['channel']) && !empty($search['channel'])) {
  69. if ($search['channel'] == 'rgcz') {
  70. $search['type'] = 3;
  71. }
  72. $where[] = ['payment_orders.channel', '=', $search['channel']];
  73. }
  74. if (isset($search['type']) && !empty($search['type'])) {
  75. $where[] = ['payment_orders.type', '=', $search['type']];
  76. }
  77. if (isset($search['order_no']) && !empty($search['order_no'])) {
  78. $where[] = ['payment_orders.order_no', '=', $search['order_no']];
  79. }
  80. if (isset($search['id']) && !empty($search['id'])) {
  81. $where[] = ['payment_orders.id', '=', $search['id']];
  82. }
  83. if (isset($search['status']) && $search['status'] != '') {
  84. $where[] = ['payment_orders.status', '=', $search['status']];
  85. }
  86. if (!empty($search['account'])) {
  87. $where[] = ['users.account', 'like', '%' . trim((string)$search['account']) . '%'];
  88. }
  89. if (isset($search['first_name']) && !empty($search['first_name'])) {
  90. $where[] = ['users.first_name', 'like', "%" . $search['first_name'] . "%"];
  91. }
  92. if (isset($search['payment_type']) && $search['payment_type'] != '') {
  93. $where[] = ['payment_orders.payment_type', '=', $search['payment_type']];
  94. }
  95. return $where;
  96. }
  97. /**
  98. * @description: 查询单条数据
  99. * @param array $search
  100. * @return \App\Models\Coin|null
  101. */
  102. public static function findOne(array $search): ?PaymentOrder
  103. {
  104. return static::$MODEL::where(self::getWhere($search))->first();
  105. }
  106. /**
  107. * @description: 查询所有数据
  108. * @param array $search
  109. * @return \Illuminate\Database\Eloquent\Collection
  110. */
  111. public static function findAll(array $search = [])
  112. {
  113. return static::$MODEL::where(self::getWhere($search))->get();
  114. }
  115. /**
  116. * @description: 分页查询
  117. * @param array $search
  118. * @return array
  119. */
  120. public static function paginate(array $search = [])
  121. {
  122. $limit = isset($search['limit']) ? $search['limit'] : 15;
  123. $paginator = static::$MODEL::where(self::getWhere($search))
  124. ->join('users', 'users.member_id', '=', 'payment_orders.member_id')
  125. ->select("payment_orders.*", "users.first_name", "users.account", "users.admin_note as user_admin_note") // 提前查好需要的字段
  126. ->orderByDesc('payment_orders.created_at')
  127. ->paginate($limit);
  128. $totalAmount = 0;
  129. $totalSuccess = 0;
  130. $totalFail = 0;
  131. $list = $paginator->items();
  132. foreach ($list as $item) {
  133. $item['amount'] = floatval($item['amount']);
  134. $totalAmount += $item['amount'];
  135. if (in_array($item['status'], [1, 2])) $totalSuccess += $item['amount'];
  136. if ($item['status'] == 3) $totalFail += $item['amount'];
  137. }
  138. return [
  139. 'total' => $paginator->total(),
  140. 'total_amount' => $totalAmount,
  141. 'total_success' => $totalSuccess,
  142. 'total_fail' => $totalFail,
  143. 'data' => $list
  144. ];
  145. }
  146. /**
  147. * @description:
  148. * @param {*} $params
  149. * @return {*}
  150. */
  151. public static function submit($params = [])
  152. {
  153. $result = false;
  154. $msg['code'] = self::NOT;
  155. $msg['msg'] = '';
  156. // 2. 判断是否是更新
  157. if (!empty($params['id'])) {
  158. // 更新
  159. $info = self::findOne(['id' => $params['id']]);
  160. if (!$info) {
  161. $msg['msg'] = '数据不存在!';
  162. } else {
  163. $result = $info->update($params);
  164. $id = $params['id'];
  165. }
  166. } else {
  167. // 创建
  168. $result = $info = static::$MODEL::create($params);
  169. $id = $result->id;
  170. }
  171. if ($result) {
  172. $msg['code'] = self::YES;
  173. $msg['msg'] = '设置成功';
  174. $msg['key'] = $id;
  175. } else {
  176. $msg['msg'] = empty($msg['msg']) ? '操作失败' : $msg['msg'];
  177. }
  178. return $msg;
  179. }
  180. /**
  181. * @description: 创建代收订单
  182. * @param {*} $memberId
  183. * @param {*} $amount
  184. * @param {*} $paymentType 支付类型:支付宝、数字人民币
  185. * @return {*}
  186. */
  187. public static function createPay($memberId, $amount, $paymentType)
  188. {
  189. $result = [];
  190. $result['chat_id'] = $memberId;
  191. $result['code'] = 0;
  192. $result['url'] = '';
  193. if (!PaymentProviderService::requestEnabled((string)$paymentType, 1)) {
  194. $result['code'] = 20001;
  195. $result['text'] = '支付公司已禁用';
  196. return $result;
  197. }
  198. $user = User::query()->where('member_id', $memberId)->first();
  199. $groupId = (int)($user->recharge_channel_group_id ?? 1);
  200. $channelConfig = RechargeChannel::checkRechargeChannel($paymentType, $groupId);
  201. if ($channelConfig === false) {
  202. $result['text'] = '不支持此充值方式';
  203. $result['code'] = 20001;
  204. return $result;
  205. }
  206. $bonusSnapshot = RechargeBonusService::capture((string)$channelConfig->type, (string)$memberId);
  207. $extra = $bonusSnapshot === null ? [] : ['recharge_bonus_snapshot' => $bonusSnapshot];
  208. return self::createPayOrder(PaymentOrder::class, $memberId, $amount, $paymentType, $groupId, $channelConfig, $extra);
  209. }
  210. /**
  211. * 按通道向三方(808/NO/JD/三斤)发起代收并保存订单。会员和代理共用:
  212. * - $model:PaymentOrder(会员)或 AgentPaymentOrder(代理);
  213. * - $memberId:付款人标识,会员为 member_id,代理为 AG{代理ID};
  214. * - $extra:额外写入订单的字段;$orderPrefix:订单号前缀(代理为 ag)。
  215. * 调用方需先校验支付公司启用、通道可用。成功时返回值带 order_no。
  216. */
  217. public static function createPayOrder(string $model, $memberId, $amount, $paymentType, int $groupId, $channelConfig, array $extra = [], string $orderPrefix = ''): array
  218. {
  219. $result = [];
  220. $result['chat_id'] = $memberId;
  221. $result['code'] = 0;
  222. $result['url'] = '';
  223. $channel = ''; // 支付的通道
  224. $product = SanJinService::product($groupId);
  225. $max = (float)($channelConfig->max ?? 0);
  226. $min = (float)($channelConfig->min ?? 0);
  227. $rate = (float)($channelConfig->rate ?? 0);
  228. $selectedProduct = null;
  229. $geText = '';
  230. foreach ($product as $k => $v) {
  231. if ($v['type'] == $paymentType) {
  232. $selectedProduct = $v;
  233. if ($v['type'] == 'zfbge') {
  234. if (in_array($amount, $v['fixed'])) {
  235. $channel = $k;
  236. } else {
  237. $geText .= "❌ 此充值通道固定充值金额为" . implode(',', $v['fixed']) . "请选择上述固定金额!";
  238. }
  239. } else {
  240. if ($amount >= $v['min'] && $amount <= $v['max']) {
  241. $channel = $k;
  242. $rate = $v['rate'];
  243. }
  244. if ($min == 0) {
  245. $min = $v['min'];
  246. }
  247. if ($max == 0) {
  248. $max = $v['max'];
  249. }
  250. if ($min > $v['min']) {
  251. $min = $v['min'];
  252. }
  253. if ($max < $v['max']) {
  254. $max = $v['max'];
  255. }
  256. }
  257. }
  258. }
  259. $isDirectProvider = JdPayService::isChannel($paymentType)
  260. || NoPayService::isRechargeChannel($paymentType)
  261. || ZimuPayService::isRechargeChannel($paymentType);
  262. if ($isDirectProvider && ($amount < $min || ($max > 0 && $amount > $max))) {
  263. $result['text'] = "❌ 此充值通道充值金额{$min}-{$max}请务必输入区间金额!";
  264. $result['code'] = 20001;
  265. return $result;
  266. }
  267. // 没有找到支付通道
  268. if (empty($channel) && !JdPayService::isChannel($paymentType) && !NoPayService::isRechargeChannel($paymentType) && !ZimuPayService::isRechargeChannel($paymentType)) {
  269. // $text = "发起充值失败 \n";
  270. // $text .= "最低充值:" . $min . " \n";
  271. // $text .= "最高充值:" . $max . " \n";
  272. // $text .= "请重新填写充值的金额!";
  273. $text = "❌ 此充值通道充值金额{$min}-{$max}请务必输入区间金额!";
  274. $result['text'] = $text;
  275. if ($geText) {
  276. $result['text'] = $geText;
  277. }
  278. $result['code'] = 20001;
  279. return $result;
  280. }
  281. if (ZimuPayService::isRechargeChannel($paymentType) || ZimuPayService::isRechargeChannel($channel)) {
  282. $channel = ZimuPayService::CHANNEL_RECHARGE;
  283. $bankName = $selectedProduct['name'] ?? '808充值';
  284. $data = [];
  285. $data['type'] = self::TYPE_PAY;
  286. $data['member_id'] = $memberId;
  287. $data['amount'] = ZimuPayService::amount($amount);
  288. $data['channel'] = $channel;
  289. $data['fee'] = $amount * $rate;
  290. $data['bank_name'] = $bankName;
  291. $order_no = self::createOrderNo($orderPrefix . 'zm' . $data['type'] . '_', $memberId);
  292. $data['order_no'] = $order_no;
  293. $data['callback_url'] = ZimuPayService::getPayNotifyUrl();
  294. $data['remark'] = '充值费率:' . $rate;
  295. $data['status'] = self::STATUS_STAY;
  296. $ret = ZimuPayService::pay($amount, $order_no, (string)$memberId);
  297. Log::channel('payment')->info('ZIMU支付发起', [
  298. 'order_no' => $order_no,
  299. 'member_id' => $memberId,
  300. 'amount' => $data['amount'],
  301. 'response' => $ret,
  302. ]);
  303. if (($ret['code'] ?? -1) == 0) {
  304. $item = $ret['data'] ?? [];
  305. $payUrl = $item['payUrl'] ?? '';
  306. $data['status'] = self::STATUS_PROCESS;
  307. $data['pay_no'] = $item['orderNo'] ?? '';
  308. $data['pay_url'] = $payUrl;
  309. $data['pay_data'] = json_encode($ret, JSON_UNESCAPED_UNICODE);
  310. $model::create($data + $extra);
  311. $result['order_no'] = $order_no;
  312. if ($payUrl) {
  313. $result['image'] = asset(self::createPaymentQrCode($payUrl));
  314. }
  315. $result['text'] = "{$bankName}确认 \n";
  316. $result['text'] .= "请使用浏览器扫码或者复制支付地址到浏览器打开 \n";
  317. $result['text'] .= "支付地址:{$payUrl}\n";
  318. $result['text'] .= "支付金额:" . $amount . " RMB \n";
  319. $result['text'] .= "支付完成后请耐心等待,支付到账会第一时间通知您! \n";
  320. $result['url'] = $payUrl;
  321. } else {
  322. $result['text'] = $ret['msg'] ?? 'ZIMU支付发起失败';
  323. $result['code'] = 20002;
  324. }
  325. return $result;
  326. }
  327. if (NoPayService::isRechargeChannel($paymentType) || NoPayService::isRechargeChannel($channel)) {
  328. $channel = NoPayService::isRechargeChannel($paymentType) ? $paymentType : $channel;
  329. $bankName = $selectedProduct['name'] ?? 'NO快捷充值';
  330. $data = [];
  331. $data['type'] = self::TYPE_PAY;
  332. $data['member_id'] = $memberId;
  333. $data['amount'] = NoPayService::amount($amount);
  334. $data['channel'] = $channel;
  335. $data['fee'] = $amount * $rate;
  336. $data['bank_name'] = $bankName;
  337. $order_no = self::createOrderNo($orderPrefix . 'no' . $data['type'] . '_', $memberId);
  338. $data['order_no'] = $order_no;
  339. $data['callback_url'] = NoPayService::getDepositNotifyUrl();
  340. $data['remark'] = '充值费率:' . $rate;
  341. $data['status'] = self::STATUS_STAY;
  342. $ret = NoPayService::pay($amount, $order_no, (string)$memberId, $channel);
  343. Log::channel('payment')->info('NO支付发起', [
  344. 'order_no' => $order_no,
  345. 'member_id' => $memberId,
  346. 'amount' => $data['amount'],
  347. 'channel' => $channel,
  348. 'response' => $ret,
  349. ]);
  350. if (($ret['code'] ?? -1) == 0) {
  351. $payUrl = $ret['data']['url'] ?? '';
  352. $data['status'] = self::STATUS_PROCESS;
  353. $data['pay_url'] = $payUrl;
  354. $data['pay_data'] = json_encode($ret, JSON_UNESCAPED_UNICODE);
  355. $model::create($data + $extra);
  356. $result['order_no'] = $order_no;
  357. if ($payUrl) {
  358. $result['image'] = asset(self::createPaymentQrCode($payUrl));
  359. }
  360. $result['text'] = "{$bankName}充值确认 \n";
  361. $result['text'] .= "请使用浏览器扫码或者复制支付地址到浏览器打开 \n";
  362. $result['text'] .= "支付地址:{$payUrl}\n";
  363. $result['text'] .= "支付金额:" . $amount . " RMB \n";
  364. $result['text'] .= "支付完成后请耐心等待,支付到账会第一时间通知您! \n";
  365. $result['url'] = $payUrl;
  366. } else {
  367. $result['text'] = $ret['msg'] ?? 'NO支付发起失败';
  368. $result['code'] = 20002;
  369. }
  370. return $result;
  371. }
  372. if (JdPayService::isChannel($paymentType) || JdPayService::isChannel($channel)) {
  373. $channel = JdPayService::CHANNEL;
  374. $bankName = $selectedProduct['name'] ?? 'JD钱包';
  375. $data = [];
  376. $data['type'] = self::TYPE_PAY;
  377. $data['member_id'] = $memberId;
  378. $data['amount'] = JdPayService::amount($amount);
  379. $data['channel'] = $channel;
  380. $data['fee'] = $amount * $rate;
  381. $data['bank_name'] = $bankName;
  382. $order_no = self::createOrderNo($orderPrefix . 'jd' . $data['type'] . '_', $memberId);
  383. $data['order_no'] = $order_no;
  384. $data['callback_url'] = JdPayService::getNotifyUrl();
  385. $data['remark'] = '充值费率:' . $rate;
  386. $data['status'] = self::STATUS_STAY;
  387. $ret = JdPayService::pay($amount, $order_no);
  388. Log::channel('payment')->info('JD支付发起', $ret);
  389. if (($ret['code'] ?? 0) == 200) {
  390. $item = $ret['data'] ?? [];
  391. $payUrl = $item['url'] ?? '';
  392. $data['status'] = self::STATUS_PROCESS;
  393. $data['pay_no'] = $item['orderNo'] ?? '';
  394. $data['pay_url'] = $payUrl;
  395. $data['pay_data'] = json_encode($ret, JSON_UNESCAPED_UNICODE);
  396. $model::create($data + $extra);
  397. $result['order_no'] = $order_no;
  398. if ($payUrl) {
  399. $result['image'] = asset(self::createPaymentQrCode($payUrl));
  400. }
  401. $text = "{$bankName}充值确认 \n";
  402. $text .= "请使用浏览器扫码或者复制支付地址到浏览器打开 \n";
  403. $text .= "支付地址:{$payUrl}\n";
  404. $text .= "支付金额:" . $amount . " RMB \n";
  405. $text .= "请按实际支付金额进行付款,否则影响到账 \n";
  406. $text .= "支付完成后请耐心等待,支付到账会第一时间通知您! \n";
  407. $result['text'] = $text;
  408. $result['url'] = $payUrl;
  409. } else {
  410. $result['text'] = $ret['message'] ?? 'JD支付发起失败';
  411. $result['code'] = 20002;
  412. }
  413. return $result;
  414. }
  415. $data = [];
  416. $data['type'] = self::TYPE_PAY;
  417. $data['member_id'] = $memberId;
  418. $data['amount'] = $amount;
  419. $data['channel'] = $channel;
  420. $data['fee'] = $amount * $rate;
  421. $data['bank_name'] = SanJinService::getChannel($paymentType) ?? '';
  422. $order_no = self::createOrderNo($orderPrefix . 'sj' . $data['type'] . '_', $memberId);
  423. $data['order_no'] = $order_no;
  424. $data['callback_url'] = SanJinService::getNotifyUrl();
  425. $data['remark'] = '充值费率:' . $rate;
  426. $data['status'] = self::STATUS_STAY;
  427. $ret = SanJinService::pay(($amount * 100), $order_no, $channel);
  428. Log::error('三斤支付发起:', $ret);
  429. if ($ret['code'] == 0) {
  430. $qrCode = asset(self::createPaymentQrCode($ret['data']['payUrl']));
  431. $result['image'] = $qrCode;
  432. $item = $ret['data'];
  433. $data['status'] = self::STATUS_PROCESS;
  434. $data['pay_no'] = $item['tradeNo'];
  435. $data['pay_url'] = $item['payUrl'];
  436. $data['pay_data'] = json_encode($ret, JSON_UNESCAPED_UNICODE);
  437. $info = $model::create($data + $extra);
  438. $result['order_no'] = $order_no;
  439. // $text = "✅ 支付提示 \n";
  440. $text = "{$data['bank_name']}充值确认 \n";
  441. // $text .= "支付方式:{$data['bank_name']} \n";
  442. $text .= "请使用浏览器扫码或者复制支付地址到浏览器打开 \n";
  443. $text .= "支付地址:{$ret['data']['payUrl']}\n";
  444. $text .= "支付金额:" . $amount . " RMB \n";
  445. $text .= "请按实际支付金额进行付款,否则影响到账 \n";
  446. $text .= "支付完成后请耐心等待,支付到账会第一时间通知您! \n";
  447. $result['text'] = $text;
  448. $result['url'] = $ret['data']['payUrl'];
  449. } else {
  450. $result['text'] = $ret['message'];
  451. $result['code'] = 20002;
  452. }
  453. return $result;
  454. }
  455. /**
  456. * @description: 接收支付的通知
  457. * @param {*} $params
  458. * @return {*}
  459. */
  460. public static function receivePay($params)
  461. {
  462. if (ZimuPayService::getAppId() !== '' && ($params['appId'] ?? '') === ZimuPayService::getAppId()) {
  463. $info = self::findPayOrder((string)($params['mchOrderNo'] ?? ''));
  464. if (!$info || $info->type != self::TYPE_PAY || !ZimuPayService::isRechargeChannel($info->channel)) {
  465. Log::error('ZIMU充值回调订单不存在或类型不匹配', [
  466. 'mch_order_no' => $params['mchOrderNo'] ?? '',
  467. 'order_id' => $info->id ?? null,
  468. 'order_type' => $info->type ?? null,
  469. 'channel' => $info->channel ?? null,
  470. ]);
  471. return false;
  472. }
  473. if (!ZimuPayService::verifyNotify($params)) {
  474. Log::error('ZIMU充值回调验签失败', [
  475. 'mch_order_no' => $params['mchOrderNo'] ?? '',
  476. 'app_id' => $params['appId'] ?? '',
  477. 'signature' => ZimuPayService::notifySignatureDiagnostics($params),
  478. ]);
  479. return false;
  480. }
  481. if (bccomp(ZimuPayService::amount($info->amount), ZimuPayService::amount($params['amount'] ?? 0), 2) !== 0) {
  482. Log::error('ZIMU充值回调金额不一致', [
  483. 'mch_order_no' => $params['mchOrderNo'] ?? '',
  484. 'order_amount' => $info->amount,
  485. 'callback_amount' => $params['amount'] ?? null,
  486. ]);
  487. return false;
  488. }
  489. if (!in_array((int)$info->status, self::PAY_OPEN_STATUSES, true)) {
  490. return true;
  491. }
  492. $processed = self::applyPayCallback(
  493. $info,
  494. ZimuPayService::amount($info->amount),
  495. (string)$params['status'],
  496. ZimuPayService::PAY_STATUS_SUCCESS,
  497. ZimuPayService::PAY_STATUS_FAIL,
  498. $params
  499. );
  500. if ($processed && (string)$params['status'] === ZimuPayService::PAY_STATUS_SUCCESS) {
  501. self::notifyUser($info->member_id, "✅ 支付成功 \n充值金额:{$info->amount} RMB \n订单号:{$info->order_no} \n您充值的金额已到账,请注意查收!");
  502. }
  503. return true;
  504. }
  505. if (NoPayService::getDepositMerchantId() !== '' && ($params['appId'] ?? '') === NoPayService::getDepositMerchantId()) {
  506. $info = self::findPayOrder((string)($params['merchantOrderNo'] ?? ''));
  507. if (!$info || $info->type != self::TYPE_PAY || !NoPayService::isRechargeChannel($info->channel)) {
  508. Log::error('NO钱包充值回调订单不存在或类型不匹配', [
  509. 'merchant_order_no' => $params['merchantOrderNo'] ?? '',
  510. 'order_id' => $info->id ?? null,
  511. 'order_type' => $info->type ?? null,
  512. 'channel' => $info->channel ?? null,
  513. ]);
  514. return false;
  515. }
  516. if (!NoPayService::verifyDepositNotify($params)) {
  517. Log::error('NO钱包充值回调验签失败', [
  518. 'merchant_order_no' => $params['merchantOrderNo'] ?? '',
  519. 'app_id' => $params['appId'] ?? '',
  520. 'signature' => NoPayService::depositNotifySignatureDiagnostics($params),
  521. ]);
  522. return false;
  523. }
  524. if (bccomp(NoPayService::amount($info->amount), NoPayService::amount($params['amount'] ?? 0), 2) !== 0) {
  525. Log::error('NO钱包充值回调金额不一致', [
  526. 'merchant_order_no' => $params['merchantOrderNo'] ?? '',
  527. 'order_amount' => $info->amount,
  528. 'callback_amount' => $params['amount'] ?? null,
  529. ]);
  530. return false;
  531. }
  532. if (!in_array((int)$info->status, self::PAY_OPEN_STATUSES, true)) {
  533. return true;
  534. }
  535. $processed = self::applyPayCallback(
  536. $info,
  537. NoPayService::amount($info->amount),
  538. (string)$params['state'],
  539. NoPayService::STATE_SUCCESS,
  540. NoPayService::STATE_FAIL,
  541. $params
  542. );
  543. if ($processed && (string)$params['state'] === NoPayService::STATE_SUCCESS) {
  544. self::notifyUser($info->member_id, "✅ 支付成功 \n充值金额:{$info->amount} RMB \n订单号:{$info->order_no} \n您充值的金额已到账,请注意查收!");
  545. }
  546. return true;
  547. }
  548. if (($params['userCode'] ?? '') === JdPayService::getMerchantId()) {
  549. $info = self::findPayOrder((string)($params['orderCode'] ?? ''), true);
  550. if (!$info || $info->type != self::TYPE_PAY) {
  551. return false;
  552. }
  553. if (!JdPayService::verifyPayNotify($params)) {
  554. return false;
  555. }
  556. if (bccomp(JdPayService::amount($info->amount), JdPayService::amount($params['amount'] ?? 0), 2) !== 0) {
  557. return false;
  558. }
  559. if (!in_array((int)$info->status, self::PAY_OPEN_STATUSES, true)) {
  560. return true;
  561. }
  562. $processed = self::applyPayCallback($info, JdPayService::amount($info->amount), (string)$params['status'], JdPayService::PAY_STATUS_SUCCESS, JdPayService::PAY_STATUS_FAIL, $params);
  563. if ($processed && (string)$params['status'] === JdPayService::PAY_STATUS_SUCCESS) {
  564. $text = "✅ 支付成功 \n";
  565. $text .= "充值金额:{$info->amount} RMB \n";
  566. $text .= "订单号:{$info->order_no} \n";
  567. $text .= "您充值的金额已到账,请注意查收!";
  568. self::notifyUser($info->member_id, $text);
  569. }
  570. return true;
  571. }
  572. // 判断商户号
  573. if (($params['mchId'] ?? '') == SanJinService::getMerchantId()) {
  574. $must = ['mchId', 'productId', 'tradeNo', 'outTradeNo', 'amount', 'payAmount', 'state', 'createTime', 'payTime'];
  575. $info = self::findPayOrder((string)($params['outTradeNo'] ?? ''));
  576. if ($info) {
  577. // 平台以分计算转成元
  578. $payAmount = $params['payAmount'] / 100;
  579. // 判断金额是不是正确认
  580. if ($info->amount != $payAmount) {
  581. $text = '❌ 支付失败提醒 \n';
  582. $text .= "订单金额:{$info->amount} \n";
  583. $text .= "实际支付:{$payAmount} \n";
  584. $text .= "订单号:{$params['outTradeNo']} \n";
  585. $text .= "失败原因:支付金额与订单金额不一致 \n";
  586. $text .= "请联系客服处理!";
  587. self::notifyUser($info->member_id, $text);
  588. return false;
  589. }
  590. if ($params['sign'] != SanJinService::signature($params, $must)) {
  591. return false;
  592. }
  593. if (!in_array((int)$info->status, self::PAY_OPEN_STATUSES, true)) {
  594. return false;
  595. }
  596. // 付款
  597. if ($info->type == self::TYPE_PAY) {
  598. $processed = self::applyPayCallback($info, $payAmount, (string)$params['state'], '1', null, $params);
  599. if ($processed && $params['state'] == 1) {
  600. $text = "✅ 支付成功 \n";
  601. $text .= "充值金额:{$payAmount} RMB \n";
  602. $text .= "订单号:{$params['outTradeNo']} \n";
  603. $text .= "您充值的金额已到账,请注意查收!";
  604. self::notifyUser($info->member_id, $text);
  605. } else {
  606. $text = "❌ 支付失败 \n";
  607. $text .= "充值金额:{$payAmount} RMB \n";
  608. $text .= "订单号:{$params['outTradeNo']} \n";
  609. }
  610. return true;
  611. }
  612. }
  613. }
  614. }
  615. /**
  616. * 按订单号找在线充值订单:ag 开头的是代理订单(agent_payment_orders),其余是会员订单。
  617. * JD 回调的 orderCode 可能是三方单号,$byPayNo 时再按 pay_no 查。
  618. */
  619. public static function findPayOrder(string $orderNo, bool $byPayNo = false): ?PaymentOrder
  620. {
  621. if ($orderNo === '') return null;
  622. // 按 ag 前缀先查代理订单,找不到再查另一张表,避免前缀约定被打破时回调丢单。
  623. $models = str_starts_with($orderNo, AgentPaymentOrder::ORDER_PREFIX)
  624. ? [AgentPaymentOrder::class, PaymentOrder::class] : [PaymentOrder::class, AgentPaymentOrder::class];
  625. $order = $models[0]::where('order_no', $orderNo)->first() ?: $models[1]::where('order_no', $orderNo)->first();
  626. if (!$order && $byPayNo) {
  627. $order = PaymentOrder::where('pay_no', $orderNo)->first() ?: AgentPaymentOrder::where('pay_no', $orderNo)->first();
  628. }
  629. return $order;
  630. }
  631. private static function applyPayCallback(PaymentOrder $info, $payAmount, string $status, string $successStatus, ?string $failStatus, array $params): bool
  632. {
  633. return DB::transaction(function () use ($info, $payAmount, $status, $successStatus, $failStatus, $params) {
  634. $order = $info::query()->where('id', $info->id)->lockForUpdate()->first();
  635. if (!$order || (int)$order->type !== self::TYPE_PAY || !in_array((int)$order->status, self::PAY_OPEN_STATUSES, true)) {
  636. return false;
  637. }
  638. $order->callback_data = json_encode($params, JSON_UNESCAPED_UNICODE);
  639. $order->state = $status;
  640. if ($status === $successStatus) {
  641. $order->status = self::STATUS_SUCCESS;
  642. $order->save();
  643. if ($order instanceof AgentPaymentOrder) {
  644. // 代理订单入代理钱包(合营计入保证金),不发充值优惠。
  645. app(AgentPaymentService::class)->credit($order, (string)$payAmount, 'system', null);
  646. return true;
  647. }
  648. $wallet = WalletModel::where('member_id', $order->member_id)->lockForUpdate()->first();
  649. if (!$wallet) {
  650. throw new Exception('钱包不存在', HttpStatus::CUSTOM_ERROR);
  651. }
  652. $balance = $wallet->available_balance;
  653. $availableBalance = bcadd($balance, $payAmount, 10);
  654. $wallet->available_balance = $availableBalance;
  655. $wallet->save();
  656. $principal = BalanceLogService::addLog($order->member_id, $payAmount, $balance, $availableBalance, '三方充值', $order->id, '');
  657. RechargeBonusService::grant('payment_order', $order, $principal);
  658. // Deposit hooks can grant an immediately available first-deposit reward.
  659. $wallet->refresh();
  660. return true;
  661. }
  662. if ($failStatus === null || $status === $failStatus) {
  663. $order->status = self::STATUS_FAIL;
  664. }
  665. $order->save();
  666. return true;
  667. }, 3);
  668. }
  669. /**
  670. * 拒绝提现
  671. * @description 改变订单状态为失败,将金额退给用户,并记录提现退款的资金日
  672. * @param int $orderId 订单ID PaymentOrder 表的ID
  673. * @param string $remark 失败原因
  674. * @return array
  675. */
  676. public static function withdrawalFailed($orderId, $remark): array
  677. {
  678. try {
  679. DB::transaction(function () use ($orderId, $remark) {
  680. $order = PaymentOrder::where('id', $orderId)
  681. ->whereIn('type', [2,4])
  682. ->where('status', self::STATUS_STAY)
  683. ->lockForUpdate()
  684. ->first();
  685. if (!$order) throw new Exception("订单不存在_{$orderId}", HttpStatus::CUSTOM_ERROR);
  686. // 更新提现记录状态为失败
  687. $order->status = self::STATUS_FAIL;
  688. $order->remark = $remark;
  689. if (!$order->save()) {
  690. throw new Exception("更新失败,请重试", HttpStatus::CUSTOM_ERROR);
  691. }
  692. // 冻结余额退回可用余额,并记录退款日志
  693. WithdrawFundsService::release(WithdrawFundsService::PAYMENT_ORDERS, $order, $order->amount, '三方提现', '提现失败退款');
  694. }, 3);
  695. } catch (Exception $e) {
  696. return ['code' => HttpStatus::CUSTOM_ERROR, 'msg' => $e->getMessage()];
  697. }
  698. return ['code' => 0, 'msg' => 'ok'];
  699. }
  700. /**
  701. * 创建代付订单 (根据 orderId 将钱转到用户指定账户)
  702. * @param int $orderId PaymentOrder 表的ID
  703. */
  704. public static function createPayout(int $orderId): array
  705. {
  706. try {
  707. $order = PaymentOrder::where('id', $orderId)
  708. ->where('type', 2)
  709. ->where('status', self::STATUS_STAY)
  710. ->first();
  711. if (!$order) throw new Exception("订单不存在_{$orderId}", HttpStatus::CUSTOM_ERROR);
  712. $amount = $order->amount;
  713. if (!PaymentProviderService::requestEnabled((string)$order->channel, 2)) {
  714. throw new Exception('支付公司已禁用', HttpStatus::CUSTOM_ERROR);
  715. }
  716. if (!RechargeChannel::query()->where('data_type', 2)->where('type', $order->channel)->available()->exists()) {
  717. throw new Exception('提现通道已停用或删除', HttpStatus::CUSTOM_ERROR);
  718. }
  719. $amount = number_format($amount, 2, '.', '');
  720. if (NoPayService::isWithdrawChannel($order->channel)) {
  721. $ret = NoPayService::withdraw($amount, $order->order_no, (string)$order->member_id, (string)$order->account, (string)$order->card_no);
  722. Log::channel('payment')->info('NO提现接口调用', [
  723. 'order_id' => $order->id,
  724. 'order_no' => $order->order_no,
  725. 'member_id' => $order->member_id,
  726. 'amount' => $amount,
  727. 'q_account' => $order->card_no,
  728. 'response' => $ret,
  729. ]);
  730. if (($ret['code'] ?? -1) != 0) {
  731. throw new Exception($ret['msg'] ?? 'NO提现失败', HttpStatus::CUSTOM_ERROR);
  732. }
  733. $order->pay_no = $ret['data']['orderNo'] ?? '';
  734. $order->pay_data = json_encode($ret, JSON_UNESCAPED_UNICODE);
  735. } elseif (ZimuPayService::isPayoutChannel($order->channel)) {
  736. self::assertZimuBalanceEnough($amount, [
  737. 'order_id' => $order->id,
  738. 'order_no' => $order->order_no,
  739. 'member_id' => $order->member_id,
  740. 'channel' => $order->channel,
  741. ]);
  742. if (ZimuPayService::isCashChannel($order->channel)) {
  743. $ret = ZimuPayService::cash(
  744. $amount,
  745. $order->order_no,
  746. (string)$order->account,
  747. (string)$order->card_no,
  748. (string)$order->bank_name,
  749. ZimuPayService::cashPayMethod((string)$order->bank_name)
  750. );
  751. } else {
  752. $ret = ZimuPayService::withdraw($amount, $order->order_no, (string)$order->member_id, (string)$order->card_no, (string)$order->account);
  753. }
  754. Log::channel('payment')->info('ZIMU提现/代付接口调用', [
  755. 'order_id' => $order->id,
  756. 'order_no' => $order->order_no,
  757. 'member_id' => $order->member_id,
  758. 'amount' => $amount,
  759. 'channel' => $order->channel,
  760. 'response' => $ret,
  761. ]);
  762. if (($ret['code'] ?? -1) != 0) {
  763. throw new Exception($ret['msg'] ?? 'ZIMU提现/代付失败', HttpStatus::CUSTOM_ERROR);
  764. }
  765. $order->pay_no = $ret['data']['orderNo'] ?? '';
  766. $order->pay_data = json_encode($ret, JSON_UNESCAPED_UNICODE);
  767. } elseif (JdPayService::isChannel($order->channel)) {
  768. self::assertJdBalanceEnough($amount, [
  769. 'order_id' => $order->id,
  770. 'order_no' => $order->order_no,
  771. 'member_id' => $order->member_id,
  772. 'address' => $order->card_no,
  773. ]);
  774. $ret = JdPayService::remit($amount, $order->order_no, $order->card_no);
  775. Log::channel('payment')->info('JD下发接口调用', [
  776. 'order_id' => $order->id,
  777. 'order_no' => $order->order_no,
  778. 'member_id' => $order->member_id,
  779. 'amount' => $amount,
  780. 'address' => $order->card_no,
  781. 'response' => $ret,
  782. ]);
  783. if (($ret['code'] ?? 0) != 200) {
  784. $logContext = [
  785. 'order_id' => $order->id,
  786. 'order_no' => $order->order_no,
  787. 'member_id' => $order->member_id,
  788. 'amount' => $amount,
  789. 'address' => $order->card_no,
  790. 'response' => $ret,
  791. ];
  792. Log::channel('payment_error')->error('JD下发接口失败', $logContext);
  793. Log::error('JD下发接口失败', $logContext);
  794. throw new Exception($ret['message'] ?? 'JD下发失败', HttpStatus::CUSTOM_ERROR);
  795. }
  796. $order->pay_no = $ret['data']['orderNo'] ?? '';
  797. $order->pay_data = json_encode($ret, JSON_UNESCAPED_UNICODE);
  798. } else {
  799. $ret = QianBaoService::payout(
  800. $amount, $order->order_no, $order->bank_name, $order->account, $order->card_no, $order->channel
  801. );
  802. Log::error('第三方代付接口调用:' . json_encode($ret, JSON_UNESCAPED_UNICODE));
  803. if ($ret['code'] != 200) throw new Exception($ret['msg'], HttpStatus::CUSTOM_ERROR);
  804. }
  805. $order->status = self::STATUS_PROCESS;
  806. $order->save();
  807. } catch (Exception $e) {
  808. Log::channel('payment_error')->error('创建代付订单失败', [
  809. 'order_id' => $orderId,
  810. 'error' => $e->getMessage(),
  811. ]);
  812. Log::error('创建代付订单失败', [
  813. 'order_id' => $orderId,
  814. 'error' => $e->getMessage(),
  815. ]);
  816. return ['code' => HttpStatus::CUSTOM_ERROR, 'msg' => $e->getMessage()];
  817. }
  818. return ['code' => 0, 'msg' => 'ok'];
  819. }
  820. /**
  821. * @description: 创建代付订单 (自动直接到账,包括用户钱包的扣款和提现记录的生成以及余额日志的创建)
  822. * @param {*} $memberId 会员ID
  823. * @param {*} $amount 代付金额
  824. * @param {*} $channel 提现通道 DF001 支付宝转卡/DF002 支付宝转支付宝
  825. * @param {*} $bank_name 银行名称/支付宝
  826. * @param {*} $card_no 银行卡号/支付宝账号
  827. * @return {*}
  828. */
  829. public static function autoCreatePayout($memberId, $amount, $channel, $bank_name, $card_no, ?string $bankBranch = null)
  830. {
  831. $default_amount = $amount;
  832. $result = [];
  833. $result['chat_id'] = $memberId;
  834. if ($channel === 'rgtx') {
  835. $result['text'] = lang('不支持此提现方式');
  836. return $result;
  837. }
  838. if (!PaymentProviderService::requestEnabled((string)$channel, 2)) {
  839. $result['text'] = '支付公司已禁用';
  840. return $result;
  841. }
  842. $user = User::query()->where('member_id', $memberId)->first();
  843. $groupId = (int)($user->recharge_channel_group_id ?? 1);
  844. $channelConfig = RechargeChannel::checkWithdrawChannel($channel, $groupId);
  845. if ($channelConfig === false) {
  846. $result['text'] = '不支持此提现方式';
  847. return $result;
  848. }
  849. $minAmount = (float)($channelConfig->min ?? 0);
  850. $maxAmount = (float)($channelConfig->max ?? 0);
  851. if ($amount < $minAmount || ($maxAmount > 0 && $amount > $maxAmount)) {
  852. $result['text'] = "提现金额范围为{$minAmount}-{$maxAmount}";
  853. return $result;
  854. }
  855. $channelRate = (float)($channelConfig->rate ?? 0);
  856. // 在调用三方支付前开始事务
  857. DB::beginTransaction();
  858. try {
  859. $account = WithdrawalName::forPayout($user, $channel);
  860. $bankBranch = WithdrawalDestination::bankBranch($channel, $bank_name, $bankBranch);
  861. $data = [];
  862. $data['type'] = self::TYPE_PAYOUT;
  863. $order_no = self::createOrderNo('sj' . $data['type'] . '_', $memberId);
  864. $data['order_no'] = $order_no;
  865. $data['member_id'] = $memberId;
  866. $data['fee'] = $amount * $channelRate + 2;
  867. $amount = number_format($amount, 2, '.', '');
  868. $data['amount'] = $amount;
  869. $data['channel'] = $channel;
  870. $data['bank_name'] = $bank_name;
  871. if ($bankBranch !== null) {
  872. $data['bank_branch'] = $bankBranch;
  873. }
  874. $data['account'] = $account;
  875. $data['card_no'] = $card_no;
  876. if (NoPayService::isWithdrawChannel($channel)) {
  877. $data['callback_url'] = NoPayService::getWithdrawNotifyUrl();
  878. } elseif (JdPayService::isChannel($channel)) {
  879. $data['callback_url'] = JdPayService::getRemitNotifyUrl();
  880. } elseif (ZimuPayService::isPayoutChannel($channel)) {
  881. $data['callback_url'] = ZimuPayService::getPayoutNotifyUrl();
  882. } else {
  883. $data['callback_url'] = QianBaoService::getNotifyUrl();
  884. }
  885. $data['status'] = self::STATUS_STAY;
  886. $data['remark'] = '提现费率:' . ($channelRate * 100) . '%+2';
  887. // 校验打码量 → 冻结资金 → 创建待处理的提现记录 → 重置打码周期
  888. [$info, $wallet] = WithdrawFundsService::hold((string)$memberId, $default_amount, '三方提现', '钱宝提现费率:' . ($channelRate * 100) . '%+2',
  889. WithdrawFundsService::PAYMENT_ORDERS, fn () => static::$MODEL::create($data));
  890. $id = $info->id;
  891. $available_balance = $wallet->available_balance;
  892. // 提交事务,确保预扣款成功
  893. DB::commit();
  894. } //
  895. catch (Exception $e) {
  896. // 预扣款失败,回滚事务
  897. DB::rollBack();
  898. $result['text'] = $e->getCode() === HttpStatus::CUSTOM_ERROR ? $e->getMessage() : '系统繁忙,请稍后重试!';
  899. Log::error('提现预扣款失败: ' . $e->getMessage(), [
  900. 'member_id' => $memberId,
  901. 'amount' => $amount
  902. ]);
  903. return $result;
  904. }
  905. // 调用三方支付接口(在事务外)
  906. if (NoPayService::isWithdrawChannel($channel)) {
  907. try {
  908. $ret = NoPayService::withdraw($amount, $order_no, (string)$memberId, (string)$account, (string)$card_no);
  909. Log::channel('payment')->info('NO提现接口调用', [
  910. 'order_no' => $order_no,
  911. 'member_id' => $memberId,
  912. 'amount' => $amount,
  913. 'q_account' => $card_no,
  914. 'response' => $ret,
  915. ]);
  916. $success = (($ret['code'] ?? -1) == 0);
  917. $failureMessage = $ret['msg'] ?? 'NO提现失败';
  918. } catch (Exception $e) {
  919. $ret = ['msg' => $e->getMessage()];
  920. $success = false;
  921. $failureMessage = $e->getMessage();
  922. Log::channel('payment_error')->error('NO提现接口异常', [
  923. 'order_no' => $order_no,
  924. 'member_id' => $memberId,
  925. 'amount' => $amount,
  926. 'q_account' => $card_no,
  927. 'error' => $e->getMessage(),
  928. ]);
  929. }
  930. } elseif (ZimuPayService::isPayoutChannel($channel)) {
  931. try {
  932. self::assertZimuBalanceEnough($amount, [
  933. 'order_no' => $order_no,
  934. 'member_id' => $memberId,
  935. 'channel' => $channel,
  936. ]);
  937. if (ZimuPayService::isCashChannel($channel)) {
  938. $ret = ZimuPayService::cash(
  939. $amount,
  940. $order_no,
  941. (string)$account,
  942. (string)$card_no,
  943. (string)$bank_name,
  944. ZimuPayService::cashPayMethod((string)$bank_name)
  945. );
  946. } else {
  947. $ret = ZimuPayService::withdraw($amount, $order_no, (string)$memberId, (string)$card_no, $account);
  948. }
  949. Log::channel('payment')->info('ZIMU提现/代付接口调用', [
  950. 'order_no' => $order_no,
  951. 'member_id' => $memberId,
  952. 'amount' => $amount,
  953. 'channel' => $channel,
  954. 'response' => $ret,
  955. ]);
  956. $success = (($ret['code'] ?? -1) == 0);
  957. $failureMessage = $ret['msg'] ?? 'ZIMU提现/代付失败';
  958. } catch (Exception $e) {
  959. $ret = ['msg' => $e->getMessage()];
  960. $success = false;
  961. $failureMessage = $e->getMessage();
  962. Log::channel('payment_error')->error('ZIMU提现/代付接口异常', [
  963. 'order_no' => $order_no,
  964. 'member_id' => $memberId,
  965. 'amount' => $amount,
  966. 'channel' => $channel,
  967. 'error' => $e->getMessage(),
  968. ]);
  969. }
  970. } elseif (JdPayService::isChannel($channel)) {
  971. try {
  972. self::assertJdBalanceEnough($amount, [
  973. 'order_no' => $order_no,
  974. 'member_id' => $memberId,
  975. 'address' => $card_no,
  976. ]);
  977. $ret = JdPayService::remit($amount, $order_no, $card_no);
  978. Log::channel('payment')->info('JD下发接口调用', [
  979. 'order_no' => $order_no,
  980. 'member_id' => $memberId,
  981. 'amount' => $amount,
  982. 'address' => $card_no,
  983. 'response' => $ret,
  984. ]);
  985. $success = (($ret['code'] ?? 0) == 200);
  986. $failureMessage = $ret['message'] ?? 'JD下发失败';
  987. if (!$success) {
  988. $logContext = [
  989. 'order_no' => $order_no,
  990. 'member_id' => $memberId,
  991. 'amount' => $amount,
  992. 'address' => $card_no,
  993. 'response' => $ret,
  994. ];
  995. Log::channel('payment_error')->error('JD下发接口失败', $logContext);
  996. Log::error('JD下发接口失败', $logContext);
  997. }
  998. } catch (Exception $e) {
  999. $ret = ['message' => $e->getMessage()];
  1000. $success = false;
  1001. $failureMessage = $e->getMessage();
  1002. $logContext = [
  1003. 'order_no' => $order_no,
  1004. 'member_id' => $memberId,
  1005. 'amount' => $amount,
  1006. 'address' => $card_no,
  1007. 'error' => $e->getMessage(),
  1008. ];
  1009. Log::channel('payment_error')->error('JD下发接口异常', $logContext);
  1010. Log::error('JD下发接口异常', $logContext);
  1011. }
  1012. } else {
  1013. $ret = QianBaoService::payout($amount, $order_no, $bank_name, $account, $card_no, $channel);
  1014. Log::error('第三方代付接口调用:' . json_encode($ret, JSON_UNESCAPED_UNICODE));
  1015. $success = (($ret['code'] ?? 0) == 200);
  1016. $failureMessage = $ret['msg'] ?? '代付失败';
  1017. }
  1018. if ($success) {
  1019. // 更新提现记录状态为处理中
  1020. DB::beginTransaction();
  1021. try {
  1022. $info->status = self::STATUS_PROCESS;
  1023. if (NoPayService::isWithdrawChannel($channel) || JdPayService::isChannel($channel) || ZimuPayService::isPayoutChannel($channel)) {
  1024. $info->pay_no = $ret['data']['orderNo'] ?? '';
  1025. $info->pay_data = json_encode($ret, JSON_UNESCAPED_UNICODE);
  1026. }
  1027. $info->save();
  1028. DB::commit();
  1029. $text = "✅ 提现申请已提交!\n\n";
  1030. $text .= "钱包余额:{$available_balance} RMB\n";
  1031. $text .= "提现金额:{$default_amount} RMB\n";
  1032. $text .= "⌛️请等待系统处理, 到账时间可能需要几分钟!\n";
  1033. $result['text'] = $text;
  1034. } catch (Exception $e) {
  1035. DB::rollBack();
  1036. // 状态更新失败,但资金已扣款且三方支付已成功
  1037. // 这里可以记录告警,需要人工干预
  1038. Log::error('提现状态更新失败: ' . $e->getMessage(), [
  1039. 'member_id' => $memberId,
  1040. 'order_no' => $order_no
  1041. ]);
  1042. $result['text'] = '提现申请已提交,系统处理中...';
  1043. }
  1044. } //
  1045. else {
  1046. // 三方支付失败,需要回滚之前的预扣款
  1047. DB::beginTransaction();
  1048. try {
  1049. // 更新提现记录状态为失败
  1050. $info->status = self::STATUS_FAIL;
  1051. $info->remark = $failureMessage;
  1052. $info->save();
  1053. // 冻结余额退回可用余额,并记录退款日志
  1054. WithdrawFundsService::release(WithdrawFundsService::PAYMENT_ORDERS, $info, $default_amount, '三方提现', '提现失败退款');
  1055. DB::commit();
  1056. $result['text'] = $failureMessage;
  1057. } catch (Exception $e) {
  1058. DB::rollBack();
  1059. // 回滚失败,需要记录告警,人工干预
  1060. Log::error('提现失败回滚异常: ' . $e->getMessage(), [
  1061. 'member_id' => $memberId,
  1062. 'order_no' => $order_no,
  1063. 'amount' => $amount
  1064. ]);
  1065. $result['text'] = '提现失败,请联系客服处理退款!';
  1066. }
  1067. }
  1068. return $result;
  1069. }
  1070. /**
  1071. * @description: 接收三方订单
  1072. * @param {*} $params
  1073. * @return {*}
  1074. */
  1075. public static function receiveOrder($params)
  1076. {
  1077. if (ZimuPayService::getAppId() !== '' && ($params['appId'] ?? '') === ZimuPayService::getAppId()) {
  1078. $info = self::findOne(['order_no' => $params['mchOrderNo'] ?? '']);
  1079. if (!$info || $info->type != self::TYPE_PAYOUT || !ZimuPayService::isPayoutChannel($info->channel)) {
  1080. Log::error('ZIMU提现/代付回调订单不存在或类型不匹配', [
  1081. 'mch_order_no' => $params['mchOrderNo'] ?? '',
  1082. 'order_id' => $info->id ?? null,
  1083. 'order_type' => $info->type ?? null,
  1084. 'channel' => $info->channel ?? null,
  1085. ]);
  1086. return false;
  1087. }
  1088. if (!ZimuPayService::verifyNotify($params)) {
  1089. Log::error('ZIMU提现/代付回调验签失败', [
  1090. 'mch_order_no' => $params['mchOrderNo'] ?? '',
  1091. 'app_id' => $params['appId'] ?? '',
  1092. 'signature' => ZimuPayService::notifySignatureDiagnostics($params),
  1093. ]);
  1094. return false;
  1095. }
  1096. if (bccomp(ZimuPayService::amount($info->amount), ZimuPayService::amount($params['amount'] ?? 0), 2) !== 0) {
  1097. Log::error('ZIMU提现/代付回调金额不一致', [
  1098. 'mch_order_no' => $params['mchOrderNo'] ?? '',
  1099. 'order_amount' => $info->amount,
  1100. 'callback_amount' => $params['amount'] ?? null,
  1101. ]);
  1102. return false;
  1103. }
  1104. self::onSubmitZimuPayout($params, $info);
  1105. return true;
  1106. }
  1107. if (NoPayService::getWithdrawMerchantId() !== '' && ($params['appId'] ?? '') === NoPayService::getWithdrawMerchantId()) {
  1108. $info = self::findOne(['order_no' => $params['merchantOrderNo'] ?? '']);
  1109. if (!$info || $info->type != self::TYPE_PAYOUT || !NoPayService::isWithdrawChannel($info->channel)) {
  1110. Log::error('NO钱包提现回调订单不存在或类型不匹配', [
  1111. 'merchant_order_no' => $params['merchantOrderNo'] ?? '',
  1112. 'order_id' => $info->id ?? null,
  1113. 'order_type' => $info->type ?? null,
  1114. 'channel' => $info->channel ?? null,
  1115. ]);
  1116. return false;
  1117. }
  1118. if (!NoPayService::verifyWithdrawNotify($params)) {
  1119. Log::error('NO钱包提现回调验签失败', [
  1120. 'merchant_order_no' => $params['merchantOrderNo'] ?? '',
  1121. 'app_id' => $params['appId'] ?? '',
  1122. 'signature' => NoPayService::withdrawNotifySignatureDiagnostics($params),
  1123. ]);
  1124. return false;
  1125. }
  1126. if (bccomp(NoPayService::amount($info->amount), NoPayService::amount($params['amount'] ?? 0), 2) !== 0) {
  1127. Log::error('NO钱包提现回调金额不一致', [
  1128. 'merchant_order_no' => $params['merchantOrderNo'] ?? '',
  1129. 'order_amount' => $info->amount,
  1130. 'callback_amount' => $params['amount'] ?? null,
  1131. ]);
  1132. return false;
  1133. }
  1134. self::onSubmitNoPayout($params, $info);
  1135. return true;
  1136. }
  1137. if (($params['userCode'] ?? '') === JdPayService::getMerchantId()) {
  1138. $info = self::findOne(['order_no' => $params['customerOrderCode'] ?? ''])
  1139. ?: static::$MODEL::where('pay_no', $params['orderCode'] ?? '')->first();
  1140. if (!$info || $info->type != self::TYPE_PAYOUT) {
  1141. return false;
  1142. }
  1143. if (!JdPayService::verifyRemitNotify($params)) {
  1144. return false;
  1145. }
  1146. if (bccomp(JdPayService::amount($info->amount), JdPayService::amount($params['amount'] ?? 0), 2) !== 0) {
  1147. return false;
  1148. }
  1149. self::onSubmitJdPayout($params, $info);
  1150. return true;
  1151. }
  1152. // 判断商户号是否一致
  1153. if (($params['merchantNum'] ?? '') == QianBaoService::getMerchantId()) {
  1154. $info = self::findOne(['order_no' => $params['orderNo']]);
  1155. if ($info) {
  1156. // 判断金额是不是正确认
  1157. if ($info->amount != $params['amount']) {
  1158. return false;
  1159. }
  1160. // 验证签名
  1161. $sign = QianBaoService::verifyNotifySign($params['state'], $params['orderNo'], $params['amount']);
  1162. if ($params['sign'] != $sign) {
  1163. return false;
  1164. }
  1165. // 代付
  1166. if ($info->type == self::TYPE_PAYOUT) {
  1167. self::onSubmitPayout($params, $info);
  1168. }
  1169. // 代收
  1170. if ($info->type == self::TYPE_PAY) {
  1171. }
  1172. }
  1173. }
  1174. }
  1175. /**
  1176. * @description: 处理代付订单
  1177. * @param {*} $params
  1178. * @return {*}
  1179. */
  1180. public static function onSubmitJdPayout($params, $info)
  1181. {
  1182. if ($info->status != self::STATUS_PROCESS) {
  1183. return;
  1184. }
  1185. $memberId = $info->member_id;
  1186. $amount = JdPayService::amount($params['amount'] ?? $info->amount);
  1187. $data = [];
  1188. $chat_id = $info->member_id;
  1189. $data['callback_data'] = json_encode($params, JSON_UNESCAPED_UNICODE);
  1190. DB::beginTransaction();
  1191. try {
  1192. $locked = static::$MODEL::where('id', $info->id)->lockForUpdate()->first();
  1193. if (!$locked || $locked->status != self::STATUS_PROCESS) {
  1194. DB::rollBack();
  1195. return;
  1196. }
  1197. if ((string)$params['status'] === JdPayService::REMIT_STATUS_SUCCESS) {
  1198. $data['status'] = self::STATUS_SUCCESS;
  1199. $res = static::$MODEL::where(['id' => $info->id])->update($data);
  1200. AdminRealtime::record('payment_orders', $info->id);
  1201. WithdrawFundsService::settle(WithdrawFundsService::PAYMENT_ORDERS, $locked);
  1202. if ($res) {
  1203. $text = "✅ 提现通知 \n";
  1204. $text .= "提现平台:{$info->bank_name} \n";
  1205. $text .= "收款人:{$info->account} \n";
  1206. $text .= "收款地址:{$info->card_no} \n";
  1207. $text .= "提现金额:{$info->amount} \n";
  1208. $text .= "提现成功,金额已到账,请注意查收!";
  1209. self::notifyUser($chat_id, $text);
  1210. }
  1211. } elseif ((string)$params['status'] === JdPayService::REMIT_STATUS_FAIL) {
  1212. $data['status'] = self::STATUS_FAIL;
  1213. $res = static::$MODEL::where(['id' => $info->id])->update($data);
  1214. AdminRealtime::record('payment_orders', $info->id);
  1215. WithdrawFundsService::release(WithdrawFundsService::PAYMENT_ORDERS, $locked, $amount, '三方提现', '提现失败退款');
  1216. if ($res) {
  1217. $text = "❌ 提现通知 \n";
  1218. $text .= "提现平台:{$info->bank_name} \n";
  1219. $text .= "收款人:{$info->account} \n";
  1220. $text .= "收款地址:{$info->card_no} \n";
  1221. $text .= "提现金额:{$info->amount} \n";
  1222. $text .= "提现失败,金额已返回钱包,请注意查收!";
  1223. self::notifyUser($chat_id, $text);
  1224. }
  1225. }
  1226. DB::commit();
  1227. } catch (Exception $e) {
  1228. DB::rollBack();
  1229. Log::error('JD提现回调处理异常: ' . $e->getMessage(), $params);
  1230. }
  1231. }
  1232. public static function onSubmitNoPayout($params, $info)
  1233. {
  1234. $notification = null;
  1235. try {
  1236. DB::transaction(function () use ($params, $info, &$notification) {
  1237. $order = PaymentOrder::where('id', $info->id)->lockForUpdate()->first();
  1238. if (!$order || $order->status != self::STATUS_PROCESS) {
  1239. return;
  1240. }
  1241. $order->callback_data = json_encode($params, JSON_UNESCAPED_UNICODE);
  1242. if ((string)$params['state'] === NoPayService::STATE_SUCCESS) {
  1243. $order->status = self::STATUS_SUCCESS;
  1244. $order->save();
  1245. WithdrawFundsService::settle(WithdrawFundsService::PAYMENT_ORDERS, $order);
  1246. $notification = "✅ 提现通知 \n提现平台:{$order->bank_name} \n收款人:{$order->account} \nNO钱包账号:{$order->card_no} \n提现金额:{$order->amount} \n提现成功,金额已到账,请注意查收!";
  1247. } elseif ((string)$params['state'] === NoPayService::STATE_FAIL) {
  1248. $order->status = self::STATUS_FAIL;
  1249. $order->save();
  1250. WithdrawFundsService::release(WithdrawFundsService::PAYMENT_ORDERS, $order, NoPayService::amount($order->amount), '三方提现', '提现失败退款');
  1251. $notification = "❌ 提现通知 \n提现平台:{$order->bank_name} \n收款人:{$order->account} \nNO钱包账号:{$order->card_no} \n提现金额:{$order->amount} \n提现失败,金额已返回钱包,请注意查收!";
  1252. } else {
  1253. $order->save();
  1254. }
  1255. }, 3);
  1256. if ($notification !== null) {
  1257. self::notifyUser($info->member_id, $notification);
  1258. }
  1259. } catch (Exception $e) {
  1260. Log::channel('payment_error')->error('NO提现回调处理异常', [
  1261. 'order_id' => $info->id,
  1262. 'params' => $params,
  1263. 'error' => $e->getMessage(),
  1264. ]);
  1265. }
  1266. }
  1267. public static function onSubmitZimuPayout($params, $info)
  1268. {
  1269. $notification = null;
  1270. $successStatus = ZimuPayService::isCashChannel($info->channel)
  1271. ? ZimuPayService::CASH_STATUS_SUCCESS
  1272. : ZimuPayService::WITHDRAW_STATUS_SUCCESS;
  1273. $failStatus = ZimuPayService::isCashChannel($info->channel)
  1274. ? ZimuPayService::CASH_STATUS_FAIL
  1275. : ZimuPayService::WITHDRAW_STATUS_FAIL;
  1276. try {
  1277. DB::transaction(function () use ($params, $info, &$notification, $successStatus, $failStatus) {
  1278. $order = PaymentOrder::where('id', $info->id)->lockForUpdate()->first();
  1279. if (!$order || $order->status != self::STATUS_PROCESS) {
  1280. return;
  1281. }
  1282. $order->callback_data = json_encode($params, JSON_UNESCAPED_UNICODE);
  1283. if ((string)$params['status'] === $successStatus) {
  1284. $order->status = self::STATUS_SUCCESS;
  1285. $order->save();
  1286. WithdrawFundsService::settle(WithdrawFundsService::PAYMENT_ORDERS, $order);
  1287. $accountLabel = ZimuPayService::isCashChannel($order->channel) ? '收款账号' : '808钱包地址';
  1288. $notification = "✅ 提现通知 \n提现平台:{$order->bank_name} \n收款人:{$order->account} \n{$accountLabel}:{$order->card_no} \n提现金额:{$order->amount} \n提现成功,金额已到账,请注意查收!";
  1289. } elseif ((string)$params['status'] === $failStatus) {
  1290. $order->status = self::STATUS_FAIL;
  1291. $order->save();
  1292. WithdrawFundsService::release(WithdrawFundsService::PAYMENT_ORDERS, $order, ZimuPayService::amount($order->amount), '三方提现', '提现失败退款');
  1293. $accountLabel = ZimuPayService::isCashChannel($order->channel) ? '收款账号' : '808钱包地址';
  1294. $notification = "❌ 提现通知 \n提现平台:{$order->bank_name} \n收款人:{$order->account} \n{$accountLabel}:{$order->card_no} \n提现金额:{$order->amount} \n提现失败,金额已返回钱包,请注意查收!";
  1295. } else {
  1296. $order->save();
  1297. }
  1298. }, 3);
  1299. if ($notification !== null) {
  1300. self::notifyUser($info->member_id, $notification);
  1301. }
  1302. } catch (Exception $e) {
  1303. Log::channel('payment_error')->error('ZIMU提现/代付回调处理异常', [
  1304. 'order_id' => $info->id,
  1305. 'params' => $params,
  1306. 'error' => $e->getMessage(),
  1307. ]);
  1308. }
  1309. }
  1310. public static function onSubmitPayout($params, $info)
  1311. {
  1312. $memberId = $info->member_id;
  1313. $amount = $params['amount'];
  1314. $data = [];
  1315. $result = [];
  1316. $chat_id = $info->member_id;
  1317. $data['callback_data'] = json_encode($params, JSON_UNESCAPED_UNICODE);
  1318. DB::beginTransaction();
  1319. try {
  1320. // 只处理“处理中”的代付单,避免重复回调重复退款
  1321. $locked = static::$MODEL::where('id', $info->id)->lockForUpdate()->first();
  1322. if (!$locked || $locked->status != self::STATUS_PROCESS) {
  1323. DB::rollBack();
  1324. return;
  1325. }
  1326. if ($params['state'] == 1) {
  1327. $data['status'] = self::STATUS_SUCCESS;
  1328. $res = static::$MODEL::where(['order_no' => $params['orderNo']])->update($data);
  1329. AdminRealtime::record('payment_orders', $info->id);
  1330. WithdrawFundsService::settle(WithdrawFundsService::PAYMENT_ORDERS, $locked);
  1331. if ($res) {
  1332. $text = "✅ 提现通知 \n";
  1333. $text .= "提现平台:{$info->bank_name} \n";
  1334. $text .= "收款人:{$info->account} \n";
  1335. $text .= "收款卡号:{$info->card_no} \n";
  1336. $text .= "提现金额:{$info->amount} \n";
  1337. $text .= "提现成功,金额已到账,请注意查收!";
  1338. self::notifyUser($chat_id, $text);
  1339. }
  1340. } else {
  1341. $data['status'] = self::STATUS_FAIL;
  1342. $res = static::$MODEL::where(['order_no' => $params['orderNo']])->update($data);
  1343. AdminRealtime::record('payment_orders', $info->id);
  1344. // 冻结余额退回可用余额,并记录退款日志
  1345. WithdrawFundsService::release(WithdrawFundsService::PAYMENT_ORDERS, $locked, $amount, '三方提现', '提现失败退款');
  1346. if ($res) {
  1347. $text = "❌ 提现通知 \n";
  1348. $text .= "提现平台:{$info->bank_name} \n";
  1349. $text .= "收款人:{$info->account} \n";
  1350. $text .= "收款卡号:{$info->card_no} \n";
  1351. $text .= "提现金额:{$info->amount} \n";
  1352. $text .= "提现失败,金额已返回钱包,请注意查收!";
  1353. self::notifyUser($chat_id, $text);
  1354. }
  1355. }
  1356. DB::commit();
  1357. } catch (Exception $e) {
  1358. DB::rollBack();
  1359. // 回滚失败,需要记录告警,人工干预
  1360. Log::error('提现失败回滚异常: ' . $e->getMessage(), $params);
  1361. }
  1362. }
  1363. /**
  1364. * @description: 查询支付订单
  1365. * @param {*} $id
  1366. * @return {*}
  1367. */
  1368. public static function singlePayOrder($id)
  1369. {
  1370. return self::queryPayOrder(self::findOne(['id' => $id]));
  1371. }
  1372. /** 向三方查询在线充值订单并按结果入账。会员和代理订单共用。 */
  1373. public static function queryPayOrder(?PaymentOrder $info): array
  1374. {
  1375. $msg = [];
  1376. $msg['code'] = self::NOT;
  1377. if ($info && (int)$info->type === self::TYPE_PAY && in_array((int)$info->status, self::PAY_OPEN_STATUSES, true)) {
  1378. if (NoPayService::isRechargeChannel($info->channel)) {
  1379. $ret = NoPayService::queryPayOrder($info->order_no, (string)$info->member_id);
  1380. Log::channel('payment')->info('NO支付查询订单', $ret);
  1381. if (($ret['code'] ?? -1) == 0) {
  1382. $item = $ret['data'] ?? [];
  1383. if ((string)($item['state'] ?? '') === NoPayService::STATE_SUCCESS) {
  1384. $processed = self::applyPayCallback(
  1385. $info,
  1386. NoPayService::amount($info->amount),
  1387. NoPayService::STATE_SUCCESS,
  1388. NoPayService::STATE_SUCCESS,
  1389. NoPayService::STATE_FAIL,
  1390. $ret
  1391. );
  1392. $msg['code'] = $processed ? self::YES : self::NOT;
  1393. $msg['msg'] = $processed ? '支付成功' : '订单已处理';
  1394. } elseif ((string)($item['state'] ?? '') === NoPayService::STATE_FAIL) {
  1395. self::applyPayCallback(
  1396. $info,
  1397. NoPayService::amount($info->amount),
  1398. NoPayService::STATE_FAIL,
  1399. NoPayService::STATE_SUCCESS,
  1400. NoPayService::STATE_FAIL,
  1401. $ret
  1402. );
  1403. $msg['msg'] = '支付失败';
  1404. } else {
  1405. $msg['msg'] = '支付中';
  1406. }
  1407. } else {
  1408. $msg['msg'] = '查询失败:' . ($ret['msg'] ?? '');
  1409. }
  1410. return $msg;
  1411. }
  1412. if (ZimuPayService::isRechargeChannel($info->channel)) {
  1413. $ret = ZimuPayService::queryPayOrder($info->order_no);
  1414. Log::channel('payment')->info('ZIMU支付查询订单', $ret);
  1415. if (($ret['code'] ?? -1) == 0) {
  1416. $item = $ret['data'] ?? [];
  1417. if ((string)($item['status'] ?? '') === ZimuPayService::PAY_STATUS_SUCCESS) {
  1418. $processed = self::applyPayCallback(
  1419. $info,
  1420. ZimuPayService::amount($info->amount),
  1421. ZimuPayService::PAY_STATUS_SUCCESS,
  1422. ZimuPayService::PAY_STATUS_SUCCESS,
  1423. ZimuPayService::PAY_STATUS_FAIL,
  1424. $ret
  1425. );
  1426. $msg['code'] = $processed ? self::YES : self::NOT;
  1427. $msg['msg'] = $processed ? '支付成功' : '订单已处理';
  1428. } elseif ((string)($item['status'] ?? '') === ZimuPayService::PAY_STATUS_FAIL) {
  1429. self::applyPayCallback(
  1430. $info,
  1431. ZimuPayService::amount($info->amount),
  1432. ZimuPayService::PAY_STATUS_FAIL,
  1433. ZimuPayService::PAY_STATUS_SUCCESS,
  1434. ZimuPayService::PAY_STATUS_FAIL,
  1435. $ret
  1436. );
  1437. $msg['msg'] = '支付失败';
  1438. } else {
  1439. $msg['msg'] = '支付中';
  1440. }
  1441. } else {
  1442. $msg['msg'] = '查询失败:' . ($ret['msg'] ?? '');
  1443. }
  1444. return $msg;
  1445. }
  1446. if (JdPayService::isChannel($info->channel)) {
  1447. $ret = JdPayService::queryPayOrder($info->pay_no ?? '', $info->order_no);
  1448. Log::channel('payment')->info('JD支付查询订单', $ret);
  1449. if (($ret['code'] ?? 0) == 200) {
  1450. $item = $ret['data'] ?? [];
  1451. if ((string)($item['status'] ?? '') === JdPayService::PAY_STATUS_SUCCESS) {
  1452. $processed = self::applyPayCallback($info, JdPayService::amount($info->amount), JdPayService::PAY_STATUS_SUCCESS,
  1453. JdPayService::PAY_STATUS_SUCCESS, JdPayService::PAY_STATUS_FAIL, $ret);
  1454. $msg['code'] = $processed ? self::YES : self::NOT;
  1455. $msg['msg'] = $processed ? '支付成功' : '订单已处理';
  1456. } else {
  1457. $msg['msg'] = '支付中';
  1458. }
  1459. } else {
  1460. $msg['msg'] = '查询失败:' . ($ret['message'] ?? '');
  1461. }
  1462. return $msg;
  1463. }
  1464. $ret = SanJinService::queryOrder($info->order_no);
  1465. Log::error('三斤支付查询订单:', $ret);
  1466. if ($ret['code'] == 0) {
  1467. $item = [];
  1468. $item['state'] = $ret['data']['state'];
  1469. if ($ret['data']['state'] == 1) {
  1470. $processed = self::applyPayCallback($info, (string)$info->amount, '1', '1', null, $ret);
  1471. $msg['code'] = $processed ? self::YES : self::NOT;
  1472. $msg['msg'] = $processed ? '支付成功' : '订单已处理';
  1473. } else {
  1474. $msg['msg'] = '支付中';
  1475. }
  1476. } else {
  1477. $msg['msg'] = '查询失败:' . $ret['message'];
  1478. }
  1479. } else {
  1480. $msg['msg'] = '该状态无法查询';
  1481. }
  1482. return $msg;
  1483. }
  1484. private static function assertJdBalanceEnough($amount, array $context = []): void
  1485. {
  1486. $ret = JdPayService::balance();
  1487. Log::channel('payment')->info('JD余额查询', $context + [
  1488. 'amount' => $amount,
  1489. 'response' => $ret,
  1490. ]);
  1491. if (($ret['code'] ?? 0) != 200) {
  1492. $logContext = $context + [
  1493. 'amount' => $amount,
  1494. 'response' => $ret,
  1495. ];
  1496. Log::channel('payment_error')->error('JD余额查询失败', $logContext);
  1497. Log::error('JD余额查询失败', $logContext);
  1498. throw new Exception($ret['message'] ?? 'JD余额查询失败', HttpStatus::CUSTOM_ERROR);
  1499. }
  1500. $balance = $ret['data']['balance'] ?? null;
  1501. if ($balance === null || bccomp((string)$balance, JdPayService::amount($amount), 2) < 0) {
  1502. $logContext = $context + [
  1503. 'amount' => $amount,
  1504. 'balance' => $balance,
  1505. 'response' => $ret,
  1506. ];
  1507. Log::channel('payment_error')->error('JD商户余额不足', $logContext);
  1508. Log::error('JD商户余额不足', $logContext);
  1509. throw new Exception('JD商户余额不足', HttpStatus::CUSTOM_ERROR);
  1510. }
  1511. }
  1512. private static function assertZimuBalanceEnough($amount, array $context = []): void
  1513. {
  1514. $ret = ZimuPayService::balance();
  1515. Log::channel('payment')->info('ZIMU余额查询', $context + [
  1516. 'amount' => $amount,
  1517. 'response' => $ret,
  1518. ]);
  1519. if (($ret['code'] ?? -1) != 0) {
  1520. $logContext = $context + [
  1521. 'amount' => $amount,
  1522. 'response' => $ret,
  1523. ];
  1524. Log::channel('payment_error')->error('ZIMU余额查询失败', $logContext);
  1525. Log::error('ZIMU余额查询失败', $logContext);
  1526. throw new Exception($ret['msg'] ?? 'ZIMU余额查询失败', HttpStatus::CUSTOM_ERROR);
  1527. }
  1528. $balance = $ret['data']['balance'] ?? null;
  1529. if ($balance === null || bccomp((string)$balance, ZimuPayService::amount($amount), 2) < 0) {
  1530. $logContext = $context + [
  1531. 'amount' => $amount,
  1532. 'balance' => $balance,
  1533. 'response' => $ret,
  1534. ];
  1535. Log::channel('payment_error')->error('ZIMU商户余额不足', $logContext);
  1536. Log::error('ZIMU商户余额不足', $logContext);
  1537. throw new Exception('ZIMU商户余额不足', HttpStatus::CUSTOM_ERROR);
  1538. }
  1539. }
  1540. private static function notifyUser($chatId, string $text): void
  1541. {
  1542. if ((int)User::where('member_id', $chatId)->value('from') !== -1) {
  1543. return;
  1544. }
  1545. try {
  1546. self::sendMessage($chatId, $text);
  1547. } catch (\Throwable $e) {
  1548. Log::channel('payment_error')->error('支付订单用户通知失败', [
  1549. 'chat_id' => $chatId,
  1550. 'error' => $e->getMessage(),
  1551. ]);
  1552. }
  1553. }
  1554. public static function syncPayOrder()
  1555. {
  1556. $list = static::$MODEL::where('state', 0)->where('type', self::TYPE_PAY)->take(100)->get();
  1557. // foreach($list->toArray() as $k => $v){
  1558. // $item= [];
  1559. // if($v['status'] == self::STATUS_SUCCESS){
  1560. // $item['state'] = 1;
  1561. // static::$MODEL::where(['id'=>$v['id']])->update($item);
  1562. // }else{
  1563. // $ret = SanJinService::queryOrder($v['order_no']);
  1564. // var_dump($ret);
  1565. // if($ret['code'] == 0){
  1566. // $item['state'] = $ret['data']['state'];
  1567. // static::$MODEL::where(['id'=>$v['id']])->update($item);
  1568. // }
  1569. // }
  1570. // }
  1571. }
  1572. }