一、支付准备
- 首先,
去微信商户平台 - 产品中心 - 开发配置 - 支付配置 设置JSAPI支付授权目录和回调地址 - SDK下载,微信开发文档,https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1 (非必须)
- 支付证书下载(支付用不到,退款的时候使用),微信商户平台 https://pay.weixin.qq.com/index.php/account/api_cert
二、获取用户openid
- 首先,到微信公众平台后台 - 设置 - 网页授权域名(别忘了添加开发者)
// 在头部引入WechatPubService.php文件,见附录一
use app\api\service\WechatPubService;
/**
* 获取code
*/
public function getInfo(){
//前端传code过来
$code = $this->request->param('code');
// dump($code);die;
if (isset($code)) {
$wechat_public = config('wechat_public');
try {
$wechatService = new WechatPubService($wechat_public);
$token = file_get_contents($wechatService->getToken($code));
$token = json_decode($token, true);
// dump($token);
} catch (\Exception $e) {
return $this->json_error('系统错误');
}
if (!isset($token['errcode'])) {
$useInfo = $wechatService->getUserInfo($token['access_token'], $token['openid']);
$useInfo = json_decode($useInfo, true);
// dump($useInfo);
return $this->json_success('获取用户信息成功', $useInfo);
} else {
return $this->json_error('获取用户token失败');
}
} else {
return $this->json_error('code能不能为空');
}
}
三、项目安装Payment
- 安装Payment的前提条件为PHP版本 大于7.0,项目下命令行运行:composer require "riverslei/payment:*",即可安装成功。
Payment为第三方支付SDK,项目地址:https://github.com/helei112g/payment,可以去了解更多的支付接入。
四、接入支付
- 服务端,创建支付订单,获取支付参数并传给客户端
// 头部引入
use Payment\Common\PayException;
use Payment\Client\Charge;
use Payment\Config;
use Payment\Client\Notify;
use Payment\Notify\PayNotifyInterface;
/**
* 创建订单,并调起支付
*/
public function create_order()
{
if (IS_POST) {
//
//---------- 获取订单参数 ----------
//$openid = $this->request->param('openid');
//
//---------- 获取订单参数结束 ----------
//---------- 验证订单参数 ----------
//if (!$openid) {
// return $this->json_error('openid不能为空');
//}
//
//---------- 验证订单参数结束 ----------
// 生成订单号
$orderNo = date('ymdHi') . rand(1000, 9999);
Db::startTrans();
try {
//
//---------- 以下为处理具体业务,并进行数据库操作 ----------
//
//
//---------- 处理具体业务结束 ----------
//---------- 以下为发起微信支付 ----------
// 支付订单信息,即传递给微信的参数
$payData = [
'body' => '校友捐赠', //商品描述 用户支付后看不到此项
'subject' => '校友捐赠', //商品详情
'order_no' => $orderNo,
'timeout_express' => time() + 600,// 表示必须 600s 内付款
'amount' => 0.01,// 微信沙箱模式,需要金额固定为3.01 单位:元
'return_param' => 'JSAPI', // 支付回调值
'client_ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1',// 客户地址
'openid' => $openid,
'product_id' => '123',
];
// 获取微信配置
$channel = 'wx_pub';// wx_app wx_pub wx_qr wx_bar wx_lite wx_wap
$wxConfig = config('wechat.wx_pay'); // 这里的配置文件见附录二
$ret = Charge::run($channel, $wxConfig, $payData);
Db::commit();
} catch (Exception $e) {
Db::rollback();
return $this->json_error('调取支付失败'.$e->getMessage() );
}
if (!empty($ret)) {
return $this->json_success('调取支付成功', $ret);
} else {
return $this->json_error('调取支付失败');
}
}
}
- 客户端,调起支付,若成功支付则进入下一步
- 服务端,到支付回调方法下查看回调结果 (这里需要注意后台拿到回调结果后,需要进行签名验证提高安全性)
/**
* 支付回调方法
*/
public function notify(){
// file_put_contents('upload/1.txt' , '进入了回调');
// 获取回调值
$xmlData = file_get_contents('php://input');
// 解析回调值
//XML格式转换
$xmlObj = simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA);
$xmlObj = json_decode(json_encode($xmlObj),true);
// 当支付通知返回支付成功时
if ($xmlObj['return_code'] == "SUCCESS" && $xmlObj['result_code'] == "SUCCESS") {
//回调签名验证;
foreach( $xmlObj as $k=>$v) {
if($k == 'sign') {
$xmlSign = $xmlObj[$k];
unset($xmlObj[$k]);
};
}
$sign = http_build_query($xmlObj);
// file_put_contents('upload/1.txt' , $sign);
//md5处理
$sign = md5($sign.'&key='.'fb84170d5baeaf8a9a96c286821ad781');
//转大写
$sign = strtoupper($sign);
//验签名。默认支持MD5
//验证加密后的32位值和 微信返回的sign 是否一致!!!
if ( $sign === $xmlSign) {
// 订单号
$trade_no = $xmlObj['out_trade_no'];
//---------- 以下为处理具体业务 ----------
$edit['is_pay'] = 1;
$edit['pay_time'] = date('Y-m-d H:i:s');
$model = new OrderModel();
$order = $model->where(['order_no'=>$trade_no])->update($edit);
//---------- 处理具体业务结束 ----------
if($order){
return $this->json_success('支付成功,感谢您的捐赠');
}else{
return $this->json_error('支付失败');
}
}
}
}
附录:
- 附录一:(这里有多种微信相关方法,需要什么就可以调用什么)
<?php
namespace app\api\service;
use \Exception;
use think\facade\Cache;
class WechatPubService
{
//获取微信授权cod地址
const CODE_URL_BASE = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect';
const CODE_URL_USER = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect';
const TOKEN_URL = 'https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code';
const TOKEN = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s';
const APPQRCODE = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s';
//获取token
const ACCESS_TOKEN = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code';
//获取用户信息
const USER_INFO = 'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN';
//获取jsapi_tocket
const JSAPI_TICKET = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi';
//获取公众号的全局唯一接口调用凭据
const PUBLIC_ACCESS_TOKEN = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s';
//判断用户是否关注了公众号
const ATTEND_PUBLIC = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN';
private static $instance;
//配置
private $_config = [
'AppID' => 'wx**********************99',
'AppSecret' => '21**********************7c',
];
//access_token和ticket的有效时间,最好小于7200秒
const TOKEN_EXPIRE = 7000;
/**
* WechatPubService 初始化方法.
* @param array $conf_option
* @throws Exception
*/
public function __construct($conf_option = [])
{
$this->_config = array_merge($this->_config, $conf_option);
if (!isset($this->_config['AppID']) || empty($this->_config['AppID'])) {
throw new Exception('微信AppID不能为空');
} else if (!isset($this->_config['AppSecret']) || empty($this->_config['AppSecret'])) {
throw new Exception('微信AppSecret不能为空');
}
}
/**
* @param array $conf_option
* @return WechatPubService
* @throws Exception
*/
public static function instance($conf_option = [])
{
if (is_null(self::$instance)) {
self::$instance = new self($conf_option);
}
return self::$instance;
}
/**
* 生成获取用户code的请求链接
* @param $uri string 用户同意授权以后的跳转链接(当前项目的链接,不要有任何登录检查和权限检查)
* @return string 获得授权地址,使用header进行跳转
*/
public function getCode($uri = '')
{
$uri = urlencode($uri);
$url = sprintf(self::CODE_URL_USER, $this->_config['AppID'], $uri);
return $url;
}
/**
* 根据用户的授权code获取用户的access_token
* @param string $code
* @return string 获得请求地址,使用curl或者file_get_contents获取结果
*/
public function getToken($code = '')
{
$url = sprintf(self::ACCESS_TOKEN, $this->_config['AppID'], $this->_config['AppSecret'], $code);
return $url;
}
/**
* 拉取用户信息 scope为 snsapi_userinfo才可以调用
* @param string $access_token
* @param string $openid
* @return false|string
*/
public function getUserInfo($access_token = '', $openid = '')
{
$url = sprintf(self::USER_INFO, $access_token, $openid);
$result = file_get_contents($url);
return $result;
}
/**
* 获取公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token
* @param string $app_id
* @param string $app_secret
* @return mixed
*/
public function getAccessToken($app_id = '', $app_secret = '')
{
$url = sprintf(self::PUBLIC_ACCESS_TOKEN, $app_id, $app_secret);
$result = file_get_contents($url);
return json_decode($result, true);
}
/**
* 获取公众号accessTOken
* @param string $app_id
* @param string $app_secret
* @return mixed
* @throws Exception
*/
public function getPublicAccessToken($app_id = '', $app_secret = '')
{
$cache_key = 'wechat_public_access_token_cache_' . $app_id;
// Cache::rm($cache_key);
if (Cache::has($cache_key)) {
return Cache::get($cache_key);
} else {
$result = $this->getAccessToken($app_id, $app_secret);
if (isset($result['errcode'])) {
throw new Exception($result['errmsg']);
} else {
Cache::set($cache_key, $result['access_token'], self::TOKEN_EXPIRE);
return $result['access_token'];
}
}
}
/**
* 获取JSAPI_TICKET接口
* @throws Exception
*/
public function getJsApiTicket()
{
$cache_key = 'wechat_public_js_api_ticket_prefix_' . $this->_config['AppID'];
// Cache::rm($cache_key);
if (Cache::has($cache_key) && 1 ==2) {
return Cache::get($cache_key);
} else {
//抓取错误,不然缓存会缓存错误的结果
try {
$access_token = $this->getPublicAccessToken($this->_config['AppID'], $this->_config['AppSecret']);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$url = sprintf(self::JSAPI_TICKET, $access_token);
$result = file_get_contents($url);
$result = json_decode($result, true);
Cache::set($cache_key, $result, self::TOKEN_EXPIRE);
return $result;
}
}
//获取随机字符串
public static function getRandCode($num = 16)
{
$array = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
);
$tmp_str = '';
$max = count($array);
for ($i = 1; $i <= $num; $i++) {
$tmp_str .= $array[rand(0, $max - 1)];
}
return $tmp_str;
}
/**
* 判断用户是否关注了公众号
* @param $access_token
* @param $open_id
*/
public function hasAttendPub($open_id)
{
$access_token = $this->getPublicAccessToken($this->_config['AppID'], $this->_config['AppSecret']);
$url = sprintf(self::ATTEND_PUBLIC, $access_token, $open_id);
$result = file_get_contents($url);
return json_decode($result, true);
}
}
- 附录二:
<?php
return [
'appid' => 'wx*****************99', // 公众号或小程序的appid
'secret' => '21*****************7c', // 公众号或小程序的secret
'wx_pay' => [
'use_sandbox' => false,// 是否使用 微信支付仿真测试系统 开启使用沙箱模式。金额固定
'app_id' => 'wx***************99', // 公众账号ID
'mch_id' => '14******02',// 商户id
'md5_key' => 'fb*******************81',// md5 秘钥(商户的秘钥)
// (前面第一步下载的证书文件放到指定位置,在这里设置路径)
'app_cert_pem' => env('CONFIG_PATH').'cert/' . 'apiclient_cert.pem',
'app_key_pem' => env('CONFIG_PATH').'cert/' . 'apiclient_key.pem',
'sign_type' => 'MD5',// MD5 HMAC-SHA256
'limit_pay' => [
//'no_credit',
],// 指定不能使用信用卡支付 不传入,则均可使用
'fee_type' => 'CNY',// 货币类型 当前仅支持该字段
'notify_url' => 'http://***.com/api/v1/Payment/notify', // 回调返回的方法,到此方法下接收支付回调
// 'redirect_url' => 'https://helei112g.github.io/',// 如果是h5支付,可以设置该值,返回到指定页面
'return_raw' => false,// 在处理回调时,是否直接返回原始数据,默认为true
],
];
结束,这是我目前总结的最简单的实现微信JSAPI支付的流程,其中还包含了获取用户信息。
如果仅仅是需要获取用户微信信息可以直接使用第二步,简单便捷。
不足之处,望不吝指出~
网友评论