小程序支付 和 回调处理

最近做了一个小程序支付,发现网上还是有很多问题的今天就把整个思路和代码贴给大家并详细的讲解一下
思路:
	1.预支付
	2.预支付返回的prepay_id 进行重新组建数据,搭建需要的参数调用js进行移动端支付
	3.在2步骤中上传的notify_url地址就是回调地址记住:小程序这个地址不允许有参数切记,在这个地址中处理你的服务器的逻辑
准备的数据:
	1.小程序appid
	2.小程序绑定的商户号
	3.商户号申请或者重置后的key值
代码:
    1.准备配置公共配置参数  
	    static $wxpayconf = [
	        'wx_smallprogram_appid'=>'wx13212312313123',// 小程序appid
	        'mch_id'=>12312345454 ,// 小程序商户号
	        'key'=>'Gjanuarygaoqingsong',
	        'appsecret'=>'1231231212daseqe123213',
	        'notify_url'=>'http://www.gaoqingsong.com/index.php?',// 支付回调地址
	    ];
	2.预支付部分(其实就是统一下单和微信公众号的都一样不过这里我还要贴出来), 支付环境配置部分:
		 // 统一下单,并且直接支付
	    public function unifiedorder($paydata,$orderSn){
			// 初始化小程序支付配置
			$wxpayconf = self::$wxpayconf;
			$userIP = $_SERVER['REMOTE_ADDR'];
			$sign['appid']            = $appid            = $wxpayconf['wx_smallprogram_appid'];
			$sign['mch_id']           = $mch_id           = $wxpayconf['mch_id'];
			$sign['nonce_str']        = $nonce_str        = $this->nonceStr();
			$sign['body']             = $body             = 'Gjanury测试商品';
			$sign['out_trade_no']     = $out_trade_no     = $orderSn;
			$sign['total_fee']        = $total_fee        = 1/*$paydata['actualpayment'] * 100*/;// 单位是分 1元 = 100分
			$sign['spbill_create_ip'] = $spbill_create_ip = $userIP;// 终端客户端ip
			$sign['trade_type']       = $trade_type       = 'JSAPI';
			$sign['openid']           = $openid           = $paydata['openid'];
			$sign['notify_url']       = $notify_url       = $wxpayconf['notify_url'];;
			$sign['sign'] = $this->getSign($sign,$wxpayconf['key']);
			// 统一下单接口
			$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
			$data = $this->arrayToXml($sign);
			$res = $this->postXmlCurl($data,$url);
			if($res['errNum'] != 0){
				echo json_encode($res);
				die;
			}else{
				$res = $res['info'];
			}
			if($res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS'){
				$prepay_id = $res['prepay_id'];

				// prepay_id的为预支付
				// 小程序提供的是接口而已。直接调出即可
	            // 支付环境参数配置部分

				$yuzhifudata['appId'] = $wxpayconf['wx_smallprogram_appid'];
				$yuzhifudata['package'] =  'prepay_id='.$prepay_id;
				$yuzhifudata['timeStamp'] = (string)time();
				$yuzhifudata['nonceStr'] = $this->nonceStr();
				$yuzhifudata['signType'] = 'MD5';
				$yuzhifudata['paySign'] = $this->getSign($yuzhifudata,$wxpayconf['key']);
				exit(json_encode($yuzhifudata));

			}else{
				if($res['return_code'] == 'FAIL'){
					$info['return_msg'] = $res['return_msg'];
				}
				if($res['result_code'] == 'FAIL'){
					$info['err_code'] = $res['err_code'];
					$info['err_code_des'] = $res['err_code_des'];
				}
				exit($info);
			}

			//  签名方法
			  private function getSign($params, $key1)
		    {
		        //签名步骤一:按字典序排序数组参数
		        ksort($params);
				$singstring = '';
				foreach ($params as $key => $value) {
					$singstring .= '&'.$key . '=' . $value;
				}
		        $string = $singstring . "&key=" . $key1;
		        //签名步骤三:MD5加密
				$string = ltrim($string,'&');
		        $string = md5($string);
		        //签名步骤四:所有字符转为大写
		        $result = strtoupper($string);
		        return $result;
		    }
		    // 数组转xml
		    public function arrayToXml($arr,$is_array=false){  
				if (!$is_array){  
					$xml = '<xml>';  
				}  
				foreach ($arr as $key=>$val){  
					if(is_array($val)){  
						$xml.="<".$key.">".$this->arrayToXml($val,true)."</".$key.">";  
					}else{  
						$xml.="<".$key.">".$val."</".$key.">";  
					}  
				}  
				if (!$is_array){  
					$xml.="</xml>";  
				}
				return $xml;		
			}
	3.小程序js部分:
		wx.request({
            url: 'http://www.gaoqingsong.com/index.php?m=xxxx&a=payment', 
            data: {
                tablenumber: tablenumber,// 下单桌号
                orderdetail: JSON.stringify(that.data.goods),// 点餐的详情,对象转化成json字符串方便后台进行存储调用
                amount: that.data.totalprice,// 订餐的总价格
                actualpayment: that.data.totalprice,// 实际付款金额
                openid: useropenid,// 实际付款金额
                wxheadimg: wxinfo.avatarUrl,
                wxname: wxinfo.nickName,
            },
            method: "POST",
            header: {
                "content-type": "application/x-www-form-urlencoded"// post方式提交用这种header头
            },
            success: function (res) {
                
                wx.navigateTo({
                    url: '../paysuccess/paysuccess'
                });
                var timeStamp = res.data.timeStamp;
                wx.requestPayment(
                    {
                        'timeStamp': timeStamp,
                        'nonceStr': res.data.nonceStr,
                        'package': res.data.package,
                        'signType': res.data.signType,
                        'paySign': res.data.paySign,
                        'success': function (res) {
                            if(res.errMsg == 'requestPayment:ok'){
                                wx.navigateTo({
                                    url: '../paysuccess/paysuccess'
                                });
                            }
                         },
                        'fail': function (res) {
                            console.log(res);
                            
                        },
                        'complete': function (res) { }
                    }
                )
                
            }
        })
    4.回调页面代码:
    	$receipt = $_REQUEST;
		if($receipt==null){
			$receipt = file_get_contents("php://input");
		}
		if($receipt == null){
			$receipt = $GLOBALS['HTTP_RAW_POST_DATA'];
		}
		$post_data = $this->xml_to_array($receipt); 
		$postSign = $post_data['sign'];
        unset($post_data['sign']);
        ksort($post_data);// 对数据进行排序
        $str = $this->ToUrlParams($post_data);//对数组数据拼接成key=value字符串
        $user_sign = strtoupper(md5($post_data));   //再次生成签名,与$postSign比较
        $ordernumber = $post_data['out_trade_no'];// 订单可以查看一下数据库是否有这个订单

        if($post_data['return_code']=='SUCCESS'&&$postSign){
        	// 查询订单是否已经支付
       
        	$result = M('userorder')->where('ordernumber = "'.$ordernumber.'"')->select();
          
 		
        	if($result){
        		if($result[0]['paystatus'] == 0){
        			// 进行更改支付成功状态
        			$obj = array(
						"paystatus" =>1,
					);
        			$res = M('userorder')->where('ordernumber = "'.$ordernumber.'"')->save($obj);
        			file_put_contents('gg.txt',$res);
        			if($res){
						$this->return_success();
        			}
        		}else{
        			$this->return_success();
        		}
        	}else{
        		echo '微信支付失败,数据未存在该订单。';
        	}
        }else{
        	// 写个日志记录
        	file_put_contents('wxpayerrorlog.txt',$post_data['return_code'].PHP_EOL, FILE_APPEND);
            echo '微信支付失败';
        }
    5.效果图:
    	1.web开发端效果:
    	2.移动端效果:




<think>好的,我现在需要帮助用户了解如何在微信小程序中正确处理支付回调。首先,我需要明确用户的需求。用户已经看过一些参考资料,可能对支付回调的基本流程有一定了解,但需要具体的处理方法示例代码。 首先,我应该回顾微信小程序支付的整个流程。根据引用[3],流程分为三个接口:统一下单、生成支付参数、支付结果通知。支付回调属于第三个环节,即支付结果通知。这里需要注意,微信支付V3版本之前的版本可能有所同,但用户提到的是JSAPI-V3,所以需要确保示例代码符合V3的规范。 接下来,支付回调处理需要几个关键步骤。首先,商户服务器需要配置支付通知的回调地址,这个地址在统一下单接口中指定。然后,当用户完成支付后,微信服务器会向这个地址发送POST请求,携带支付结果的数据。商户服务器需要验证这个请求的合法性,处理业务逻辑,并返回正确的响应。 验证请求的合法性非常重要。根据微信支付的文档,回调请求会带有签名,服务器需要使用微信支付的API密钥进行验签,确保请求来自微信,防止伪造请求。引用[2]提到支付回调函数需要接收通知并进行处理,比如更新订单状态。因此,在示例代码中,需要包含验签的步骤。 然后,处理业务逻辑。验证通过后,服务器需要解析回调中的数据,如订单号、支付状态等,并更新本地数据库中的订单状态。同时,需要处理可能出现的重复通知问题,微信可能会多次发送回调,因此需要确保处理的幂等性。 接下来是示例代码部分。用户可能需要Node.js的示例,但根据引用[4],示例中有使用wx.request进行网络请求,但支付回调是在服务端处理的。因此,应该提供服务器端的代码示例,比如使用Node.js或Java。由于用户提供的引用中有Java云开发的例子,可能需要兼顾同的技术栈,但根据问题描述,用户可能更倾向于云开发或Node.js。 另外,引用[1]提到使用小程序云开发实现支付功能,可能涉及到云函数的使用。云开发的支付回调可以使用云函数来处理,但同样需要验证签名更新订单状态。因此,可能需要分别提供云函数传统服务器的处理示例。 需要注意,支付回调处理完成后,必须按照微信的要求返回正确的XML或JSON响应,否则微信会持续重试通知。例如,处理成功后返回<return_code><![CDATA[SUCCESS]]></return_code>。 此外,用户可能关心如何处理异步通知,比如在回调中更新订单状态后,如何通知小程序前端。可能需要结合WebSocket或前端轮询来更新支付状态,但回调处理本身是后端逻辑。 最后,要确保代码示例符合最佳实践,比如错误处理、日志记录、数据库事务等,以保证数据的完整性系统的可靠性。同时,需要提醒用户注意安全事项,如保护API密钥,防止泄露。</think>### 微信小程序支付回调处理方法及示例代码 在微信小程序支付流程中,**支付回调**是接收支付结果的核心环节。以下是处理步骤示例代码: --- #### 一、回调处理流程 1. **配置回调地址** 在微信支付商户平台设置`支付通知URL`(如`https://yourdomain.com/pay/callback`),该地址用于接收支付结果[^3]。 2. **验证签名** 微信会通过POST请求发送加密数据,需验证签名确保请求合法性: ```javascript // Node.js示例(使用微信支付V3 API) const { createHash } = require('crypto'); function verifySignature(wechatSign, body, timestamp, nonce, publicKey) { const signStr = `${timestamp}\n${nonce}\n${body}\n`; const verify = crypto.createVerify('SHA256'); verify.update(signStr); return verify.verify(publicKey, wechatSign, 'base64'); } ``` 3. **解析并处理业务逻辑** 解析回调数据,更新订单状态并返回响应: ```javascript app.post('/pay/callback', (req, res) => { const { resource } = req.body; // 微信V3回调数据结构 const decryptData = decryptResource(resource); // 解密数据 const { out_trade_no, trade_state } = decryptData; if (trade_state === 'SUCCESS') { // 更新订单状态(需处理幂等性) updateOrderStatus(out_trade_no, 'paid'); res.status(200).send({ code: 'SUCCESS', message: 'OK' }); } else { // 处理失败情况 logError(decryptData); res.status(500).send({ code: 'FAIL', message: '支付异常' }); } }); ``` --- #### 二、云开发示例(小程序云函数) 若使用小程序云开发,可通过云函数处理回调: ```javascript // 云函数 pay_cb const cloud = require('wx-server-sdk'); cloud.init(); exports.main = async (event) => { const { resource } = event; const decryptData = decryptResource(resource); // 解密逻辑 const db = cloud.database(); try { await db.collection('orders').where({ order_id: decryptData.out_trade_no }).update({ data: { status: 'paid' } }); return { code: 'SUCCESS' }; // 必须返回SUCCESS[^2] } catch (err) { console.error('回调处理失败:', err); return { code: 'FAIL' }; } }; ``` --- #### 三、注意事项 1. **签名验证** 必须验证回调请求来源,防止伪造攻击。 2. **幂等性处理** 使用数据库事务或唯一标识,避免重复更新订单。 3. **响应格式** 返回`SUCCESS`避免微信重复通知,格式需严格符合XML或JSON要求[^4]。 ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gjanuary

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值