verify($userId, $GiftsID, $pay_amount); if ($PayVerify->verify($userId, $GiftsID, $pay_amount) === false) { $this->error = $PayVerify->getError(); return false; } if ($pay_amount < 0) { $this->error = 'Payment error_4'; return false; } $service = new SupefinaSpei('NewSupefinaSpei'); $config = $service->config; $merId = $config['merId'] ?? ''; $baseUrl = $config['baseUrl'] ?? ''; $payinPath = $config['payinPath'] ?? '/api/supefina/transactions/collection'; $callbackUrl = $config['callbackUrl'] ?? ''; $countryId = $config['countryId'] ?? 'MX'; $currency = $config['currency'] ?? 'MXN'; $aesKey = $config['aesKey'] ?? ''; $privateKey = $config['privateKey'] ?? ''; if ($merId === '' || $baseUrl === '' || empty($privateKey) || $callbackUrl === '') { $this->error = 'Payment config error'; return false; } $order_sn = CreateOrder::order_sn($userId); $logic = new OrderLogic(); $amount = (int)round($pay_amount * NumConfig::NUM_VALUE); $logic->orderCreate($order_sn, $amount, 'NewSupefinaSpei', $userId, $pay_method, $GiftsID, $AdId, $eventType); $orderAmount = (string)(int)$pay_amount; if ($pay_amount != (int)$pay_amount) { $orderAmount = number_format($pay_amount, 2, '.', ''); } $params = [ 'merchantOrderNo' => $order_sn, 'amount' => $orderAmount, 'country' => $countryId, 'currency' => $currency, 'notifyUrl' => $callbackUrl, ]; $headers = [ 'version' => '1.0.0', 'merchantNo' => $merId, 'requestId' => mt_rand(100000, 999999) . time(), 'requestTime' => time() * 1000, ]; $body = [ 'productNo' => 'CHECKOUT_COUNTER', 'data' => Aes::encrypt(json_encode($params), base64_decode($aesKey)), ]; $sign = $this->sign($headers, $body, $privateKey); $headers['sign'] = $sign; $request_extra = \GuzzleHttp\json_encode(array_merge($headers, $params)); CreateLog::pay_request($userPhone, $request_extra, $order_sn, $userEmail, $userId, $userName); $url = rtrim($baseUrl, '/') . $payinPath; Util::WriteLog('NewSupefinaSpei', 'payin request: ' . $url . ' | ' . $request_extra); try { $client = new Client(); $result = $client->post($url, [ 'headers' => $headers, 'json' => $body, ])->getBody()->getContents(); } catch (\Throwable $e) { Util::WriteLog('NewSupefinaSpei_error', 'payin request exception: ' . $e->getMessage()); $this->error = 'Payment processing error'; return false; } Util::WriteLog('NewSupefinaSpei', 'payin response: ' . $result); try { $data = \GuzzleHttp\json_decode($result, true); } catch (\Throwable $e) { Util::WriteLog('NewSupefinaSpei_error', [$result, $e->getMessage()]); $this->error = 'Payment processing error'; return false; } if (!isset($data['success']) || $data['success'] !== true) { $this->error = $data['message'] ?? 'Payment request failed'; return false; } $decryptData = Aes::decrypt($data['data'], base64_decode($aesKey)); Util::WriteLog('NewSupefinaSpei', 'payin response1: ' . $decryptData); $this->result = json_decode($decryptData, true); return $this->result; } /** * 代收回调:仅代收存在“实际金额与订单金额不一致”,以 realityAmount 入账 * * @param array $post */ public function notify($post) { $order_sn = $post['merchantOrderNo']; if ($order_sn === '') { Util::WriteLog('NewSupefinaSpei_error', 'payin notify missing merchantOrderNo'); return 'fail'; } try { $order = DB::connection('write')->table('agent.dbo.order')->where('order_sn', $order_sn)->first(); if (!$order) { Util::WriteLog('NewSupefinaSpei', 'payin order not found: ' . $order_sn); return '{"success":true}'; } if (!empty($order->pay_at) || !empty($order->finished_at)) { if ($order->payment_sn != $post['platformOrderNo']) { $logic = new OrderLogic(); $amount = 100; $order_sn = $order_sn . '#' . time(); $logic->orderCreate($order_sn, $amount, 'NewSupefinaSpei', $order->user_id); $order = DB::connection('write')->table('agent.dbo.order')->where('order_sn', $order_sn) ->first(); } else { return '{"success":true}'; } } $status = (string)($post['transStatus'] ?? ''); if (in_array($status, ['FAILED', 'CLOSED', 'CANCELED'])) { $body = ['pay_status' => 2, 'updated_at' => date('Y-m-d H:i:s')]; DB::connection('write')->table('agent.dbo.order')->where('order_sn', $order_sn)->update($body); return '{"success":true}'; } if (in_array($status, ['REFUNDING', 'PARTIAL_REFUND', 'REFUNDED'])) { Util::WriteLog('NewSupefinaSpei', 'payin notify status refund: ' . $order_sn); return '{"success":true}'; } if ($status !== 'SUCCESS') { Util::WriteLog('NewSupefinaSpei', 'payin notify status not success: ' . $order_sn); return '{"success":true}'; } $GiftsID = $order->GiftsID ?: ''; $userID = $order->user_id ?: ''; $AdId = $order->AdId ?: ''; $eventType = $order->eventType ?: ''; $realityAmount = isset($post['amount']) ? (float)$post['amount'] : (float)($post['amount'] ?? 0); if ($realityAmount <= 0) { Util::WriteLog('NewSupefinaSpei_error', 'payin notify invalid realityAmount: ' . json_encode($post)); return 'fail'; } $payAmt = round($realityAmount, 2); $amountInScore = (int)round($payAmt * NumConfig::NUM_VALUE); $body = [ 'payment_sn' => $post['platformOrderNo'] ?? '', 'pay_status' => 1, 'pay_at' => date('Y-m-d H:i:s'), 'finished_at' => date('Y-m-d H:i:s'), 'amount' => $amountInScore, 'updated_at' => date('Y-m-d H:i:s'), ]; $body['payment_fee'] = $post['fee'] ?? 0; $service = new OrderServices(); if ((int)$order->amount != $amountInScore) { $body['GiftsID'] = 0; $body['amount'] = $amountInScore; $Recharge = $payAmt; $give = 0; $favorable_price = $Recharge + $give; $czReason = 1; $cjReason = 45; } else { [$give, $favorable_price, $Recharge, $czReason, $cjReason] = $service->getPayInfo($GiftsID, $userID, $payAmt); } [$Score] = $service->addRecord($userID, $payAmt, $favorable_price, $order_sn, $GiftsID, $Recharge, $czReason, $give, $cjReason, $AdId, $eventType, $body['payment_fee'] ?? 0); Order::dispatch([$userID, $payAmt, $Score, $favorable_price, $GiftsID, $order_sn]); DB::connection('write')->table('agent.dbo.order')->where('order_sn', $order_sn)->update($body); Util::WriteLog('NewSupefinaSpei', 'payin success, order_sn=' . $order_sn . ', realityAmount=' . $realityAmount); return '{"success":true}'; } catch (\Throwable $exception) { Util::WriteLog('NewSupefinaSpei_error', $exception->getMessage() . "\n" . $exception->getTraceAsString()); throw $exception; } } public function payment($RecordID, $amount, $accountName, $phone, $email, $OrderId, $PixNum, $PixType, $IFSCNumber, $BranchBank, $BankNO) { // 查询提现订单 $query = DB::connection('write') ->table('QPAccountsDB.dbo.OrderWithDraw') ->where('RecordID', $RecordID) ->first(); if (!$query) { Util::WriteLog('NewSupefinaSpei', 'withdraw order not found: ' . $RecordID); return 'fail'; } $config = (new PayConfig())->getConfig('NewSupefinaSpei'); // 账户号:优先 PixNum,其次 BankNO $account = $PixNum ?: $BankNO; if (!$account) { Util::WriteLog('NewSupefinaSpei_error', 'missing account for withdraw: ' . $OrderId); return 'fail'; } // 银行编号:优先 BankNO(如果看作 bankId),否则使用配置默认 $bankId = $BranchBank; if ($bankId === '') { Util::WriteLog('NewSupefinaSpei_error', 'missing bankId for withdraw: ' . $OrderId); return 'fail'; } // 提现金额是以分存储,转成两位小数金额 $orderAmount = number_format($amount / 100, 2, '.', ''); $bankList = config('games.mex_bank_list'); try { $now = time() * 1000; $data = [ 'transTime' => $now, 'merchantOrderNo' => $OrderId, 'amount' => (string)$orderAmount, 'country' => $config['country'] ?? 'MX', 'currency' => $config['currency'] ?? 'MXN', 'payeeInfo' => [ 'accountInfo' => [ 'accountNo' => $account, 'accountType' => mb_strlen($account) == 16 ? '20' : '10', ], 'bankInfo' => [ 'bankCode' => $bankId, 'bankName' => $bankList[$bankId] ?? 'bank', ], 'payeeName' => $accountName ?: 'slot777', ], 'notifyUrl' => $config['cashCallbackUrl'], ]; $url = $config['baseUrl'] . $config['transferPath']; $result = $this->sendRequest($url, 'TRANSFER', $data); if ($result === false) { return 'fail'; } } catch (\Throwable $e) { Util::WriteLog('NewSupefinaSpei_error', 'payout request exception: ' . $e->getMessage()); return ''; } try { $responseData = \GuzzleHttp\json_decode($result, true); } catch (\Throwable $e) { Util::WriteLog('NewSupefinaSpei_error', [$result, $e->getMessage()]); $this->error = 'Payment processing error'; return false; } if (!isset($responseData['success']) || $responseData['success'] !== true) { $this->error = $responseData['message'] ?? 'request failed'; // 同步下单失败:如果订单在处理中(State=5),退回资金并标记失败 if ((int)$query->State === 5) { $msg = $data['msg'] ?? 'NewSupefinaSpei payout failed'; $WithDraw = $query->WithDraw + $query->ServiceFee; $bonus = '30000,' . $WithDraw; PrivateMail::failMail($query->UserID, $OrderId, $WithDraw, $msg, $bonus); $withdraw_data = [ 'State' => 6, 'agent' => self::AGENT, 'finishDate' => now(), 'remark' => json_encode($data), ]; DB::connection('write') ->table('QPAccountsDB.dbo.OrderWithDraw') ->where('OrderId', $query->OrderId) ->update($withdraw_data); $RecordData = ['after_state' => 6, 'update_at' => now()]; DB::connection('write') ->table('QPAccountsDB.dbo.AccountsRecord') ->where('type', 1) ->where('RecordID', $RecordID) ->update($RecordData); } return 'fail'; } $decryptData = Aes::decrypt($responseData['data'], base64_decode($config['aesKey'])); Util::WriteLog('NewSupefinaSpei', 'transfer decrypt response: ' . $decryptData); $this->result = json_decode($decryptData, true); return $this->result; } public function cashNotify($post) { $OrderId = $post['merchantOrderNo']; if ($OrderId === '') { Util::WriteLog('NewSupefinaSpei_error', 'payout notify missing merchantOrderNo'); return 'fail'; } $query = DB::connection('write') ->table('QPAccountsDB.dbo.OrderWithDraw') ->where('OrderId', $OrderId) ->first(); if (!$query) { Util::WriteLog('NewSupefinaSpei_error', 'withdraw order not found in notify: '.$OrderId); return '{"success":true}'; } // 只处理 State=5 或 7 的订单,避免重复 if (!in_array((int)$query->State, [5, 7], true)) { Util::WriteLog('NewSupefinaSpei', 'withdraw already handled: '.$OrderId); return '{"success":true}'; } $status = $post['transStatus'] ?? ''; $orderStatus = 0; if (in_array($status, ['SUCCESS'], true)) { $orderStatus = 1; // 成功 } elseif (in_array($status, ['FAILED', 'CANCELED', 'REFUNDED'])) { $orderStatus = 2; // 失败 } if ($orderStatus === 0) { Util::WriteLog('NewSupefinaSpei', 'payout processing: '.$OrderId.' status='.$status); return '{"success":true}'; } $agentID = DB::connection('write') ->table('agent.dbo.admin_configs') ->where('config_value', self::AGENT) ->where('type', 'cash') ->value('id') ?? ''; $now = now(); $UserID = $query->UserID; $TakeMoney = $query->WithDraw + $query->ServiceFee; // Supefina 回调里带有 amount/fee 和 realityAmount/realityFee // 提醒:这里仍以我们订单金额为准做账,仅将真实金额用于日志,可根据需要扩展到统计侧。 $realityAmount = isset($post['amount']) ? (float)$post['amount'] : null; $realityFee = isset($post['fee']) ? (float)$post['fee'] : null; Util::WriteLog('NewSupefinaSpei', 'payout reality: amount='.$realityAmount.', fee='.$realityFee.', orderId='.$OrderId); $withdraw_data = []; switch ($orderStatus) { case 1: // 提现成功 $withdraw_data = [ 'State' => 2, 'agent' => $agentID, 'finishDate' => $now, ]; // 增加提现记录 $first = DB::connection('write') ->table('QPAccountsDB.dbo.UserTabData') ->where('UserID', $UserID) ->first(); if ($first) { DB::connection('write') ->table('QPAccountsDB.dbo.UserTabData') ->where('UserID', $UserID) ->increment('TakeMoney', $TakeMoney); } else { DB::connection('write') ->table('QPAccountsDB.dbo.UserTabData') ->insert(['TakeMoney' => $TakeMoney, 'UserID' => $UserID]); try { PrivateMail::praiseSendMail($UserID); } catch (\Throwable $e) { // 忽略邮件发送失败 } } // 免审记录 $withdrawal_position_log = DB::connection('write') ->table('agent.dbo.withdrawal_position_log') ->where('order_sn', $OrderId) ->first(); if ($withdrawal_position_log) { DB::connection('write') ->table('agent.dbo.withdrawal_position_log') ->where('order_sn', $OrderId) ->update(['take_effect' => 2, 'update_at' => date('Y-m-d H:i:s')]); } try { StoredProcedure::addPlatformData($UserID, 4, $TakeMoney); } catch (\Throwable $exception) { Util::WriteLog('StoredProcedure', $exception->getMessage()); } $ServiceFee = $query->ServiceFee; RecordUserDataStatistics::updateOrAdd($UserID, $TakeMoney, 0, $ServiceFee); $fee = DB::table('QPAccountsDB.dbo.OrderWithDraw')->where('OrderId', $OrderId) ->value('withdraw_fee'); (new RechargeWithDraw())->withDraw($UserID, $TakeMoney, $fee, $ServiceFee); $redis = Redis::connection(); $redis->incr('draw_'.date('Ymd').$UserID); break; case 2: // 提现失败 $msg = $post['msg'] ?? 'Withdraw rejected'; $bonus = '30000,'.$TakeMoney; PrivateMail::failMail($query->UserID, $OrderId, $TakeMoney, $msg, $bonus); Util::WriteLog('SupefinaSpeiEmail', [$query->UserID, $OrderId, $TakeMoney, $msg, $bonus]); $withdraw_data = [ 'State' => 6, 'agent' => $agentID, 'remark' => $msg, ]; break; } $RecordData = [ 'before_state' => $query->State, 'after_state' => $withdraw_data['State'] ?? 0, 'RecordID' => $query->RecordID, 'update_at' => date('Y-m-d H:i:s'), ]; DB::connection('write') ->table('QPAccountsDB.dbo.AccountsRecord') ->updateOrInsert( ['RecordID' => $query->RecordID, 'type' => 1], $RecordData ); DB::connection('write') ->table('QPAccountsDB.dbo.OrderWithDraw') ->where('OrderId', $OrderId) ->update($withdraw_data); if (isset($withdraw_data['State']) && (int)$withdraw_data['State'] === 2) { StoredProcedure::user_label($UserID, 2, $TakeMoney); } return '{"success":true}'; } public function getSignString($headers, $data) { $params = array_merge($headers, $data); $filtered = []; foreach ($params as $k => $v) { if (strtolower($k) === 'sign') { continue; } if ($v === null) { continue; } $filtered[$k] = $v; } ksort($filtered); $parts = []; foreach ($filtered as $k => $v) { $parts[] = $k . '=' . $v; } $signString = implode('&', $parts); return $signString; } public function sign($headers, $data, $key) { $signString = $this->getSignString($headers, $data); $res = openssl_pkey_get_private("-----BEGIN PRIVATE KEY-----\n" . $key . "\n-----END PRIVATE KEY-----"); openssl_sign( $signString, $signature, $res, OPENSSL_ALGO_SHA256 // RSA2 核心 ); return base64_encode($signature); } public function verifySign($data, $sign, $key) { $res = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----\n" . $key . "\n-----END PUBLIC KEY-----"); $result = openssl_verify( $data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256 ); return $result === 1; } private function sendRequest($url, $productNo, $data) { $config = (new PayConfig())->getConfig('NewSupefinaSpei'); $headers = [ 'version' => '1.0.0', 'merchantNo' => $config['merId'] ?? '', 'requestId' => mt_rand(100000, 999999) . time(), 'requestTime' => time() * 1000, ]; $body = [ 'productNo' => $productNo, 'data' => Aes::encrypt(json_encode($data), base64_decode($config['aesKey'])), ]; $sign = $this->sign($headers, $body, $config['privateKey']); $headers['sign'] = $sign; try { $client = new Client(); Util::WriteLog('NewSupefinaSpei', $productNo . ' request: ' . $url . ' | ' . json_encode($data) . '|' . json_encode($headers)); $result = $client->post($url, [ 'headers' => $headers, 'json' => $body, ])->getBody()->getContents(); Util::WriteLog('NewSupefinaSpei', $productNo . ' response: ' . $result); return $result; } catch (\Throwable $e) { Util::WriteLog('NewSupefinaSpei_error', $productNo . ' request exception: ' . $e->getMessage()); $this->error = 'Payment processing error'; return false; } } }