PHP JSAPI调支付API实现微信支付功能详解

一、首先我们来填个坑

支付验签失败

这个问题折磨了我两天,官方文档比较含糊不清。各种百度下来的方法试过之后也不尽人意,最后发现问题是没有二次签名

二次签名需要参数(代码会展示在哪里二次签名):

appId:      商户申请的公众号对应的appid(I大写)

nonceStr: 随机字符串(注意是JSAPI下单接口中返回的 nonce_str、不是重新生成)

package:  统一下单接口返回的prepay_id参数值 ,(注意格式prepay_id=wx.....)

signType: 签名类型、(官方文档)仅支持RSA。

(我的签名类型是 HMAC-SHA256 也是可以的,必须和下单使用的签名类型保持一致)

timeStamp:时间戳(这里要把 time() 转成字符串类型)

注明:使用这五个参数生成的 paySign 签名才是需要返给前端的(

官方文档实例要计算签名也给我整的蒙圈,最后发现直接将五个必须参数生成的签名返给前端就可以直接调取API了

二、代码示例

1.请求参数配置

$oInput = [
	'body' => '测试商品', // 商品说明 
	'attach' => '测试场景', // 自定义参数:可以用来做回调后场景区分 
	'out_trade_no' => '测试单号' . time(), // 自定义订单号 
	'total_fee' => 1 * 100, // 付款金额:记得*100 微信官方是以分为单位 
	'goods_tag' => '', // 优惠券相关参数 
	'notify_url' => 'http://...', // 回调通知地址
	'trade_type' => 'JSAPI', // 支付方式 
	'openid' => $openid, // 付款用户openid 
 // 'profit_sharing' => 'Y', // 是否分账的标识 
	];
	$res = $this->unifiedOrder($oInput); // 这里我调用的统一下单
	return $res; // 返给前端带APPID等参数给前端去调用支付

2.统一下单API

public function unifiedOrder($inputObj, $timeOut = 6)
	{
	$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	// 首次签名参数
	$oValues = [
	'body' 	=> $inputObj['body'],	// 设置商品或支付单简要描述
	'attach' 	=> $inputObj['attach'],	// 设置附加数据,用于商户携带订单的自定义数据
	'out_trade_no' 	=> $inputObj['out_trade_no'], 	// 设置商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no
	'total_fee' 	=> $inputObj['total_fee'], 	// 设置订单总金额,只能为整数,单位:分
	'time_start' 	=> date("YmdHis"), 	// 设置订单生成时间
	'time_expire' 	=> date("YmdHis", time() + 600), 	// 设置订单失效时间
	'goods_tag' 	=> $inputObj['goods_tag'], 	// 设置商品标记,代金券或立减优惠功能的参数
	'notify_url' 	=> $inputObj['notify_url'], 	// 获取接收微信支付异步通知回调地址的值
	'trade_type' 	=> $inputObj['trade_type'], 	// JSAPI,NATIVE,APP
	'openid' 	=> $inputObj['openid'], 	// 用户在商户appid下的唯一标识
	//'profit_sharing' 	=> $inputObj['profit_sharing'],	// 是否需要分账
	'appid' 	=> 'appid', 	 // app_id:替换真实的
	'mch_id' 	=> 'mchid', 	 // 商户号:替换真实的
	'spbill_create_ip' 	=> $_SERVER['REMOTE_ADDR'], 	// 终端ip
	'nonce_str' 	=> '自定义生成', 	 // 随机32位字符串
	'sign_type' 	=> 'HMAC-SHA256', 	// 签名类型,自行替换
	];
	// 首次签名
	ksort($oValues);
	$oValues['sign'] = $this->MakeSign($oValues); 	// 调用签名
	$xml = $this->ToXml($oValues); // 数字转xml类型
	$response = self::postXmlCurl($xml, $url, false, $timeOut); // 请求
	$result = $this->FromXml($response); // 请求结果从xml转成数组类型
 // 二次签名参数
	$oResult = [
	'appId' => $result['appid'], // 首次请求中的appid
	'nonceStr' => $result['nonce_str'], // 首次请求中的nonce_str
	'package' => 'prepay_id=' . $result['prepay_id'],// 首次请求中的prepay_id
	'signType' => 'HMAC-SHA256', // 跟首次签名中的签名类型参数保持一致
	'timeStamp' => (string)(time()),// 时间戳转字符串类型
	];
 // 二次签名
	$oResult['paySign'] = $this->MakeSign($oResult); // 调用签名
	$result = json_encode($oResult); // encode数组
	return $result; // 直接返回
	}

3.MakeSign 签名

/**
	 * 生成签名
	 * @param bool $needSignType 是否需要补signtype
	 * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
	 */
	public function MakeSign($values, $needSignType = true)
	{
	if ($needSignType) {
	$sSignType = 'HMAC-SHA256'; // 可以在文档开头用枚举定义: 所有签名类型必须一致
	}
	$sKey = 'key'; // 获取支付参数key
	// 签名步骤一:按字典序排序参数
	ksort($values);
	$string = $this->ToUrlParams($values);
	// 签名步骤二:在string后加入KEY
	$string = $string . "&key=" . $sKey;
	// 签名步骤三:MD5加密或者HMAC-SHA256
	if ($sSignType == "MD5") {
	$string = md5($string);
	} else if ($sSignType == "HMAC-SHA256") {
	$string = hash_hmac("sha256", $string, $sKey);
	} else {
	return "签名类型不支持!";
	}
	// 签名步骤四:所有字符转为大写
	$result = strtoupper($string);
	return $result;
	}

4.ToXml 数组参数转xml

public function ToXml($values)
	{
	if (!is_array($values) || count($values) <= 0) {
	return "数组数据异常!";
	}
	$xml = "<xml>";
	foreach ($values as $key => $val) {
	if (is_numeric($val)) {
	$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
	} else {
	$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
	}
	}
	$xml .= "</xml>";
	return $xml;
	}

5.postXmlCurl 发送请求

/**
	 * 以post方式提交xml到对应的接口url
	 * 
	 * @param WxPayConfigInterface $config 配置对象
	 * @param string 	$xml 	需要post的xml数据
	 * @param string 	$url 	url
	 * @param bool 	$useCert 	是否需要证书,默认不需要
	 * @param int 	$second 	url执行超时时间,默认30s
	 */
	private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
	{
	$ch 	= curl_init();
	$curlVersion 	= curl_version();
	$ua 	= "WXPaySDK/" . self::VERSION . " (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion['version'] . " " . $aWxpayParam['mchid'];
	//设置超时
	curl_setopt($ch, CURLOPT_TIMEOUT, $second);
	$proxyHost = "0.0.0.0";
	$proxyPort = 0;
	// 如果有配置代理这里就设置代理
	if ($proxyHost != "0.0.0.0" && $proxyPort != 0) {
	curl_setopt($ch, CURLOPT_PROXY, $proxyHost);
	curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort);
	}
	curl_setopt($ch, CURLOPT_URL, $url);
	// curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
	// curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
	curl_setopt($ch, CURLOPT_USERAGENT, $ua);
	// 设置header
	curl_setopt($ch, CURLOPT_HEADER, FALSE);
	// 要求结果为字符串且输出到屏幕上
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	if ($useCert == true) {
	// 设置证书
	// 使用证书:cert 与 key 分别属于两个.pem文件
	// 证书文件请放入服务器的非web目录下
	$sslCertPath 	= 'sslCertPath'; // 证书路径
	$sslKeyPath 	= 'sslKeyPath'; // 证书路径
	curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
	curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
	curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
	curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
	}
	// post提交方式
	curl_setopt($ch, CURLOPT_POST, TRUE);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
	// 运行curl
	$data = curl_exec($ch);
	// 返回结果
	if ($data) {
	curl_close($ch);
	return $data;
	} else {
	$error = curl_errno($ch);
	curl_close($ch);
	throw new WxPayException("curl出错,错误码:$error");
	}
	}

6.FromXml 结果xml参数转数组

/**
	 * 将xml转为array
	 * @param string $xml
	 * @throws WxPayException
	 */
	public function FromXml($xml)
	{
	if (!$xml) {
	return "xml数据异常!";
	}
	//将XML转为array
	//禁止引用外部xml实体
	libxml_disable_entity_loader(true);
	$res = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
	return $res;
	}

总结

注意统一下单中五个调用方法别忘了:

getNonceStr:我没贴出来,这个要自己写(0.0)

MakeSign: 这里面的key要记得替换成自己真实的参数

ToXml

postXmlCurl : 注意这里面的证书要改成自己真实的哈

FromXml

作者:一本曾经原文地址:https://blog.csdn.net/arlene12345/article/details/127831004

%s 个评论

要回复文章请先登录注册