- 授权根据code获取用户openid
- 此时需要给客户端读取jsApiList权限 以及返回对应签名、时间戳、字符串等(备注:获取时需要前端给一个下单的完整url路径进行验签否则 客户端会报60032 签名错误)
- 根据code调统一下单接口
- 拿到prepay_id进行二次验签
- 验签完成返回给前端拉起支付
- 回调完成 至此流程结束
配置相关:
公众平台:授权目录来获取openid 安全目录、安全域名 配置客户端的h5链接 关联商户平台
商户平台:支付授权目录(顶级目录即可),api密钥,证书配置(退款、提现使用)
遇到的坑:
给客户端生成配置时url问题一直报签名错误,url规则(必须是完整域名且携带参数并且不得携带#后面的内容),url携带参数时easywechat会自动转换成 xx.com?id=1&params1=xxx&params2=xxx(此时需要str_replace除去无用字符串保证提交过来的url和获取签名后的url是一致的否则也会报签名错误)
客户端调起支付时签名错误(此时在开放平台验签工具是验证通过的),解决:需要拿prepay_id预支付订单id二次验签调用 $app->jssdk->sdkConfig($prepay_id) 返回给客户端 支付调起成功。
代码段:
<?php
namespace app\api\library;
use EasyWeChat\Factory;
use think\Cache;
use think\Config;
class Wechat
{
protected $config = [];
protected $siteConfig = [];
protected $app = null;
protected static $instance = null;
/**
* Wechat constructor.
* @param $type
*/
public function __construct($type = 'pay')
{
$siteConfig = Config::get('site');
$this->siteConfig = $siteConfig;
$this->config = array(
'app_id' => $siteConfig['appid'],
'appid' => $siteConfig['appid'],
'mch_id' => $siteConfig['mch_id'],
'key' => $siteConfig['secret'],
'cert_path' => $siteConfig['cert_path'],
'key_path' => $siteConfig['key_path'],
'notify_url' => request()->domain() . '/api' . $siteConfig['notify'],
);
$method = $type == 'pay' ? 'payment' : 'officialAccount';
if ($type == 'officialAccount'){
}
$this->config = array_merge(array('secret' => $siteConfig['app_secret']),$this->config);
$this->app = Factory::$method($this->config);
}
/**
* @param string $type
* @return static|null
*/
public static function instance($type = 'pay')
{
if (is_null(self::$instance)) {
self::$instance = new static($type);
}
return self::$instance;
}
/**
* 支付实例化
* @return Pay
*/
public function pay(){
return new Pay();
}
/**
* 退款实例化
* @return Refund
*/
public function refund(){
return new Refund();
}
/**
* 构建config
* @param $apis
* @param $uri
* @return mixed
*/
public function getBuildConfig($apis,$uri){
$uri = strpos($uri,'amp;') !== false ? str_replace('amp;','',$uri) : $uri;
$this->app->jssdk->setUrl(str_replace('amp;','',$uri));
return $this->app->jssdk->buildConfig($apis,true);
}
/**
* @param $prepayIds
* @return mixed
*/
public function getJsSdkConfig($prepayIds){
return $this->app->jssdk->sdkConfig($prepayIds);
}
/**
* 获取openid
* @param $code
* @return \Overtrue\Socialite\User
*/
public function getOpenId($code){
$secret = $this->config['secret'];
$appid = $this->config['appid'];
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=".$appid."&secret=".$secret."&code=".$code."&grant_type=authorization_code";
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_TIMEOUT,30);
$content = curl_exec($ch);
$status = (int)curl_getinfo($ch,CURLINFO_HTTP_CODE);
if ($status == 404) {
return $status;
}
curl_close($ch);
return json_decode($content,true);
}
}
<?php
namespace app\api\library;
use app\api\model\Order;
use think\Cache;
class Pay extends Wechat
{
/**
* 统一下单
* @param $out_trade_no
* @param $payInfo
* @param $openId
* @param $amount
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function handlePay($out_trade_no,$payInfo,$openId,$amount){
return $this->app->order->unify(array(
'body' => $payInfo,
'out_trade_no' => $out_trade_no,
'total_fee' => (int)($amount * 100),
'notify_url' => $this->config['notify_url'], // 支付结果通知网址,如果不设置则会使用配置里的默认地址
'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
'openid' => $openId,
));
}
/**
* 支付回调
* @return mixed
*/
public function handleNotify(){
return $this->app->handlePaidNotify(function ($message,$error){
$order = Order::get(['sn'=>$message['out_trade_no']]);
if (!$order || $order['status'] == Order::SUCCESS) return true;
if ($message['return_code'] === 'SUCCESS') {
if ($message['result_code'] === 'SUCCESS') {
$order['status'] = Order::SUCCESS;
} elseif ($message['result_code'] === 'FAIL') {
$order['status'] = 'paid_fail';
}
} else {
return $error('通信失败,请稍后再通知我');
}
if ($order->save()){
return true;
}
return false;
});
}
}
<?php
namespace app\api\library;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
class Refund extends Wechat
{
/**
* 根据商户订单号退款
* @param $no 商户订单号
* @param $refundNo 退款单号
* @param $orderAmount 订单金额
* @param $refundAmount 退款金额
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
*/
public function handleByRefundNo($no,$refundNo,$orderAmount,$refundAmount){
try {
$handel = $this->app->refund->byOutTradeNumber($no,$refundNo,($orderAmount * 100),($refundAmount * 100));
}catch (InvalidConfigException $e){
return ['code'=>$e->getCode(),'msg'=>$e->getMessage()];
}
return $handel;
}
}
<?php
//统一下单
Wechat::instance()->pay()->handlePay($out_trade_no,'缴费',$this->auth->getOpenId(),$order['amount']);
//二次验签
Wechat::instance('jsapi')->getJsSdkConfig($submitPay['prepay_id']);
//支付回调
Wechat::instance()->pay()->handleNotify()->send()
//退款
RefundService::instance()->refund()->handleByRefundNo($out_trade_no,$refund_no,$order_amount,$refund_amount);
//获取微信接口权限
Wechat::instance('jsapi')->getBuildConfig(array('scanQRCode','chooseWXPay'),$this->request->post('url'))