早睡早起,方能养生
Sleep early rise early, way to keep healthy

PHP微信APP支付

super
2018-08-06 12:16
views 4034

首先,需要注册注册个开放平台以及商户平台的账号,注册完成,经过乱七八糟的认证之后,你会得到商户号(1xxxxxxxxx),AppID,KEY(API秘钥 商户支付密钥:https://pay.weixin.qq.com/index.php/account/api_cert)

 

image.png

 

APP与微信支付主要的交互:

 

  1. 1. 用户在商户APP应用(移动端)中选择商品提交订单,支付方式选择微信支付。
  2. 2. 商户APP应用(后台)收到用户支付订单,调用微信支付中的统一下单接口
  3. 3. 商户APP应用(后台)统一下单接口调用成功后,返回的数据中有我们需要的prepay_id,按照签名规范重新生成一个签名,然后把这个重新生成的签名及app需要的数据返回给商户APP应用(移动端)。
  4. 4. 商户APP应用(移动端)收到商户APP应用(后台)的数据调起微信支付,用户进行支付
  5. 5. 商户APP应用(后台)的回调接口会收到微信发来的支付结果通知
  6. 6. 商户APP在回调接口中处理数据后返回给微信处理成功的通知
  7. 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 条讨论
top