PayPlusLogic.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <?php
  2. namespace App\Http\logic\api;
  3. use App\dao\Pay\AccountPayInfo;
  4. use App\dao\Pay\PayController;
  5. use App\Http\helper\CreateOrder;
  6. use App\Http\helper\NumConfig;
  7. use App\Jobs\Order;
  8. use App\Notification\TelegramBot;
  9. use App\Services\CreateLog;
  10. use App\Services\OrderServices;
  11. use App\Services\PayConfig;
  12. use App\Services\PayPlus;
  13. use App\Util;
  14. use Illuminate\Support\Facades\DB;
  15. class PayPlusLogic extends BaseApiLogic
  16. {
  17. protected $service;
  18. public function __construct(PayPlus $service = null)
  19. {
  20. $this->service = $service ?: new PayPlus();
  21. }
  22. public function pay_order(
  23. $userId,
  24. $payAmount,
  25. $userPhone,
  26. $userEmail,
  27. $userName,
  28. $GiftsID,
  29. $buyIP,
  30. $AdId,
  31. $eventType,
  32. $payMethod = ''
  33. ) {
  34. $dao = new AccountPayInfo();
  35. list($userPhone, $userName, $userEmail) = $dao->payInfo($userId);
  36. $payVerify = new PayController();
  37. $payAmount = $payVerify->verify($userId, $GiftsID, $payAmount);
  38. if ($payAmount === false || $payAmount < 0) {
  39. $this->error = $payVerify->getError() ?: 'Payment error_4';
  40. return false;
  41. }
  42. $orderSn = CreateOrder::order_sn($userId);
  43. $amount = (int) round($payAmount * NumConfig::NUM_VALUE);
  44. $logic = new OrderLogic();
  45. if (
  46. !$logic->orderCreate(
  47. $orderSn,
  48. $amount,
  49. 'PayPlus',
  50. $userId,
  51. $payMethod,
  52. $GiftsID,
  53. $AdId,
  54. $eventType
  55. )
  56. ) {
  57. $this->error = $logic->getError();
  58. return false;
  59. }
  60. $payload = $this->buildPaymentPayload([
  61. 'order_sn' => $orderSn,
  62. 'amount' => $payAmount,
  63. 'user_id' => $userId,
  64. 'user_email' => $userEmail,
  65. 'user_phone' => $userPhone,
  66. 'user_name' => $userName,
  67. 'buy_ip' => $buyIP,
  68. 'pay_method' => $payMethod,
  69. ]);
  70. CreateLog::pay_request(
  71. $userPhone,
  72. json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
  73. $orderSn,
  74. $userEmail,
  75. $userId,
  76. $userName
  77. );
  78. Util::WriteLog('PayPlus', 'PayPlus payment request: ' . json_encode($payload));
  79. try {
  80. $result = $this->service->postPayin($payload);
  81. Util::WriteLog('PayPlus', 'PayPlus payment response: ' . json_encode($result));
  82. if ($result['code'] !== 200) {
  83. TelegramBot::getDefault()->sendProgramNotify(
  84. 'PayPlus payment failed',
  85. 'Response: ' . json_encode($result),
  86. null
  87. );
  88. }
  89. return $result;
  90. } catch (\Exception $exception) {
  91. Util::WriteLog('PayPlus_error', $exception->getMessage());
  92. $this->error = 'Payment processing error';
  93. return false;
  94. }
  95. }
  96. public function buildPaymentPayload(array $input)
  97. {
  98. $config = $this->service->getConfig();
  99. $payMethod = (int) ($input['pay_method'] ?: 1);
  100. $methodMap = $config['payment_methods'] ?? [
  101. 1 => 8,
  102. 2 => 2,
  103. 4 => 1,
  104. 8 => 5,
  105. ];
  106. $nameParts = preg_split('/\s+/', trim((string) ($input['user_name'] ?? '')), 2);
  107. return [
  108. 'order_type' => 'RECHARGE',
  109. 'platform_order_id' => (string) $input['order_sn'],
  110. 'currency' => strtoupper($config['currency'] ?? 'USD'),
  111. 'amount' => number_format((float) $input['amount'], 2, '.', ''),
  112. 'payment_method' => $methodMap[$payMethod] ?? 8,
  113. 'return_url' => $config['return'] ?? '',
  114. 'cancel_url' => $config['cancel'] ?? ($config['return'] ?? ''),
  115. 'connection_info' => [
  116. 'ip' => $input['buy_ip'] ?: '0.0.0.0',
  117. 'country' => $config['country'] ?? 'US',
  118. 'state' => $config['state'] ?? 'NY',
  119. 'zip_code' => $config['zip'] ?? '00000',
  120. 'media_source' => $config['media_source'] ?? 'organic',
  121. 'language' => $config['language'] ?? 'en-US',
  122. ],
  123. 'account_info' => [
  124. 'merchant_user_id' => (string) $input['user_id'],
  125. 'create_time' => time()*1000,
  126. 'role' => 'PRIVATE',
  127. 'email' => $this->emailOrDefault($input['user_email'] ?? '', $input['user_id']),
  128. 'phone' => preg_replace('/\D+/', '', (string) ($input['user_phone'] ?? '')) ?: '0000000000',
  129. 'area_code' => $config['area_code'] ?? '1',
  130. 'first_name' => chr(mt_rand(65, 90)) . 'user',
  131. 'last_name' => chr(mt_rand(65, 90)) . 'user',
  132. 'vip_level' => 0,
  133. ],
  134. ];
  135. }
  136. public function notify(array $post)
  137. {
  138. $orderSn = $post['data']['platform_order_id'] ?? '';
  139. if ($orderSn === '') {
  140. return 'success';
  141. }
  142. $order = DB::connection('write')
  143. ->table('agent.dbo.order')
  144. ->where('order_sn', $orderSn)
  145. ->first();
  146. if (!$order || !empty($order->pay_at) || !empty($order->finished_at)) {
  147. return 'success';
  148. }
  149. $data = $post['data'] ?? [];
  150. $body = [
  151. 'payment_sn' => $data['order_id'] ?? '',
  152. 'updated_at' => date('Y-m-d H:i:s'),
  153. ];
  154. if ($this->isSuccessfulPayment($post)) {
  155. if (!$this->confirmSuccessfulPayment($post)) {
  156. Util::WriteLog('PayPlus', 'PayPlus order query not successful: ' . json_encode($post));
  157. return 'success';
  158. }
  159. $AdId = $order->AdId ?: '';
  160. $eventType = $order->eventType ?: '';
  161. $payAmount = round((float) ($data['amount'] ?? 0), 2);
  162. $body['pay_status'] = 1;
  163. $body['pay_at'] = date('Y-m-d H:i:s');
  164. $body['finished_at'] = date('Y-m-d H:i:s');
  165. $body['amount'] = (int) round($payAmount * NumConfig::NUM_VALUE);
  166. $config = (new PayConfig())->getConfig('PayPlus');
  167. // 根据支付方式计算代收手续费: 费率% * 金额 + 固定$
  168. // pay_rate 格式: [1=>[10,0.3], 2=>[13,0.3], 4=>[11,0.3], 8=>[12,0.3]]
  169. $payRates = $config['pay_rate'] ?? null;
  170. if (is_array($payRates)) {
  171. $payMethod = $order->order_title ?? 1;
  172. $payRate = $payRates[$payMethod] ?? ($payRates[1] ?? [10, 0.3]);
  173. $feePercent = $payRate[0] ?? 10;
  174. $feeFixed = $payRate[1] ?? 0.3;
  175. $body['payment_fee'] = intval(($body['amount'] * $feePercent) / 100)
  176. + (int)($feeFixed * NumConfig::NUM_VALUE);
  177. }
  178. $service = new OrderServices();
  179. list($give, $favorablePrice, $recharge, $czReason, $cjReason) = $service->getPayInfo(
  180. $order->GiftsID ?: '',
  181. $order->user_id ?: '',
  182. $payAmount
  183. );
  184. list($score) = $service->addRecord(
  185. $order->user_id,
  186. $payAmount,
  187. $favorablePrice,
  188. $orderSn,
  189. $order->GiftsID,
  190. $recharge,
  191. $czReason,
  192. $give,
  193. $cjReason,
  194. $order->AdId ?: '',
  195. $order->eventType ?: '',
  196. $body['payment_fee'] ?? 0
  197. );
  198. Order::dispatch([
  199. $order->user_id,
  200. $payAmount,
  201. $score,
  202. $favorablePrice,
  203. $order->GiftsID,
  204. $orderSn,
  205. ]);
  206. } else {
  207. return 'success';
  208. }
  209. DB::connection('write')
  210. ->table('agent.dbo.order')
  211. ->where('order_sn', $orderSn)
  212. ->update($body);
  213. return 'success';
  214. }
  215. public function isSuccessfulPayment(array $post)
  216. {
  217. return ($post['event'] ?? '') === 'PAYMENT.CAPTURE.COMPLETED'
  218. && ($post['event_detail_name'] ?? '') === 'payment_captured'
  219. && (int) ($post['data']['order_status'] ?? 0) === 3;
  220. }
  221. public function confirmSuccessfulPayment(array $post)
  222. {
  223. $data = $post['data'] ?? [];
  224. $platformOrderId = $data['platform_order_id'] ?? '';
  225. if ($platformOrderId === '') {
  226. return false;
  227. }
  228. try {
  229. $result = $this->service->queryPayinOrder($platformOrderId, $data['order_id'] ?? '');
  230. } catch (\Exception $exception) {
  231. Util::WriteLog('PayPlus_error', 'PayPlus query order failed: ' . $exception->getMessage());
  232. return false;
  233. }
  234. return $this->isSuccessfulPaymentQueryResult($result);
  235. }
  236. public function isSuccessfulPaymentQueryResult(array $result)
  237. {
  238. $data = $result['decryptedComponentDelta'] ?? ($result['data'] ?? $result);
  239. return (int) ($data['order_status'] ?? 0) === 3;
  240. }
  241. public function isFailedPayment(array $post)
  242. {
  243. $event = $post['event'] ?? '';
  244. $detail = $post['event_detail_name'] ?? '';
  245. $status = (int) ($post['data']['order_status'] ?? 0);
  246. return ($event === 'PAYMENT.CAPTURE.COMPLETED' && in_array($status, [4, 10, 11], true))
  247. || $event === 'PAYMENT.ORDER.TIMEOUT'
  248. || in_array($detail, ['payment_declined', 'payment_timeout'], true);
  249. }
  250. protected function resolvePaymentUrl(array $result)
  251. {
  252. $data = $result['data'] ?? $result;
  253. foreach (['cashierUrl', 'cashier_url', 'payment_url', 'paymentUrl', 'redirect_url', 'url'] as $key) {
  254. if (!empty($data[$key])) {
  255. return $data[$key];
  256. }
  257. }
  258. return '';
  259. }
  260. protected function emailOrDefault($email, $userId)
  261. {
  262. return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : 'unknown' . $userId . '@example.com';
  263. }
  264. protected function stringOrDefault($value, $default)
  265. {
  266. $value = trim((string) $value);
  267. return $value === '' ? $default : $value;
  268. }
  269. }