欢迎使用微信支付!
微信支付API共四份(证书pkcs12格式、证书pem格式、证书密钥pem格式、CA证书),为接口中强制要求时需携带的证书文件。
证书属于敏感信息,请妥善保管不要泄露和被他人复制。
不同开发语言下的证书格式不同,以下为说明指引:
证书pkcs12格式(apiclient_cert.p12)
包含了私钥信息的证书文件,为p12(pfx)格式,由微信支付签发给您用来标识和界定您的身份
部分安全性要求较高的API需要使用该证书来确认您的调用身份
windows上可以直接双击导入系统,导入过程中会提示输入证书密码,证书密码默认为您的商户ID(如:10010000)
证书pem格式(apiclient_cert.pem)
从apiclient_cert.p12中导出证书部分的文件,为pem格式,请妥善保管不要泄漏和被他人复制
部分开发语言和环境,不能直接使用p12文件,而需要使用pem,所以为了方便您使用,已为您直接提供
您也可以使用openssl命令来自己导出:openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem
证书密钥pem格式(apiclient_key.pem)
从apiclient_cert.p12中导出密钥部分的文件,为pem格式
部分开发语言和环境,不能直接使用p12文件,而需要使用pem,所以为了方便您使用,已为您直接提供
您也可以使用openssl命令来自己导出:openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem
CA证书(rootca.pem)
微信支付api服务器上也部署了证明微信支付身份的服务器证书,您在使用api进行调用时也需要验证所调用服务器及域名的真实性
该文件为签署微信支付证书的权威机构的根证书,可以用来验证微信支付服务器证书的真实性
某些环境和工具已经内置了若干权威机构的根证书,无需引用该证书也可以正常进行验证,这里提供给您在未内置所必须根证书的环境中载入使用
商户接入微信支付,调用API必须遵循以下规则:
传输方式 | 为保证交易安全性,采用HTTPS传输 |
---|---|
提交方式 | 采用POST方法提交 |
数据格式 | 提交和返回数据都为XML格式,根节点名为xml |
字符编码 | 统一采用UTF-8字符编码 |
签名算法 | MD5,后续会兼容SHA1、SHA256、HMAC等。 |
签名要求 | 请求和接收数据均需要校验签名,详细方法请参考安全规范-签名算法 |
证书要求 | 调用申请退款、撤销订单接口需要商户证书 |
判断逻辑 | 先判断协议字段返回,再判断业务返回,最后判断交易状态 |
请求参数
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信支付分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
商品描述 | body | 是 | String(128) | 腾讯充值中心-QQ会员充值 | 商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 |
标价金额 | total_fee | 是 | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(16) | 123.12.12.123 | APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付 说明详见参数规定 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 |
以下代码只是对微信支付进行了简单分析,代码不可直接拿来使用,只做参考 ,这个是公众号支付,trade_type=JSAPI时(即公众号支付),此参数openid必传
/*发起微信公众号支付订单*/
private function SubmitWxpaysend($subject,$OpenId,$OrderId,$PayMoney,$NotifyPage)
{
//时间设置
ini_set('date.timezone','Asia/Shanghai');
//引入微信支付类文件
require_once "WxPay.Api.php";
require_once "WxPay.JsApiPay.php";
//统一下单
$input = new \WxPayUnifiedOrder();
$input->SetBody("商品描述");
$input->SetAttach("附加数据");
$input->SetOut_trade_no($OrderId); //商户订单号
$input->SetTotal_fee($PayMoney); //订单总金额,单位为分
$input->SetTime_start(date("YmdHis")); //订单生成时间,格式为yyyyMMddHHmmss
$input->SetTime_expire(date("YmdHis", time() + 600)); //订单失效时间
$input->SetGoods_tag('余额充值'); //订单优惠标记
$input->SetNotify_url("weixin.com/web/admin/api/pay/".$NotifyPage); //设置回调地址
$input->SetTrade_type("JSAPI"); //交易类型 JSAPI 公众号支付
$input->SetOpenid($openId); //此参数为微信用户在商户对应appid下的唯一标识
$input->SetAppid(WxPayConfig::APPID);//公众账号ID
$input->SetMch_id(WxPayConfig::MCHID);//商户号
$input->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
$input->SetNonce_str(self::getNonceStr());//随机字符串
$input->SetSign();//签名
//以上的方法是将值保存到$values这个属性的数组里面
//数组转换为XML
$xml = $input->ToXml();//(这个方法是去拿$values的值,并且转换为XML返回)
//向统一下单接口发起请求
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单
$response = self::postXmlCurl($xml, $url, false,$timeOut=6);
//将xml转为array
$result = WxPayResults::Init($response);
$tools = new \JsApiPay();
//获取jsapi支付的参数
$jsApiParameters =$tools->GetJsApiParameters($order);
return $jsApiParameters;
}
//随机字符串函数
public static function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str ="";
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
//签名生成函数
public function SetSign()
{
//签名步骤一:按字典序排序参数
ksort($this->values);
//格式化参数格式化成url参数
$buff = "";
foreach ($this->values as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff,"&");
$string = $buff;
//签名步骤二:在string后加入KEY
$string = $string . "&key=".WxPayConfig::KEY;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
$this->values['sign'] = $result;
return $sign;
}
//数组转XML
public function ToXml()
{
if(!is_array($this->values)||count($this->values) <= 0){
throw new WxPayException("数组数据异常!");
}
$xml = "<xml>";
foreach ($this->values as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
/**
* 以post方式提交xml到对应的接口url
*
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $second url执行超时时间,默认30s
* @throws WxPayException
*/
private static function postXmlCurl($xml, $url, $useCert = false, $second = 30)
{
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
//如果有配置代理这里就设置代理
if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
&& WxPayConfig::CURL_PROXY_PORT != 0){
curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
}
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($useCert == true){
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
$certpath="cert/apiclient_cert.pem"; //证书路径
$certkeypath="cert/apiclient_key.pem"; //证书路径
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, $certpath);
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, $certkeypath);
}
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
api_functions::writeLog("postXmlCurl:".$data);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new WxPayException("curl出错,错误码:$error");
}
}
/**
* 将xml转为array
* @param string $xml
* @throws WxPayException
*/
public static function Init($xml)
{
$obj = new self();
$obj->FromXml($xml);
//fix bug 2015-06-29
if($obj->values['return_code'] != 'SUCCESS'){
return $obj->GetValues();
}
$obj->CheckSign();
return $obj->GetValues();
}
}
/**
*
* 获取jsapi支付的参数
* @param array $UnifiedOrderResult 统一支付接口返回的数据
* @throws WxPayException
*
* @return json数据,可直接填入js函数作为参数
*/
public function GetJsApiParameters($UnifiedOrderResult)
{
if(!array_key_exists("appid", $UnifiedOrderResult)
|| !array_key_exists("prepay_id", $UnifiedOrderResult)
|| $UnifiedOrderResult['prepay_id'] == "")
{
throw new WxPayException("参数错误");
}
$jsapi = new WxPayJsApiPay();
$jsapi->SetAppid($UnifiedOrderResult["appid"]);
$timeStamp = time();
$jsapi->SetTimeStamp("$timeStamp");
$jsapi->SetNonceStr(WxPayApi::getNonceStr());
$jsapi->SetPackage("prepay_id=" . $UnifiedOrderResult['prepay_id']);
$jsapi->SetSignType("MD5");
$jsapi->SetPaySign($jsapi->MakeSign());
$parameters = json_encode($jsapi->GetValues());
return $parameters;
}
下面对上面代码做过总结,这里是app支付
/*发起APP微信支付订单*/
private function SubmitWxpaysend($body,$subject,$UserId,$OrderId,$PayMoney,$PayType,$NotifyPage)
{
try
{
$appid=$this->wx_appid;
$mch_id=$this->wx_mch_id;
$wxKEY=$this->wx_wxKEY;
header("Content-type: text/html; charset=utf-8");
$order = $OrderId;
$noceStr = md5(rand(100,1000).time());//获取随机字符串
$time = time();
$paramarr = array(
"appid" => $appid,//公众账号ID
"body" => $subject,//商品描述
"mch_id" => $mch_id, //商户号
"nonce_str" => $noceStr,//随机字符串
"notify_url" => "/web/admin/api/web/".$NotifyPage,//回调地址
"out_trade_no"=> $order, //商户订单号
"spbill_create_ip"=>$_SERVER["REMOTE_ADDR"],//APP和网页支付提交用户端ip
"total_fee" => ($PayMoney)*100,//订单总金额,单位为分
"trade_type" => "APP"//APP APP支付
);
//字符串拼接,生成签名
ksort($paramarr);
$sign = "";
foreach($paramarr as $k => $v){
$sign .= $k."=".$v."&";
}
$sign .= "key=".$wxKEY;
$sign = strtoupper(md5($sign));
$paramarr['sign'] = $sign;
//数组转XML
$paramXml = "<xml>";
foreach($paramarr as $k => $v){
$paramXml .= "<" . $k . ">" . $v . "</" . $k . ">";
}
$paramXml .= "</xml>";
$ch = curl_init ();
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
@curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
@curl_setopt($ch, CURLOPT_URL, "https://api.mch.weixin.qq.com/pay/unifiedorder");
@curl_setopt($ch, CURLOPT_HEADER, FALSE);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
@curl_setopt($ch, CURLOPT_POST, 1);
@curl_setopt($ch, CURLOPT_POSTFIELDS, $paramXml);
@$resultXmlStr = curl_exec($ch);
curl_close($ch);
//XML转数组
$msg = array();
$postStr =$resultXmlStr;
$msg = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$time2 = time();
$prepayid = $result['prepay_id'];
$sign = "";
$noceStr = md5(rand(100,1000).time());//获取随机字符串
$paramarr2 = array(
"appid" => $appid,
"noncestr" => $noceStr,
"package" => "Sign=WXPay",
"partnerid" => $mch_id,
"prepayid" => $prepayid,
"timestamp" => $time2
);
//字符串拼接,生成签名
ksort($paramarr2);
$sign = "";
foreach($paramarr2 as $k => $v){
$sign .= $k."=".$v."&";
}
$sign .= "key=".$wxKEY;
$sign = strtoupper(md5($sign));
echo json_encode($paramarr2);
}
catch (Exception $ex)
{
echo $ex->getMessage();
}
}