MachantPay.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <?php
  2. namespace tw\services\payment;
  3. use \Yurun\PaySDK\Weixin\Params\PublicParams;
  4. use \Yurun\PaySDK\Weixin\CompanyPay\Weixin\Pay\Request;
  5. use \Yurun\PaySDK\Weixin\CompanyPay\Bank\Pay\Request as BankRequest;
  6. use \Yurun\PaySDK\Weixin\SDK; // TODO: update to V3
  7. use crmeb\services\SystemConfigService;
  8. use EasyWeChat\Core\Exception;
  9. use \think\facade\Config;
  10. /**
  11. * 企业付款
  12. *
  13. * 包含微信/支付宝等付款到零钱,付款到银行卡
  14. */
  15. class MachantPay
  16. {
  17. const BANK_MAP = [
  18. '工商银行' => '1002',
  19. '农业银行' => '1005',
  20. '建设银行' => '1003',
  21. '中国银行' => '1026',
  22. '交通银行' => '1020',
  23. '招商银行' => '1001',
  24. '邮储银行' => '1066',
  25. '民生银行' => '1006',
  26. '平安银行' => '1010',
  27. '中信银行' => '1021',
  28. '浦发银行' => '1004',
  29. '兴业银行' => '1009',
  30. '光大银行' => '1022',
  31. '广发银行' => '1027',
  32. '华夏银行' => '1025',
  33. '中原银行' => '4753',
  34. '河南省农村信用社' => '4115',
  35. '山西省农村信用社' => '4156',
  36. '安徽省农村信用社' => '4166',
  37. ];
  38. /**
  39. * 获得微信支付-付款到零钱/银行卡 API 用到的参数
  40. *
  41. * DEPENDENCIES: 数据库读取
  42. */
  43. protected static function getWeixinParams()
  44. {
  45. // 读取配置
  46. $payment = SystemConfigService::more([
  47. 'pay_routine_appid',
  48. 'pay_routine_mchid',
  49. 'pay_routine_key',
  50. 'pay_routine_client_cert',
  51. 'pay_routine_client_key',
  52. 'pay_weixin_open',
  53. ]);
  54. if (!isset($payment['pay_weixin_open']) || !$payment['pay_weixin_open']) {
  55. throw new Exception('weixin pay is not enabled.', -1);
  56. }
  57. $params = new PublicParams();
  58. $params->appID = $payment['pay_routine_appid'] ?? '';
  59. $params->mch_id = $payment['pay_routine_mchid'] ?? '';
  60. $params->key = $payment['pay_routine_key'] ?? '';
  61. $params->keyPath = realpath('.' . $payment['pay_routine_client_key']);
  62. $params->certPath = realpath('.' . $payment['pay_routine_client_cert']);
  63. return $params;
  64. }
  65. /**
  66. * 付款到微信零钱
  67. *
  68. * 文档见 (https://doc.yurunsoft.com/PaySDK/112)
  69. *
  70. * NOTICE: 本函数只是调用微信 API,并未进行业务逻辑处理。
  71. *
  72. * @param int $openid: wechat user openid
  73. * @param string $trade_no: 付款订单号,平台自定义
  74. * @param int $amount: 金额,单位为元,内部转换为分
  75. * @param string $desc: 订单描述
  76. * @param string $realname: 收款放真实姓名, 参数 check_name 为 FORCE_CHECK 时使用。
  77. *
  78. * @return (bool, int, string) (是否成功,错误代码,错误信息)
  79. *
  80. * TODO: 微信付款升级为 V3(https://wechatpay-api.gitbook.io/wechatpay-api-v3/wei-xin-zhi-fu-api-v3-jie-kou-gui-fan)
  81. * 本功能使用 Yurunsoft/PaySDK 也已支持 V3 (2021/11/28),但申请微信支付时未申请微信 V3 相关 Key。等待升级使用 V3 协议,或再做一个函数
  82. */
  83. public static function toWeixin($openid, $trade_no, $amount, $desc = '', $realname = '')
  84. {
  85. try {
  86. $caller_ip = Config::get('app.server_ip', '127.0.0.1');
  87. $params = self::getWeixinParams();
  88. $sdk = new SDK($params);
  89. $req = new Request();
  90. $req->partner_trade_no = $trade_no;
  91. $req->openid = $openid;
  92. $req->check_name = 'NO_CHECK';
  93. $req->re_user_name = $realname;
  94. $req->amount = intval(bcmul($amount, 100, 0));
  95. $req->desc = $desc;
  96. $req->spbill_create_ip = $caller_ip; // 调用接口的机器IP, 这个可能微信用于验证
  97. $res = $sdk->execute($req);
  98. if (!$sdk->checkResult($res)) {
  99. $err = $sdk->getError($res);
  100. errlog("toWeixin(): error=$err openid=$openid amount=$amount");
  101. return [false, $sdk->getErrorCode($res), '微信通道执行失败'];
  102. } else {
  103. return [true, 0, ''];
  104. }
  105. } catch (\Exception $e) {
  106. $err = $e->getMessage();
  107. errlog("toWeixin(): exception=$err openid=$openid amount=$amount");
  108. return [false, $e->getCode(), '微信通道执行失败'];
  109. }
  110. }
  111. /**
  112. * 微信支付提现到银行卡的手续费
  113. *
  114. * 本函数不考虑各种环节的限额。
  115. *
  116. * @param float|string|int $amount: 提现金额
  117. * @return [bool, float, float, int, int] : 第一个值表示成功否,成功的前提是 提现金额 > 手续费,否则没意义。
  118. */
  119. public static function toBankByWeixinFee($amount)
  120. {
  121. $min = 1;
  122. $max = 25;
  123. $rate = 0.01;
  124. if ($amount < $min) {
  125. return [false, 0, $rate, $min, $max,];
  126. }
  127. $fee = floatval(bcmul($amount, $rate, 2));
  128. if ($fee < $min) {
  129. $fee = $min;
  130. } else if ($fee > $max) {
  131. $fee = $max;
  132. }
  133. // 是否免手续费
  134. $free = Config::get('activity.bank_extract_free', false);
  135. if ($free) {
  136. $fee = 0;
  137. }
  138. return [true, $fee, $rate, $min, $max];
  139. }
  140. /**
  141. * 通过微信支付付款到银行卡
  142. *
  143. * @param string $trade_no: 本次交易订单号
  144. * @param float $amount: 金额(元),内部转为分
  145. * @param string $bank_no: 银行卡号
  146. * @param string $true_name: 户名
  147. * @param string $bank_code: 银行名称,内部转为银行代码(微信平台官方定义 https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4)
  148. * @param string $desc: 描述,会通过银行收到
  149. */
  150. public static function toBankByWeixin($trade_no, $amount, $bank_no, $true_name, $bank_name, $desc = '')
  151. {
  152. try {
  153. $params = self::getWeixinParams();
  154. $sdk = new SDK($params);
  155. $req = new BankRequest();
  156. // TODO: 部署时需要这个文件提前就位,后台暂时没有上传接口
  157. $rsaPublicCertFile = dirname($params->certPath) . DIRECTORY_SEPARATOR . 'wx_rsa_pub_key.pem';
  158. $req->rsaPublicCertFile = $rsaPublicCertFile;
  159. $req->partner_trade_no = $trade_no;
  160. $req->enc_bank_no = $bank_no;
  161. $req->enc_true_name = $true_name;
  162. $bank_code = self::BANK_MAP[$bank_name] ?? '';
  163. if (!$bank_code) {
  164. return [false, 100403, '不支持的银行'];
  165. }
  166. $req->bank_code = $bank_code;
  167. $req->amount = intval(bcmul($amount, 100, 0));
  168. // 最长为 100 字符,一般不会超过,超过就让失败
  169. if (mb_strlen($desc) > 0) {
  170. $req->desc = $desc;
  171. }
  172. $res = $sdk->execute($req);
  173. if (!$sdk->checkResult($res)) {
  174. $err = $sdk->getError($res);
  175. errlog("error=$err bank_no=$bank_no true_name=$true_name bank_name=$bank_name amount=$amount");
  176. return [false, $sdk->getErrorCode($res), '付款通道执行失败'];
  177. } else {
  178. return [true, 0, ''];
  179. }
  180. } catch (\Exception $e) {
  181. $err = $e->getMessage();
  182. errlog("exception=$err bank_no=$bank_no true_name=$true_name bank_name=$bank_name amount=$amount");
  183. return [false, $e->getCode(), '付款通道执行失败'];
  184. }
  185. }
  186. /**
  187. * 获取微信 RSA PUBLIC KEY
  188. *
  189. * 调用接口后,保存 pub_key 字段内容为 pem (eg. pub.pem) 文件,手动分为一行一行的格式。
  190. * 然后执行 openssl rsa -RSAPublicKey_in -in rsa_pub_key.pem -out pcs8.pem
  191. * pcs8.pem 可用于加密 API
  192. */
  193. public static function get_rsa()
  194. {
  195. try {
  196. $params = self::getWeixinParams();
  197. $sdk = new SDK($params);
  198. $req = new \Yurun\PaySDK\Weixin\GetPublicKey\Request();
  199. $res = $sdk->execute($req);
  200. var_dump($res);
  201. } catch (\Exception $e) {
  202. var_dump($e->getMessage());
  203. }
  204. }
  205. /**
  206. * 付款到支付宝
  207. */
  208. public static function toAlipay()
  209. {
  210. }
  211. /**
  212. * 通过支付宝付款到银行卡
  213. */
  214. public static function toBankByAlipay()
  215. {
  216. }
  217. }