PHP微信APP支付
super
2018-08-06 12:16
4034
首先,需要注册注册个开放平台以及商户平台的账号,注册完成,经过乱七八糟的认证之后,你会得到商户号(1xxxxxxxxx),AppID,KEY(API秘钥 商户支付密钥:https://pay.weixin.qq.com/index.php/account/api_cert)。
APP与微信支付主要的交互:
- 1. 用户在商户APP应用(移动端)中选择商品提交订单,支付方式选择微信支付。
- 2. 商户APP应用(后台)收到用户支付订单,调用微信支付中的统一下单接口。
- 3. 商户APP应用(后台)统一下单接口调用成功后,返回的数据中有我们需要的prepay_id,按照签名规范重新生成一个签名,然后把这个重新生成的签名及app需要的数据返回给商户APP应用(移动端)。
- 4. 商户APP应用(移动端)收到商户APP应用(后台)的数据调起微信支付,用户进行支付
- 5. 商户APP应用(后台)的回调接口会收到微信发来的支付结果通知
- 6. 商户APP在回调接口中处理数据后返回给微信处理成功的通知
- 7. 支付成功
tip:1,4是移动端所要做的事情,2,3,5,6是服务端后台要做的。
步骤1:用户在APP中下单
步骤2: 调用统一下单接口
/**
* APP下单
* $Body 商品描述
* $Attach 自定义数据,会在回调中原样返回,如果是数组则转化为json
* $Total_fee 订单总金额,单位为分
* $Notify_url 回调地址 必须为直接可访问的url,不能携带参数
* $Out_trade_no 商户系统内部订单号
* return array
* */
public function appPay($Body,$Attach,$Total_fee,$Notify_url,$Out_trade_no)
{
include_once "WxPay.Api.php";
$unifiedOrder= new \WxPayUnifiedOrder();
$unifiedOrder->SetBody($Body);//商品或支付单简要描述
$unifiedOrder->SetAttach($Attach);
$unifiedOrder->SetOut_trade_no($Out_trade_no);
$unifiedOrder->SetTotal_fee($Total_fee);
$unifiedOrder->SetNotify_url($Notify_url);
$unifiedOrder->SetTrade_type("APP");
$WxPayApi= new \WxPayApi();
$order = $WxPayApi->App($unifiedOrder);
return $order;
}
在lib/WxPay.Api.php中有WxPayApi类下创建一个App方法:
/**
* 统一下单,WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
* @param WxPayUnifiedOrder $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
* */
public static function App($inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//检测必填参数
if(!$inputObj->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()){
$inputObj->SetNotify_url(WxPayConfig::NOTIFY_URL);//异步通知url
}
$inputObj->SetAppid('wxxxxxxxxxxxxxxxxx');//公众账号ID
$inputObj->SetMch_id('1xxxxxxxxx');//商户号
$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
//$inputObj->SetSpbill_create_ip("1.1.1.1");
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
//签名
$inputObj->SetSign();
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($xml, $url, false, $timeOut);
$result = WxPayResults::Init($response);
self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
配置文件中的KEY要填写哦,上面签名要用到
步骤3:下单成功,重新生成一个签名,并把新数组返回到前台页面
if ($appPay['return_code'] == 'SUCCESS') {
$payArray = array();
$time = time();
$sign_array = array();
$sign_array['appid'] = $appPay['appid']; //注意 $sign_array['appid'] 里的参数名必须是appid
$sign_array['partnerid'] = $appPay['mch_id']; //注意 $sign_array['partnerid'] 里的参数名必须是partnerid
$sign_array['prepayid'] = $appPay['prepay_id'];//注意 $sign_array['prepayid'] 里的参数名必须是prepayid
$sign_array['package'] = 'Sign=WXPay'; //注意 $sign_array['package'] 里的参数名必须是package
$sign_array['noncestr'] = $appPay['nonce_str'];//注意 $sign_array['noncestr'] 里的参数名必须是noncestr
$sign_array['timestamp'] = $time; //注意 $sign_array['timestamp'] 里的参数名必须是timestamp
$sign_two = $this->MakeSign($sign_array);
$payArray = array(
'appid' => $appPay['appid'],
'noncestr' => $appPay['nonce_str'],
'package' => "Sign=WXPay",
'partnerid' => $appPay['mch_id'],
'prepayid' => $appPay['prepay_id'],
'timestamp' => (string)$time,
'sign' => $sign_two
);
}
/**
* 生成签名
* @return 签名
*/
public function MakeSign($params)
{
//签名步骤一:按字典序排序数组参数
ksort($params);
$string = $this->ToUrlParams($params);
//签名步骤二:在string后加入KEY
$string = $string . "&key=------------你的KEY------------";
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* 将参数拼接为url: key=value&key=value
* @param $params
* @return string
*/
public function ToUrlParams($params)
{
$string = '';
if ( !empty($params) ) {
$array = array();
foreach( $params as $key => $value ){
$array[] = $key.'='.$value;
}
$string = implode("&",$array);
}
return $string;
}
步骤4:把$payArray传到前台页面,调起微信支付页面
下面以wap2app(ThinkPHP3.2项目)调起为例(因为我只接入了微信支付,所以和官方文档可能有些不同)
var channels = null;
// 监听plusready事件
document.addEventListener("plusready", function(){
// 扩展API加载完毕,现在可以正常调用扩展API
plus.payment.getChannels( function(s){
channels = s;
//alert(channels[0].id);微信
//alert(channels[1].id);
}, function(e){
alert( "获取支付通道列表失败:"+e.message );
});
}, false);
// 请求支付操作
function requestPay() {
var statement = {$payArray};
plus.payment.request(channels[0], statement, function() {
layer.msg("支付成功!", {time:1400, icon:1}, function() {
location.href="{:U('User/Orderstatus/needdelivery')}";
});
}, function(e){
if (e.message.indexOf("-2") > 0) {
layer.msg("支付已取消", {time:2000}, function(){
location.href = "{:U('User/Orderstatus/needpay')}";
});
}
//alert("支付失败:"+ e.message +","+e.code);//调试的时候打开
});
}
步骤5、6:在回调接口中接受微信发送过来的数据,如果支付成功,则处理相应业务后返回给微信处理成功的信息
//app支付回调
public function app_notify()
{
$data = [];
$data = $this->getNotifyData();
$sign = '';
if ($data['return_code'] == 'SUCCESS') {
$sign = $data['sign'];
unset($data['sign']);
$verify_sign = $this->MakeSign($data);//这里的签名验证,必须是除数组sign外的所有数据(MakeSign方法前面有)
if ($verify_sign == $sign) {
$res = json_decode($data['attach'], true);//我们在调用同一下单方法传入的自定义数据(订单类型,订单号...(String(127)))
//微信官方:注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
//我们举个栗子:比如订单表 status字段 0代表未付款,1代表已付款待发货 我们在处理的时候应该先判断这条数据status是否==0,如果==0才进行处理,在处理完之后应把status = 1
//后台处理完数据 返回给微信结果
echo "<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>";
exit;
}
}
}
/**
*
* 获取支付结果通知数据
* return array
*/
public function getNotifyData()
{
//获取通知的数据
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
$data = array();
if ( empty($xml) ) {
return false;
}
$data = $this->xml_to_data($xml);
if ( !empty($data['return_code']) ) {
if ( $data['return_code'] == 'FAIL' ) {
return false;
}
}
return $data;
}
/**
* 将xml转为array
* @param string $xml
* return array
*/
public function xml_to_data($xml)
{
if (!$xml) {
return false;
}
// 将XML转为array
// 禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}
0 条讨论