智慧零售和先享后付使用微信支付V3版接口规则参见:https://wechatpay-api.gitbook.io/wechatpay-api-v3/
免押租借和免押速住使用微信支付V2版接口规则参见:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_3
https://pay.weixin.qq.com/wiki/doc/apiv3/payscore.php?chapter=18_1&index=2
微信支付提供V2和V3这两种接口方式
微信支付分支付免押订单租赁订单thinkphp5
本地项目路径:application\api\controller\Orderzfbwx.php
详情见QQ网盘:mg/tianjieb.../
/**
* https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_1.shtml
* 微信支付分支付
* @param string $orderzfb_id
* @return bool
*/
public function h5_wx($orderzfb_id='')
{
require_once "../extend/wxpay_lib/WxPay.Api.php";
require_once "../extend/wxpay_lib/WxPay.JsApiPay.php";
require_once "../extend/wxpay_lib/WxPay.Config.php";
// $order = \WxPayApi::getcertificates();
$input = [
'out_order_no' =>'1594286271',//商户订单号
'service_id' => '500001',
'service_introduction' =>'设备租赁',
'time_range' =>['start_time'=>date("YmdHis"), 'end_time'=>date("YmdHis",strtotime("+30 day")), ],
'risk_fund' =>["name"=> "DEPOSIT", "amount"=> 100,"description"=>"设备租赁的费用"],
'notify_url' =>"https://app.tianjiebao.com/api/wx/notify",
'openid' => 'oBWgc8x1wEBZPsiYdQXuMJ4',
'need_user_confirm'=>false,
];
$config = new \WxPayConfig();
$order = \WxPayApi::createrentbill($config, $input);
echo($order);
exit;
}
IsOut_trade_noSet()) {
throw new WxPayException("缺少统一支付接口必填参数out_trade_no!");
}else if(!$inputObj->IsBodySet()){
throw new WxPayException("缺少统一支付接口必填参数body!");
}else if(!$inputObj->IsTotal_feeSet()) {
throw new WxPayException("缺少统一支付接口必填参数total_fee!");
}else if(!$inputObj->IsTrade_typeSet()) {
throw new WxPayException("缺少统一支付接口必填参数trade_type!");
}
//关联参数
if($inputObj->GetTrade_type() == "JSAPI" && !$inputObj->IsOpenidSet()){
throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
}
if($inputObj->GetTrade_type() == "NATIVE" && !$inputObj->IsProduct_idSet()){
throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
}
//异步通知url未设置,则使用配置文件中的url
if(!$inputObj->IsNotify_urlSet() && $config->GetNotifyUrl() != ""){
$inputObj->SetNotify_url($config->GetNotifyUrl());//异步通知url
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
//签名
$inputObj->SetSign($config);
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 查询订单,WxPayOrderQuery中out_trade_no、transaction_id至少填一个
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayOrderQuery $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function orderQuery($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
public static function set_error($error)
{
self::$error = $error;
}
public static function get_error()
{
return self::$error;
}
/**
* 新请求方式。 直接调用当前面的签名 V2
* https://api.mch.weixin.qq.com/pay/orderquery
* @param $config
* @param $values 请求前设置的一些参数,如:$values['out_order_no'] //商户订单号
* @param int $timeOut
* @return array
* @throws WxPayException
*/
public static function orderQueryNew($config, $values, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if(!$values['out_order_no']) {
self::set_error('"订单查询接口中,out_order_no必填!"');
return false;
}
$values['appid'] = $config->GetAppId();
$values['sign'] = self::MakeSign($config, $values);//生成签名
self::$values = $values;
$xml = self::ToXml($values);
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);//将xml转为array
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
* 创建支付分订单API
* https://wechatpay-api.gitbook.io/wechatpay-api-v3/
* https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter3_1.shtml
* @param $config
* @param $values
* @param int $timeOut
* @return array|bool
*/
public static function createrentbill($config, $values, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";
//检测必填参数
if(!$values['out_order_no']) {
self::set_error('"订单查询接口中,out_order_no必填!"');
return false;
}
$values['appid'] = $config->GetAppId();
self::$values = $values;
$serial_no = config("weixin.WXPT_SERIAL_NO");//平台证书序列号
//生成V3请求 header认证信息
$header = self::createAuthorization( $url ,$values, 'POST' );
$post_data = json_encode($values , JSON_UNESCAPED_UNICODE);
//增加平台证书序列号 , 平台证书序列号方法 getcertificates()
$header[] = 'Wechatpay-Serial:' . $serial_no;
$response = self::httpRequest($config, $post_data, $url, false, "POST", $header);
var_dump($response);exit;
return json_decode($response , true);
$values['sign'] = self::MakeSign($config, $values);//生成签名
self::$values = $values;
$xml = self::ToXml($values);
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);//pos请求 postXmlCurl
$result = WxPayResults::Init($config, $response);//将xml转为array
var_dump($result);exit;
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
public static function pay_v3($config, $values, $timeOut = 6)
{
$serial_no = config("weixin.WXPT_SERIAL_NO");//平台证书序列号
$url = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if(!$values['out_order_no']) {
self::set_error('"订单查询接口中,out_order_no必填!"');
return false;
}
$values['appid'] = $config->GetAppId();
$values['sign'] = self::MakeSign($config, $values);//生成签名
self::$values = $values;
//生成V3请求 header认证信息
$header = self::createAuthorization( $url ,$values, 'POST' );
$post_data = json_encode($values , JSON_UNESCAPED_UNICODE);
//增加平台证书序列号 , 平台证书序列号方法 getcertificates()
$header[] = 'Wechatpay-Serial:' . $serial_no;
$response = self::httpRequest($config, $post_data, $url, false, "POST", $header);
var_dump($response);exit;
return json_decode($response , true);
}
//生成v3 Authorization
public static function createAuthorization($url, $body='', $method = 'GET'){
if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true)))
{
exit("当前PHP环境不支持SHA256withRSA");
}
$url_parts = parse_url($url);
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
//私钥地址
$mch_private_key = config('weixin.PRIVATE_KEY');
//商户号
$merchant_id = config('weixin.MCHID');
//当前时间戳
$timestamp = time();
//随机字符串
$nonce = self::getNonceStr();
//POST请求时 需要 转JSON字符串
if(is_array($body)) $body = json_encode($body,JSON_UNESCAPED_UNICODE);
$message = "{$method}\n".
$canonical_url."\n".
$timestamp."\n".
$nonce."\n".
$body."\n";
$message = ltrim($message);
//生成签名
openssl_sign($message, $raw_sign, openssl_get_privatekey(file_get_contents($mch_private_key)), 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
//Authorization 类型
$schema = 'WECHATPAY2-SHA256-RSA2048';
$serial_no = config('weixin.SERIAL_NO');
//生成token
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign);
$header = [
'Content-Type:application/json',
'Accept:application/json',
'User-Agent:*/*',
'Authorization: '. $schema . ' ' . $token
];
echo $message;
print_r($header);
return $header;
}
/**
* 获取平台证书, 与商户证书不是一个内容。暂时理解为用于解密和验签使用吧
{"data":[{"effective_time":"2020-07-03T16:32:55+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","associated_data":"certificate","ciphertext":"/h9rWmNFUMgCKMR5WgblfMM6tXjO5qQ657Jaj5Z9eVB4uKqqSe1fMJmmFruKfVgIMAY+oyWnEUehTO22Eg/r048ba4UeceY0DPSg+/GCLeXaOVw690q7HI3V3GA/RrS5hG3XpyXaBnA8KNsBwWiNR8ddkK5pFGeeg+DzuGgn+xdo2tvmTJabwjUGZQl3U1sTbRLJ7m6X3yc59WeBCRFPBm3ACY7nDzhRs0Bge8ODZSZExheWrCbDKEkwdHY6gFkB+/utf2gH0l4HvBorRNaF8+WO/vK4/SrPHphc14leYDXzXY4P1V6kAdebgJQ+45dHdg1S6XV9Jguf4aC2CU7IxZzkcUIJLtSmwMsjlYLr8asHh598DrQNOfPMkQzf9arcybvLgXPG2oOo1SWBaB9Z37N7LDEMopxcRkxi+qamb/BtIHZobdjdiqf3YRuTwWmJ9n7p7pvMlNxo1C0zyH9HRAcIKP++EOiID7J3mSdpHGjndze5UZllioHPsscJ0WpWk0v6LwNN6ByMMh49csDzuFhYBBbRytnIVrA/yoTR3R0SKuLgj53Cm1ZvNoauaBDzJnfUG6pBTll5gGMo3WTZvoic7Uwplo9XhDcw8JLZFj7/5c6qoQZHfdeEwBfMDGCUONOm7cPIjToZwCdA8NYEdEfOqK9kBkhfUmcS+w4muIB2idHxvDmrB6IkDUpGT/fOdO+Wd5yseXjm0peJxU3mwRwcb87Zl2+hGwGSIHGNjDcKRmzF6xAtq1Flhh5Ve+jukNsTVTWQfXJY/CxrE/T0Kce7I3NNJMzNGNzECLKUe00OB+Oa9h7xqoJizsNLTNHjkRV+ZuLmcN5zh4NNyv3q4v8MkHj8APoFLsDfD6r6SR4BQ4oWIO6ZBcoc+3pCKboqrAKcLdQuBk8DHjy8oX30pqEAsnDJapEtyE7BW/kqc7xXDjmiNTeRBw6zQ4jphsbNpXeQeJJgYb9XefdPwa7ZkuKL/On8CnCHQFw+Q1qxCn0YwdzMUFRKg0XxWY+tKGArPK5e9qqveddvOKEzSemaSeUFadwE+AKh9YxEnbAo/QKyJtY7+ZUhkfIJLrQmpEiBonNpIreexYCLhe/X7id31zwxP/9wYxV92FVUsz5PkIZCH0NDyU+kUUTfVMZXG25XJFcagM+EVg2FhKMeSLBqkl/5kqkJ8jJdAYOcvIdSOmf1Gz4ZtHASdhZ6R39xNx9inKWMC8TgdD4SmmzBptW0Kf+czjRGPlcy3wyofUTnG9cOHM+SMJqqeHQ9XysQnIcCytc9sOLv8fj0LRqdiSwOWhnrUa6RBXjsjGwfFanTsXbpRkhGRmbEsL7H3Fp0+Gl7FloVy/2IQVaUowvQcaHcIhDPUl4W49ts3QJrSUgfRRu7WzyTjT4Xe+pfsa02vMG41l38OsUANmn34XDKsmsJP3okzHe+nSntxqbGWioxB40qH19jvQB3Fmb/Peuzj3UrAtjKTDmzxx9uCBEdVEet2mWx1j7icYn1jJSETKmSFIfb8VqlFWZLveKs7YFCFZYIP7Zg2F2+fPtd8Zu81Xf1V/6WFjf2ZkKdr5BRk2JE6l5fhP3ILlPtlvI9IkhKvyHO98FKqnGOIcR9oe7+R2AIqQMcw2pMmqZa7K8V3jmbiFH7cLSMCtVfNG8gz7SYatlaglHZ9mc69xJDoMrw47s7lUalS42ZwuADR7Z1Oe5Z05edtIk47SVH70ST1SlOhcgklqaWGsU8tnE0kXKuTOYrrv06Dnmd5a2SjPs2LTgyqcAevsCzk2xm3c26DIOIWHSK5SZH0gKKEMur9L+QxW3fCUI2elIZjjBxwM8s3vGIZw1OdvY80xF+UvbTK9PV5wux0k53QEWXgCKIBT7veKR+Avh8FXYooQ==","nonce":"4b2368dfde75"},"expire_time":"2025-07-02T16:32:55+08:00","serial_no":"35DE58A6C50152902117A138F527EFD430934D7A"}]}
*/
public static function getcertificates(){
$url="https://api.mch.weixin.qq.com/v3/certificates";
//生成V3请求 header认证信息
$header = self::createAuthorization( $url );
$header[] = 'User-Agent : https://zh.wikipedia.org/wiki/User_agent';
$data = self::httpRequest([], [], $url, false, "GET", $header);
echo($data);exit;
return json_decode($data , true);
}
/**
* 支付及服务 - 服务人员注册
* 1. 获取平台证书序列号 serial_no与 商户支付证书不是一个
* 2. 解密平台证书,拿到平台证书信息
* 3. 加密请求参数时 需要用户 平台证书进行加密
*/
public static function regguide( $config, $post ,$serial_no='')
{
$serial_no = config("weixin.WXPT_SERIAL_NO");//平台证书序列号
$url = "https://api.mch.weixin.qq.com/v3/smartguide/guides";
//生成V3请求 header认证信息
$header = self::createAuthorization( $url ,$post, 'POST' );
$post_data = json_encode($post , JSON_UNESCAPED_UNICODE);
//增加平台证书序列号 , 平台证书序列号方法 getcertificates()
$header[] = 'Wechatpay-Serial:' . $serial_no;
$response = self::httpRequest($config, $post_data, $url, false);
return json_decode($response , true);
}
/**
* V3加密
*/
public static function getEncrypt($str){
//$str是待加密字符串
$public_key_path = '证书地址'; //看情况使用证书, 个别接口证书 使用的是 平台证书而不是 api证书
$public_key = file_get_contents($public_key_path);
$encrypted = '';
if (openssl_public_encrypt($str,$encrypted,$public_key,OPENSSL_PKCS1_OAEP_PADDING)) {
//base64编码
$sign = base64_encode($encrypted);
} else {
throw new Exception('encrypt failed');
}
return $sign;
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext V3签名解密
* @param stingr $aesKey V3签名
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
public static function decryptToString($aesKey ,$associatedData, $nonceStr, $ciphertext)
{
if (strlen($aesKey) != 32 ) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$ciphertext = \base64_decode($ciphertext , true);
if (strlen($ciphertext) <= 16) {
return false;
}
// ext-sodium (default installed on >= PHP 7.2)
if(function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available() ){
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if(function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()){
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
}
// PHP >= 7.1
if(PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods()) ){
$ctext = substr($ciphertext, 0, -16);
$authTag = substr($ciphertext, -16);
return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
public static function getNonce()
{
static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < 32; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
function GetValues()
{
return self::$values;
}
/**
* 生成签名
* @param WxPayConfigInterface $config 配置对象
* @param bool $needSignType 是否需要补signtype
* @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
*/
public static function MakeSign($config, $values)
{
//签名步骤一:按字典序排序参数
ksort($values);
$string = self::ToUrlParams($values);
//签名步骤二:在string后加入KEY
$string = $string . "&key=".$config->GetKey();
//签名步骤三:MD5加密或者HMAC-SHA256
if($config->GetSignType() == "MD5"){
$string = md5($string);
} else if($config->GetSignType() == "HMAC-SHA256") {
$string = hash_hmac("sha256",$string ,$config->GetKey());
} else {
self::set_error("签名类型不支持!");
return false;
}
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
public static function ToUrlParams($values)
{
$buff = "";
foreach ($values as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/**
* 输出xml字符
* @throws WxPayException
**/
public static function ToXml($values)
{
if(!is_array($values) || count($values) <= 0)
{
throw new WxPayException("数组数据异常!");
}
$xml = "";
foreach ($values as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."".$key.">";
}else{
$xml.="<".$key.">".$key.">";
}
}
$xml.=" ";
return $xml;
}
/**
*
* 关闭订单,WxPayCloseOrder中out_trade_no必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayCloseOrder $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function closeOrder($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/closeorder";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet()) {
throw new WxPayException("订单查询接口中,out_trade_no必填!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 申请退款,WxPayRefund中out_trade_no、transaction_id至少填一个且
* out_refund_no、total_fee、refund_fee、op_user_id为必填参数
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayRefund $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function refund($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
throw new WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
}else if(!$inputObj->IsOut_refund_noSet()){
throw new WxPayException("退款申请接口中,缺少必填参数out_refund_no!");
}else if(!$inputObj->IsTotal_feeSet()){
throw new WxPayException("退款申请接口中,缺少必填参数total_fee!");
}else if(!$inputObj->IsRefund_feeSet()){
throw new WxPayException("退款申请接口中,缺少必填参数refund_fee!");
}else if(!$inputObj->IsOp_user_idSet()){
throw new WxPayException("退款申请接口中,缺少必填参数op_user_id!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 查询退款
* 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,
* 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
* WxPayRefundQuery中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayRefundQuery $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function refundQuery($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/refundquery";
//检测必填参数
if(!$inputObj->IsOut_refund_noSet() &&
!$inputObj->IsOut_trade_noSet() &&
!$inputObj->IsTransaction_idSet() &&
!$inputObj->IsRefund_idSet()) {
throw new WxPayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
* 下载对账单,WxPayDownloadBill中bill_date为必填参数
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayDownloadBill $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function downloadBill($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/downloadbill";
//检测必填参数
if(!$inputObj->IsBill_dateSet()) {
throw new WxPayException("对账单接口中,缺少必填参数bill_date!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
if(substr($response, 0 , 5) == ""){
return "";
}
return $response;
}
/**
* 提交被扫支付API
* 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,
* 由商户收银台或者商户后台调用该接口发起支付。
* WxPayWxPayMicroPay中body、out_trade_no、total_fee、auth_code参数必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayWxPayMicroPay $inputObj
* @param int $timeOut
*/
public static function micropay($config, $inputObj, $timeOut = 10)
{
$url = "https://api.mch.weixin.qq.com/pay/micropay";
//检测必填参数
if(!$inputObj->IsBodySet()) {
throw new WxPayException("提交被扫支付API接口中,缺少必填参数body!");
} else if(!$inputObj->IsOut_trade_noSet()) {
throw new WxPayException("提交被扫支付API接口中,缺少必填参数out_trade_no!");
} else if(!$inputObj->IsTotal_feeSet()) {
throw new WxPayException("提交被扫支付API接口中,缺少必填参数total_fee!");
} else if(!$inputObj->IsAuth_codeSet()) {
throw new WxPayException("提交被扫支付API接口中,缺少必填参数auth_code!");
}
$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 撤销订单API接口,WxPayReverse中参数out_trade_no和transaction_id必须填写一个
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayReverse $inputObj
* @param int $timeOut
* @throws WxPayException
*/
public static function reverse($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
throw new WxPayException("撤销订单API接口中,参数out_trade_no和transaction_id必须填写一个!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 测速上报,该方法内部封装在report中,使用时请注意异常流程
* WxPayReport中interface_url、return_code、result_code、user_ip、execute_time_必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayReport $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function report($config, $inputObj, $timeOut = 1)
{
$url = "https://api.mch.weixin.qq.com/payitil/report";
//检测必填参数
if(!$inputObj->IsInterface_urlSet()) {
throw new WxPayException("接口URL,缺少必填参数interface_url!");
} if(!$inputObj->IsReturn_codeSet()) {
throw new WxPayException("返回状态码,缺少必填参数return_code!");
} if(!$inputObj->IsResult_codeSet()) {
throw new WxPayException("业务结果,缺少必填参数result_code!");
} if(!$inputObj->IsUser_ipSet()) {
throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
} if(!$inputObj->IsExecute_time_Set()) {
throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetUser_ip($_SERVER['REMOTE_ADDR']);//终端ip
$inputObj->SetTime(date("YmdHis"));//商户上报时间
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
return $response;
}
/**
*
* 生成二维码规则,模式一生成支付二维码
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayBizPayUrl $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function bizpayurl($config, $inputObj, $timeOut = 6)
{
if(!$inputObj->IsProduct_idSet()){
throw new WxPayException("生成二维码,缺少必填参数product_id!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetTime_stamp(time());//时间戳
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
return $inputObj->GetValues();
}
/**
*
* 转换短链接
* 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX),
* 减小二维码数据量,提升扫描速度和精确度。
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayShortUrl $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function shorturl($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/tools/shorturl";
//检测必填参数
if(!$inputObj->IsLong_urlSet()) {
throw new WxPayException("需要转换的URL,签名用原串,传输需URL encode!");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 支付结果通用通知
* @param function $callback
* 直接回调函数使用方法: notify(you_function);
* 回调类成员函数方法:notify(array($this, you_function));
* $callback 原型为:function function_name($data){}
*/
public static function notify($config, $callback, &$msg)
{
if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
# 如果没有数据,直接返回失败
return false;
}
//如果返回成功则验证签名
try {
//获取通知的数据
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
$result = WxPayNotifyResults::Init($config, $xml);
} catch (WxPayException $e){
$msg = $e->errorMessage();
return false;
}
return call_user_func($callback, $result);
}
/**
*
* 产生随机字符串,不长于32位
* @param int $length
* @return 产生的随机字符串
*/
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;
}
/**
* 直接输出xml
* @param string $xml
*/
public static function replyNotify($xml)
{
echo $xml;
}
/**
*
* 上报数据, 上报的时候将屏蔽所有异常流程
* @param WxPayConfigInterface $config 配置对象
* @param string $usrl
* @param int $startTimeStamp
* @param array $data
*/
private static function reportCostTime($config, $url, $startTimeStamp, $data)
{
//如果不需要上报数据
$reportLevenl = $config->GetReportLevenl();
if($reportLevenl == 0){
return;
}
//如果仅失败上报
if($reportLevenl == 1 &&
array_key_exists("return_code", $data) &&
$data["return_code"] == "SUCCESS" &&
array_key_exists("result_code", $data) &&
$data["result_code"] == "SUCCESS")
{
return;
}
//上报逻辑
$endTimeStamp = self::getMillisecond();
$objInput = new WxPayReport();
$objInput->SetInterface_url($url);
$objInput->SetExecute_time_($endTimeStamp - $startTimeStamp);
//返回状态码
if(array_key_exists("return_code", $data)){
$objInput->SetReturn_code($data["return_code"]);
}
//返回信息
if(array_key_exists("return_msg", $data)){
$objInput->SetReturn_msg($data["return_msg"]);
}
//业务结果
if(array_key_exists("result_code", $data)){
$objInput->SetResult_code($data["result_code"]);
}
//错误代码
if(array_key_exists("err_code", $data)){
$objInput->SetErr_code($data["err_code"]);
}
//错误代码描述
if(array_key_exists("err_code_des", $data)){
$objInput->SetErr_code_des($data["err_code_des"]);
}
//商户订单号
if(array_key_exists("out_trade_no", $data)){
$objInput->SetOut_trade_no($data["out_trade_no"]);
}
//设备号
if(array_key_exists("device_info", $data)){
$objInput->SetDevice_info($data["device_info"]);
}
try{
self::report($config, $objInput);
} catch (WxPayException $e){
//不做任何处理
}
}
/**
* CURL请求
* @param $url 请求url地址
* @param $method 请求方法 get post
* @param null $postfields post数据数组
* @param array $headers 请求header信息
* @param bool|false $debug 调试开启 默认false
* @return mixed
*/
public static function httpRequest($config, $postfields, $url, $useCert = false, $method = "POST", $headers = array(), $debug = false)
{
$method = strtoupper($method);
$ci = curl_init();
/* Curl settings */
curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0");
curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */
curl_setopt($ci, CURLOPT_TIMEOUT, 7); /* 设置cURL允许执行的最长秒数 */
curl_setopt($ci, CURLOPT_RETURNTRANSFER, true);
switch ($method) {
case "POST":
curl_setopt($ci, CURLOPT_POST, true);
if (!empty($postfields)) {
$tmpdatastr = is_array($postfields) ? http_build_query($postfields) : $postfields;
curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr);
}
break;
default:
curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */
break;
}
$ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE;
curl_setopt($ci, CURLOPT_URL, $url);
if ($ssl) {
curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在
}
curl_setopt($ci, CURLOPT_HEADER, FALSE); /*启用时会将头文件的信息作为数据流输出*/
$ch = $ci;
if($useCert == true){
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
//证书文件请放入服务器的非web目录下
$sslCertPath = "../extend/wxpay_lib/cert/apiclient_cert.pem";
$sslKeyPath = "../extend/wxpay_lib/cert/apiclient_key.pem";
$config->GetSSLCertPath($sslCertPath, $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);
}
curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/
curl_setopt($ci, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ci, CURLINFO_HEADER_OUT, true);
/*curl_setopt($ci, CURLOPT_COOKIE, $Cookiestr); * *COOKIE带过去** */
$response = curl_exec($ci);
$requestinfo = curl_getinfo($ci);
$http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
if ($debug) {
echo "=====post data======\r\n";
var_dump($postfields);
echo "=====info===== \r\n";
print_r($requestinfo);
echo "=====response=====\r\n";
print_r($response);
}
curl_close($ci);
return $response;
//return array($http_code, $response,$requestinfo);
}
/**
* 以post方式提交xml到对应的接口url
*
* @param WxPayConfigInterface $config 配置对象
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $second url执行超时时间,默认30s
* @throws WxPayException
*/
private static function postXmlCurl($config, $xml, $url, $useCert = false, $second = 30)
{
$ch = curl_init();
$curlVersion = curl_version();
$ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
.$config->GetMerchantId();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
$proxyHost = "0.0.0.0";
$proxyPort = 0;
$config->GetProxy($proxyHost, $proxyPort);
//如果有配置代理这里就设置代理
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_USERAGENT, $ua);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($useCert == true){
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
//证书文件请放入服务器的非web目录下
$sslCertPath = "";
$sslKeyPath = "";
$config->GetSSLCertPath($sslCertPath, $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");
}
}
/**
* 获取毫秒级别的时间戳
*/
private static function getMillisecond()
{
//获取毫秒的时间戳
$time = explode ( " ", microtime () );
$time = $time[1] . ($time[0] * 1000);
$time2 = explode( ".", $time );
$time = $time2[0];
return $time;
}
}