Env::get("wxpay.wx_app_appid") ?? "", "app_secret" => Env::get("wxpay.wx_app_secret") ?? "", ], WxService::wxMchConfig()); } private static function wxWebConfig() { return array_merge([ "appid" => Env::get("wxpay.wx_h5_appid") ?? "", "app_secret" => Env::get("wxpay.wx_h5_secret") ?? "", ], WxService::wxMchConfig()); } private static function wxAppletConfig() { return array_merge([ "appid" => Env::get("wxpay.wx_applet_appid") ?? "", "app_secret" => Env::get("wxpay.wx_applet_secret") ?? "", ], WxService::wxMchConfig()); } private static function wxMchConfig() { return [ "mch_id" => Env::get("wxpay.wx_mch_id") ?? "", "mch_v3_api" => Env::get("wxpay.wx_mch_v3_api") ?? "", "mch_certificate_serial" => Env::get("wxpay.wx_mch_certificate_serial") ?? "", "mch_private_key_file_path" => ROOT_PATH . "certificate" . DS . "wx" . DS . "apiclient_key.pem", "platform_certificate_file_path" => ROOT_PATH . "certificate" . DS . "wx" . DS . "apiclient_cert.pem" ]; } public static function wxPayConfigByType($type = "app") { if ($type == "app") return self::wxAppConfig(); if ($type == "web") return self::wxWebConfig(); return self::wxAppletConfig(); } /** * h5获取微信用户信息 * @param $code * @return \SResult */ public function wxLoginByWeb($code) { if (!$code) return $this->fail("code不存在!"); $config = WxService::wxWebConfig(); $s_result = $this->curl_get("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$config["appid"]}&secret={$config["app_secret"]}&code={$code}&grant_type=authorization_code"); if (0 === $s_result->code()) return $s_result; $code_of_data = $s_result->data(); // 请求用户信息 $userinfo_result = $this->curl_get("https://api.weixin.qq.com/sns/userinfo?access_token={$code_of_data['access_token']}&openid={$code_of_data['openid']}"); if (0 === $userinfo_result->code()) return $userinfo_result; $userinfo_of_data = $userinfo_result->data(); $merge_array = array_merge($code_of_data, $userinfo_of_data); return $this->ok($merge_array); } /** * Applet获取微信用户信息 * @param $code * @return \SResult */ public function wxLoginByApplet($code) { if (!$code) return $this->fail("code不存在!"); $config = WxService::wxAppletConfig(); $s_result = $this->curl_get("https://api.weixin.qq.com/sns/jscode2session?appid={$config["appid"]}&secret={$config["app_secret"]}&js_code={$code}&grant_type=authorization_code"); if (0 === $s_result->code()) return $s_result; return $this->ok($s_result->data()); } // curl get请求 private function curl_get($url) { $curl = curl_init(); // 设置抓取的url curl_setopt($curl, CURLOPT_URL, $url); // 设置头文件的信息作为数据流输出 curl_setopt($curl, CURLOPT_HEADER, false); // 超时设置,以秒为单位 curl_setopt($curl, CURLOPT_TIMEOUT, 1); // 设置请求头 curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Accept: application/json', ]); // 设置获取的信息以文件流的形式返回,而不是直接输出。 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 执行命令 $data = curl_exec($curl); $error = curl_error($curl); curl_close($curl); // 显示错误信息 if ($error) return $this->fail($error); $response = json_decode($data, true); if (isset($response["errcode"]) && $response["errcode"] !== 0) return $this->fail($response["errmsg"]); return $this->ok($response); } private function sign($message, $key_path) { $mch_private_key = file_get_contents($key_path); openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption'); return base64_encode($raw_sign); } public function instance($type = 'web') { $config = WxService::wxPayConfigByType($type); // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名 $merchantPrivateKeyFilePath = "file://" . $config["mch_private_key_file_path"]; $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名 $platformCertificateFilePath = "file://" . $config["platform_certificate_file_path"]; $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); // 从「微信支付平台证书」中获取「证书序列号」 $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); // 构造一个 APIv3 客户端实例 return Builder::factory([ 'mchid' => $config["mch_id"],// 商户号 'serial' => $config["mch_certificate_serial"],// 「商户API证书」的「证书序列号」 'privateKey' => $merchantPrivateKeyInstance, 'certs' => [ $platformCertificateSerial => $platformPublicKeyInstance, ], ]); } public function webPay($openid, $out_trade_no, $description, $amount) { try { $config = WxService::wxWebConfig(); $body = [ 'appid' => $config["appid"], 'mchid' => $config['mch_id'], 'description' => $description, 'out_trade_no' => $out_trade_no, 'notify_url' => "https://billiards.xunsoftware.com/api.php/api/callback/wxpay", 'amount' => [ 'total' => fixed2Float($amount * 100), 'currency' => 'CNY' ], 'payer' => [ "openid" => $openid ] ]; $resp = $this->instance("web") ->chain('v3/pay/transactions/jsapi') ->post(['json' => $body]); if (200 === $resp->getStatusCode()) { $resp_data = json_decode($resp->getBody()->__toString(), true); $res = [ 'appId' => $config['appid'], 'timeStamp' => time(), 'nonceStr' => Formatter::nonce(), 'package' => 'prepay_id=' . $resp_data['prepay_id'], 'signType' => 'RSA' ]; $sign = $this->sign($res['appId'] . "\n" . $res["timeStamp"] . "\n" . $res["nonceStr"] . "\n" . $res["package"] . "\n", $config["mch_private_key_file_path"]); $res["paySign"] = $sign; return $this->ok($res); } return $this->fail("发起支付失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); return $this->fail($r->getBody()->__toString()); } return $this->fail($e->getMessage()); } } public function webRefund($out_trade_no, $description, $total_amount, $refund_amount) { try { $body = [ 'out_trade_no' => $out_trade_no, "out_refund_no" => $out_trade_no, "reason" => $description, 'notify_url' => "https://billiards.xunsoftware.com/api.php/api/callback/wxrefund", 'amount' => [ 'refund' => fixed2Float($refund_amount * 100), 'total' => fixed2Float($total_amount * 100), 'currency' => 'CNY' ], ]; $resp = $this->instance("web") ->chain('v3/refund/domestic/refunds') ->post(['json' => $body]); if (200 === $resp->getStatusCode()) { return $this->ok(json_decode($resp->getBody()->__toString(), true), "发起退款成功!"); } return $this->fail("发起退款失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $response = json_decode($e->getResponse()->getBody()->__toString(), true); return $this->fail($response["message"]); } return $this->fail($e->getMessage()); } } public function appletPay($openid, $out_trade_no, $description, $amount) { try { $config = WxService::wxAppletConfig(); $resp = $this->instance("applet") ->chain('v3/pay/transactions/jsapi') ->post(['json' => [ 'appid' => $config["appid"], 'mchid' => $config['mch_id'], 'description' => $description, 'out_trade_no' => $out_trade_no, 'notify_url' => "https://billiards.xunsoftware.com/api.php/api/callback/wxpay", 'amount' => [ 'total' => fixed2Float($amount * 100), 'currency' => 'CNY' ], 'payer' => [ "openid" => $openid ] ]]); if (200 === $resp->getStatusCode()) { $resp_data = json_decode($resp->getBody()->__toString(), true); $res = [ 'appId' => $config['appid'], 'timeStamp' => time(), 'nonceStr' => Formatter::nonce(), 'package' => 'prepay_id=' . $resp_data['prepay_id'], 'signType' => 'RSA' ]; $sign = $this->sign($res['appId'] . "\n" . $res["timeStamp"] . "\n" . $res["nonceStr"] . "\n" . $res["package"] . "\n", $config["mch_private_key_file_path"]); $res["paySign"] = $sign; return $this->ok($res); } return $this->fail("发起支付失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); return $this->fail($r->getBody()->__toString()); } return $this->fail($e->getMessage()); } } public function appletRefund($out_trade_no, $description, $total_amount, $refund_amount) { try { $body = [ 'out_trade_no' => $out_trade_no, 'out_refund_no' => $out_trade_no, 'reason' => $description, 'notify_url' => 'https://billiards.xunsoftware.com/api.php/api/callback/wxrefund', 'amount' => [ 'refund' => fixed2Float($refund_amount * 100), 'total' => fixed2Float($total_amount * 100), 'currency' => 'CNY' ], ]; $resp = $this->instance("applet") ->chain('v3/refund/domestic/refunds') ->post(['json' => $body]); if (200 === $resp->getStatusCode()) { return $this->ok(json_decode($resp->getBody()->__toString(), true), "发起退款成功!"); } return $this->fail("发起退款失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $response = json_decode($e->getResponse()->getBody()->__toString(), true); return $this->fail($response["message"]); } return $this->fail($e->getMessage()); } } public function appPay($openid, $out_trade_no, $description, $amount) { try { $config = WxService::wxAppConfig(); $resp = $this->instance('app') ->chain('v3/pay/transactions/app') ->post(['json' => [ 'appid' => $config["appid"], 'mchid' => $config['mch_id'], 'description' => $description, 'out_trade_no' => $out_trade_no, 'notify_url' => "https://billiards.xunsoftware.com/api.php/api/callback/wxpay", 'amount' => [ 'total' => fixed2Float($amount * 100), 'currency' => 'CNY' ], ]]); if (200 === $resp->getStatusCode()) { $resp_data = json_decode($resp->getBody()->__toString(), true); $res = [ "appid" => $config['appid'], "partnerid" => $config['mch_id'], "prepayid" => $resp_data['prepay_id'], "package" => "Sign=WXPay", "noncestr" => Formatter::nonce(), "timestamp" => time(), ]; $res['sign'] = $this->sign($res['appid'] . "\n" . $res["timestamp"] . "\n" . $res["noncestr"] . "\n" . $res["prepayid"] . "\n", $config["mch_private_key_file_path"]); return $this->ok($res); } return $this->fail("发起支付失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); $body = json_decode($r->getBody()->__toString(), true); return $this->fail($body['message']); } return $this->fail($e->getMessage()); } } public function appRefund($out_trade_no, $description, $total_amount, $refund_amount) { try { $config = WxService::wxAppConfig(); $body = [ 'appid' => $config["appid"], 'mchid' => $config['mch_id'], 'description' => $description, 'out_trade_no' => $out_trade_no, "out_refund_no" => $out_trade_no, "reason" => $description, 'notify_url' => "https://billiards.xunsoftware.com/api.php/api/callback/wxrefund", 'amount' => [ 'refund' => $refund_amount * 100, "total" => $total_amount * 100, 'currency' => 'CNY' ] ]; $resp = $this->instance("web") ->chain('v3/refund/domestic/refunds') ->post(['json' => $body]); if (200 === $resp->getStatusCode()) { return $this->ok(true, "发起退款成功!"); } return $this->fail("发起退款失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); return $this->fail($r->getBody()->__toString()); } return $this->fail($e->getMessage()); } } public function deposit($openid, $out_batch_no, $batch_name, $batch_remark, $total_amount) { $config = self::wxWebConfig(); try { $resp = $this->instance("web") ->chain('/v3/transfer/batches') ->post(['json' => [ 'appid' => $config["mch_id"], "out_batch_no" => $out_batch_no, "batch_name" => $batch_name, "batch_remark" => $batch_remark, "total_amount" => fixed2Float($total_amount * 100), 'total_num' => 1, 'transfer_detail_list' => [ [ "out_detail_no" => $out_batch_no, "transfer_amount" => fixed2Float($total_amount * 100), "transfer_remark" => $batch_remark, "openid" => $openid, ] ], ]]); if (200 === $resp->getStatusCode()) { return $this->ok(json_decode($resp->getBody()->__toString(), true), "提现成功!"); } return $this->fail("提现失败!"); } catch (\Exception $e) { // 进行错误处理 if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) { $r = $e->getResponse(); return $this->fail(json_decode($r->getBody()->__toString(), true)); } return $this->fail($e->getMessage()); } } }