Pay.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <?php
  2. namespace app\controller;
  3. use app\BaseController;
  4. use think\facade\Db;
  5. //支付控制器
  6. class Pay extends BaseController
  7. {
  8. protected $noNeedLogin = ['payOrder','payCallback'];
  9. public $baseUrl = 'https://pay.v8jisu.cn';
  10. public $merchantId = '28399';
  11. public $key = '0QknLivpRQB50Bq76r06PiR464SrTrvQ';
  12. public $merchantPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy9M3hkPM/rAeow0Y6bjJzHFy4btLxIZ4gMB3ggqP1MBDTg+oLwrVfU8OUxYG7/yntiFqDFNOAopccygsNRTzdrWQjenbJ4g9xCwWiIiubMRAKfgYNX9AsUalxcXpWPvmCLKHc4YdHGh8+GrV0qZi/FAQ7uMLXeowe+Np9cUziXIDLYbgJXB0UBqEyI2GVBJMOuFqKaP5pay/DcTTQwLKcHyzPlOUTlMet4ClPrAhqfe/FQYIfa8nFtrSevAzPMwJuZfYrFr6DvyQADaP16RZQS0tLMPW47646ieQ7GmITixXd5yEzlBTlECh1sIKyzn9DjzLkmrVFsDim4NFgJp/eQIDAQAB';
  13. public $merchantPrivateKey = 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDkSKP7fp5hjHFgYufalba3+qeJMPKHhXCm1yk7jJKkOIHkgW8tpcQqw/c8krwIRdjW6W5YF5Jr/b3cXP1JmyIeyLfbglSck+GijdFGMWlwbNAPVO/TrJUy8/+beFrqVJdCBlsRYW6lml4aGMXFTIK3Rn69zRfvgUFDYcZOa0VGTfXjfR0hOME6vxw1DxCOvWxczh8uLSeFnbirIyKaES807XGC3eCTmyJi/UsPA3vQeUUTpXliitSZFH/+T6g2K4n0Oy2OER5cTFWL8p/Gpy/pxoxALrc9eSIv8diLIco7WPfFYPABeZFe0cF4FwBeVaCqh1/K9m9uE6SAXhJUKZbzAgMBAAECggEAahuRjQ5Xk8Px1vliB2nbWjy5rrz/nhpaOFJ+Kd23M3nIdmvrP25zdeVMf+08VSQSHCK4VV3vgx6YJ1tZp+LhwylMvE0iAv2BvUrp4RSKi+Un+FhkeSEY4GwlfSA+MflLrTbDEZsWEQdlgf/NvV1IzOOJebNg0sRjj2xc/opB0uD9Dp2CMmdR4cztUXlmnVx0i5UQxl59u0TQitcuriTmqre4qibYX3ddr1V91k7WCde25a7HEdoQSID566O3H3tE6+XSIEp6LXpYrZFSK+b9Zc+V7khr27AmEMsSPpy/nSPI8pKwjaoFbLaGrh9PToXin5ENGbVY79fARIVe+HfAoQKBgQD1K1yqSEs+F09Aml6xY5BjqJ14HJQ8DphktrGzlI0hYHi42OtEfaPOrThLHGes4TipJUnvWjG0YqNaW+t9j2733OShUNF5BqiwTyfZ1/F+UffU+m6Dq0aB/DZV7KGB6rfQ1XUn77rIMs0gcm7BMwhfZLXUWDhj6qen+kMCO9PY+QKBgQDuXlFaQh1/ByR/urOqU6x9EGr5B0iT2QXM1vKbGOpj2wZIRV+wJtfnSy5kTxRigGgm1gXHgqYwRrONGR/0O/yfNVEm/OuEy4IUNBXsU/vjmzpfSNt2ajY91bMuf6aAZ1M688o08ouL4mrCKWQHUXIN8jxnPN6Yp84TcotghZi2SwKBgQCO6TnY4M9LYFcIN3PfP0RZc15nN3GJGJDolD49iehCfnOgfIGXqQ0lWn+n+OTON3LJ1jyk0xSKK71A3LgGtudegFqdVfjk7WbDb0CxkVjp42ntshVdlydAef5KU+dJTcLcbrEeGHXuYP6FXW8GG3NT9+at4sbsJ0qXdiA9WxaAMQKBgHDV5u6x42KRT/7Cs2/KYhllny24++s4zV0U1w0CM1oHgSbO6Cfri0JqvVAwevbRz/uqTlwOBXtOzInbPdwQVVpME9k/2oEnELFdoo8XhmJMxcn7JCAe0QReV46IUJnxz11Vr/92XQZfrKeyji5EqJffdiZskvZyYMOl8kJDm3GXAoGBAOOd8/HCGGeaMA7Uv9bjW17BQ9d/mJ/KmBI/S0t0/Y5Ur7hdtcS/5fw9lHPIqXTP+CJhE5+sQOi5GiwWHy0CCUlWFfi3kwlNTrau3m56sx7chjvaY/1F3b4sG7eFJMbgi8BjXiZREHJ1dehczF9pguIT/rcCtkpDMaWy8DIrSRdp';
  14. // 生成RSA公钥
  15. private function makeRsaSign($data)
  16. {
  17. // 排序 & 拼接
  18. ksort($data);
  19. $str = '';
  20. foreach ($data as $k => $v) {
  21. if ($v !== "" && $k != 'sign' && $k != 'sign_type' && !is_array($v)) {
  22. $str .= "{$k}={$v}&";
  23. }
  24. }
  25. $str = rtrim($str, "&");
  26. // 拼接PEM格式
  27. $privateKey = "-----BEGIN PRIVATE KEY-----\n"
  28. . chunk_split($this->merchantPrivateKey, 64, "\n")
  29. . "-----END PRIVATE KEY-----\n";
  30. $res = openssl_get_privatekey($privateKey);
  31. if (!$res) {
  32. throw new \Exception('私钥格式错误或无法加载');
  33. }
  34. openssl_sign($str, $sign, $res, OPENSSL_ALGO_SHA256);
  35. return base64_encode($sign);
  36. }
  37. // 生成 MD5 签名
  38. private function makeMd5Sign($data)
  39. {
  40. $md5_key = $this->key; // 商户密钥
  41. ksort($data); // 按照 ASCII 码排序
  42. $str = '';
  43. foreach ($data as $k => $v) {
  44. if ($v !== "" && $k != 'sign' && $k != 'sign_type') {
  45. // 排除空值、sign 和 sign_type
  46. $str .= "{$k}={$v}&";
  47. }
  48. }
  49. $str = rtrim($str, "&"); // 去掉最后一个 &
  50. $str .= $md5_key; // 按文档要求,直接拼接 KEY
  51. return md5($str); // 小写
  52. }
  53. // 发起Http请求
  54. private function HttpRequest($url, $postData)
  55. {
  56. // 将数组编码为 x-www-form-urlencoded 格式的查询字符串
  57. $postData1 = http_build_query($postData);
  58. // 初始化 cURL 会话
  59. $ch = curl_init($url);
  60. // 设置 cURL 选项
  61. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 将响应作为字符串返回
  62. curl_setopt($ch, CURLOPT_POST, true); // 使用 POST 方法
  63. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData1); // 设置 POST 数据
  64. curl_setopt($ch, CURLOPT_HTTPHEADER, array(
  65. 'Content-Type: application/x-www-form-urlencoded' // 设置 Content-Type 头
  66. ));
  67. // 执行请求并获取响应
  68. $response = curl_exec($ch);
  69. // 关闭 cURL 会话
  70. curl_close($ch);
  71. // 检查请求是否成功
  72. if ($response === false) {
  73. return "cURL Error: " . curl_error($ch);
  74. } else {
  75. // 处理响应
  76. return $response;
  77. }
  78. }
  79. // JSON清理
  80. function fixEscapedJson($jsonString) {
  81. // 移除多余的反斜杠
  82. $fixed = stripslashes($jsonString);
  83. // 替换HTML实体
  84. $fixed = str_replace(
  85. ['&quot;', '&amp;', '&lt;', '&gt;'],
  86. ['"', '&', '<', '>'],
  87. $fixed
  88. );
  89. return $fixed;
  90. }
  91. //拉起订单
  92. public function order()
  93. {
  94. $goodsName = $this->request->post('goodsName'); // 商品名称
  95. $amount = $this->request->post('amount'); // 商品金额
  96. $type = $this->request->post('type'); // 支付方式
  97. $isVip = $this->request->post('isVip') ?? 0; // 是否为购买VIP
  98. $user = $this->getUser();
  99. if (!$goodsName || !$type || !is_numeric($amount)) {
  100. $this->fail(500, '参数校验错误');
  101. }
  102. if (!$user) {
  103. $this->fail(500, '用户未登录');
  104. }
  105. // 参数构建
  106. $data['pid'] = $this->merchantId;
  107. $data['method'] = 'jump'; // 接口类型
  108. $data['device'] = 'pc'; // 设备类型
  109. $data['type'] = $type; // 支付方式
  110. $data['out_trade_no'] = date('YmdHis') . strval(mt_rand(100000, 999999)); // 订单号
  111. $data['notify_url'] = 'https://api.danjiwanjia.com/pay/payCallback'; // 服务器异步通知地址
  112. $data['return_url'] = 'https://www.danjiwanjia.com/#/userCenter'; // 页面跳转通知地址
  113. $data['name'] = $goodsName; // 商品名称
  114. $data['money'] = $amount; // 商品金额
  115. $data['clientip'] = $this->request->ip(); // 用户ip地址
  116. $data['timestamp'] = time(); // 当前时间戳
  117. $data['param'] = json_encode(['isVip' => $isVip]); // 业务拓展参数 通过此参数判断用户是否通过VIP页面调起支付
  118. $data['sign_type'] = 'RSA'; // 当前时间戳
  119. $data['sign'] = $this->makeRsaSign($data); // 签名生成
  120. // 插入数据表
  121. Db::table('tb_user_order')
  122. ->insert([
  123. 'user_id' => $user->user_id,
  124. 'balance' => $amount,
  125. 'state' => 0,
  126. 'created_at' => time(),
  127. 'order_no' => $data['out_trade_no'],
  128. ]);
  129. // 发起请求
  130. $url = $this->baseUrl . '/api/pay/create';
  131. $result = $this->HttpRequest($url, $data);
  132. // header('Content-Type: application/json');
  133. // echo $result;
  134. // exit();
  135. $result = json_decode($result, true);
  136. if($result['code'] == 0) {
  137. $this->success('success', $result['pay_info']);
  138. } else {
  139. $this->fail(500, $result['msg']);
  140. }
  141. }
  142. //获取我的订单
  143. public function getMyOrder()
  144. {
  145. $page = $this->request->post('page'); //页码
  146. $pageNum = $this->request->post('pageNum'); //每页显示的数据条数
  147. if (!is_numeric($pageNum) || !is_numeric($page)) {
  148. $this->fail(500, 'page或pageNum参数校验错误');
  149. }
  150. $data = Db::table('tb_user_order')
  151. ->where(['user_id' => $this->getUser()->user_id])
  152. ->order('created_at DESC')
  153. ->paginate([
  154. 'page' => $page,
  155. 'list_rows' => $pageNum,
  156. ]);
  157. $this->success('success', $data);
  158. }
  159. /**
  160. * 支付回调
  161. */
  162. public function payCallback()
  163. {
  164. $data = $this->request->param();
  165. error_log('payCallback: '.json_encode($data));
  166. if(!$data) {
  167. exit('无法获取到传参');
  168. }
  169. $order_sn = $data['out_trade_no'];
  170. $amount = $data['money'];
  171. $isVip = json_decode($this->fixEscapedJson($data['param']), true)['isVip'];
  172. $order = Db::name('tb_user_order')->where('order_no', $order_sn)->find();
  173. if (!$order) {
  174. exit('订单不存在');
  175. }
  176. if ($order['state'] == 1) {
  177. // 订单已支付 不走后面的逻辑但是要返回success
  178. exit('success');
  179. }
  180. // 订单校验通过 更新订单状态
  181. Db::name('tb_user_order')
  182. ->where('order_no', $order_sn)
  183. ->update([
  184. 'state' => 1,
  185. 'pay_at' => time(),
  186. ]);
  187. // 业务判断
  188. if($isVip == 1) {
  189. // 增加用户VIP时长
  190. // 查找对应价格VIP等级
  191. $vipInfo = Db::name('tb_user_vip_price')
  192. ->where('user_id', $order['user_id'])
  193. ->where('price', $amount)
  194. ->find();
  195. // 首先判断当前用户是否已经为VIP
  196. $userVip = Db::name('tb_user_vip')->where('user_id', $order['user_id'])->find();
  197. if($userVip) {
  198. // 如果用户已经为VIP 且为同类型VIP则增加VIP时长
  199. if($userVip['type'] == $vipInfo['type']) {
  200. Db::name('tb_user_vip')
  201. ->where('user_id', $order['user_id'])
  202. ->setInc('expired_at', strtotime($vipInfo['duration'] . 'month'));
  203. } else {
  204. // 如果用户已经为VIP 且为不同类型VIP则删除原有VIP记录并新增VIP记录
  205. Db::name('tb_user_vip')
  206. ->where('user_id', $order['user_id'])
  207. ->delete();
  208. Db::name('tb_user_vip')->insert([
  209. 'user_id' => $order['user_id'],
  210. 'type' => $vipInfo['type'],
  211. 'expired_at' => strtotime($vipInfo['duration'] . 'month')
  212. ]);
  213. }
  214. } else {
  215. // 如果用户不是VIP 则新增VIP记录
  216. Db::name('tb_user_vip')->insert([
  217. 'user_id' => $order['user_id'],
  218. 'type' => $vipInfo['type'],
  219. 'expired_at' => strtotime($vipInfo['duration'] . 'month')
  220. ]);
  221. }
  222. } else {
  223. // 增加用户余额
  224. Db::name('tb_user')
  225. ->where('id', $order['user_id'])
  226. ->inc('balance', $amount * 2) // 限时活动 实际到账金额为金额的2倍
  227. ->update();
  228. }
  229. exit('success');
  230. }
  231. }