微信官方微信支付产品有付款码支付,JSAPI支付,Native支付,App支付,H5支付,小程序支付,人脸支付等不同的支付产品,在这里我们讲解微信小程序支付
namespace App\Http\Controllers\api;
class WeChatPay
{
protected $app_id; //小程序 app id
protected $app_secret;//小程序的 secret
protected $mch_id;//小程序商户号
protected $key;//商户号 key
protected $notify_url;//支付回调地址
protected $pay_url;//统一下单请求地址
protected $trade_type;//支付交易类型
protected $order;
protected $sign_type;
protected $refund_url;
public function __construct($order)
{
$this->app_id = '';
$this->app_secret = '';
$this->key = '';
$this->mch_id = '';
$this->notify_url = '';
$this->pay_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$this->refund_url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
$this->trade_type = 'JSAPI';
$this->order = $order;
}
/**
* 微信对外支付方法
*/
public function pay()
{
//统一下单 返回与支付信息
$pre_pay_id = $this->unifiedOrder($this->order);
//再次签名
$package = $this->signAgain($pre_pay_id);
return $package;
}
//微信统一下单
private function unifiedOrder($order)
{
$post_data = $this->getPayData($order);
$wx_return_data = $this->httpCurl($post_data, $this->pay_url);
$wx_return_data = $this->xmlToArray($wx_return_data);
if ($wx_return_data['return_code'] == 'SUCCESS' && $wx_return_data['result_code'] == 'SUCCESS') {
$pre_pay_id = $wx_return_data['prepay_id'];
return $pre_pay_id;
}
//记录错误信息或者抛出异常
Log::error('统一下单失败,微信返回信息:' . json_encode($wx_return_data));
throw new \Exception($wx_return_data['return_msg'], 401);
}
//根据预下单id再次签名
private function getPayData($order)
{
$params = [
'appid' => $this->app_id,
'mch_id' => $this->mch_id,
'nonce_str' => $this->createNoncestr(),
'sign_type' => $this->sign_type,
'body' => $order['pro_desc'],
'attach' => $order['attach'], //此处传入的 attach,在回调的时候会原样返回
'out_trade_no' => $order['order_num'],
'fee_type' => 'CNY',
'total_fee' => $order['total_fee'], //单位为分
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],//终端ip
'notify_url' => $this->notify_url,
'trade_type' => $this->trade_type,
'openid' => $order['openid'], //交易类型为JSAPI时,必传
];
$params['sign'] = $this->makeSign($params);
$data = $this->arrayToXml($params);
return $data;
}
/**
* 创建32位随机字符串
* @param int $length
* @return string
*/
private function createNoncestr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 获取签名
* @param $params array 所有发送的数据集合
* @return string string 签名信息
*/
private function makeSign($params)
{
ksort($params);
$string = $this->formatIntoQueryStr($params);
$string = strtoupper(md5($string . "&key=" . $this->key));
return $string;
}
/**
* 格式化成查询字符串
* @param $params
* @return string
*/
private function formatIntoQueryStr($params)
{
$tmp_array = [];
foreach ($params as $key => $value) {
if (empty($value)) continue;
$tmp_array[] = $key . '=' . $value;
}
$string = implode("&", $tmp_array);
unset($tmp_array);
return $string;
}
/**
* 将数组转换为xml字符串
* @param $arg array
* @return bool|string
*/
private function arrayToXml($arg)
{
if (!is_array($arg) || count($arg) == 0) return false;
$xml = "" ;
foreach ($arg as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "" . $key . ">";
} else {
$xml .= "<" . $key . "> . $val . "]]>" . $key . ">";
}
}
$xml .= "";
return $xml;
}
private function httpCurl($xml, $url,$is_need_pem = false, $second = 30, $headers = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
//设置header
if ($headers) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
} else {
curl_setopt($ch, CURLOPT_HEADER, FALSE);
}
//是否需要使用证书:cert 与 key 分别属于两个.pem文件
if ($is_need_pem) {
$cert_path = '';
$key_path = '';
//默认格式为PEM,可以注释
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $cert_path);
//默认格式为PEM,可以注释
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $key_path);
}
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
//运行curl
$data = curl_exec($ch);
//返回结果
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new \Exception("curl出错,错误码:$error");
}
}
/**
* 通过 simpleXML 将xml转换为数组,如果xml某一项为空,会将该项转换为空数组
* @param $xml
* @return bool|false|mixed|SimpleXMLElement|string
*/
private function xmlToArray($xml)
{
if (!$xml) return false;
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$data = json_encode($data);
$data = json_decode($data, true);
return $data;
}
/**
* 将xml转换为数组
* @param $xml
* @return array
*/
private function xml2array($xml)
{
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $value_arr, $index_arr);
xml_parser_free($p);
$data = [];
foreach ($index_arr as $key => $value) {
if ($key == 'xml' || $key == 'XML') continue;
$tag = strtolower($value_arr[$value[0]]['tag']);
$value = $value_arr[$value[0]]['value'];
$data[$tag] = $value;
}
return $data;
}
/**
* 返回再次签名信息
* @param $pre_pay_id
* @return array
*/
private function signAgain($pre_pay_id)
{
$data = [
'appId' => $this->app_id,
'timeStamp' => time(),
'nonceStr' => $this->createNoncestr(),
'package' => 'prepay_id=' . $pre_pay_id,
'signType' => $this->sign_type,
];
$sign = $this->makeSign($data);
unset($data['appId']);
$data['paySign'] = $sign;
return $data;
}
/**
* 微信支付回调
* @return string
*/
public function payNotify()
{
$xml = file_get_contents("php://input");
$data = $this->xml2array($xml);
//微信支付回调其他业务操作
return "
";
}
/**
* 微信退款
* @param $order
*/
public function refund($order)
{
$xml = $this->getRefundXml($order);
$wx_refund_result = $this->httpCurl($xml, $this->refund_url, true);
$result = $this->xml2array($wx_refund_result);
if( $result['return_code'] == 'SUCCESS'){
//退款成功,处理业务逻辑
}else{
throw new \Exception('微信退款错误信息:'.$result['return_msg'],401);
}
}
/**
* @param $order
* @return array
*/
private function getRefundXml($order)
{
$data = [
'appid' => $this->app_id,
'mch_id' => $this->mch_id,
'nonce_str' => $this->createNoncestr(),
'sign_type' => $this->sign_type,
'out_trade_no' => $order['trade_no'],
'out_refund_no' => $order['refund_no'],
'total_fee' => $order['total_fee'],
'refund_fee' => $order['refund_fee'],//单位为分
'refund_desc' => $order['refund_desc'],//退款原因
'refund_account' => 'REFUND_SOURCE_RECHARGE_FUNDS',//退款资金来源
];
$sign = $this->makeSign($data);
$data['sign'] = $sign;
$xml = $this->arrayToXml($data);
return $xml;
}
}
注意其中重要的环节需要记录日志和异常处理