支付宝退款与微信退款的PHP代码示例

1、支付宝退款有老版新版两种接口,老版接口叫“即时到账批量退款有密接口”,接口名称叫“refund_fastpay_by_platform_pwd”,也就是说调用退款接口的时候,会跳转到支付宝那边去,然后需输入商家账号的支付密码确认才能完成退款操作,显然,这跟直接登陆支付宝商家账号操作退款差不多,都需要人工来处理,无法通过纯代码来实现退款了。老版有密退款的PHP代码示例如下:

	//退款接口
	function apply_refund($refund_info) {
        //配置信息
		$alipay_config['partner'] = '合作者身份ID';
		$alipay_config['seller_user_id'] = $alipay_config['partner'];
		$alipay_config['key'] = '支付密钥';
		$alipay_config['notify_url'] = 'http://ken.01h.net/alipay_refund_notify.php';  //退款通知url地址
		$alipay_config['sign_type'] = strtoupper('MD5');
		$alipay_config['refund_date'] = date("Y-m-d H:i:s",time());  //退款日期 时间格式 yyyy-MM-dd HH:mm:ss
		$alipay_config['service'] = 'refund_fastpay_by_platform_pwd';  //退款接口名
		$alipay_config['input_charset'] = strtolower('utf-8');
		$alipay_config['cacert'] = '/payments/alipay/lib' . '\\cacert.pem';
		$alipay_config['transport'] = 'http';
        require_once("/payments/alipay/lib/alipay_submit.class.php");  //此文件在支付宝旧版接口的SDK中
		
		//批次号,必填,格式:当天日期[8位]+序列号[3至24位],如:201908171000001
		$batch_no = $refund_info['WIDbatch_no'];
		//退款笔数,必填,参数detail_data的值中,“#”字符出现的数量加1,最大支持1000笔(即“#”字符出现的数量999个)
		$batch_num = $refund_info['WIDbatch_num'];
		//退款详细数据,必填,格式(支付宝交易号^退款金额^备注),多笔请用#隔开
		$detail_data = $refund_info['WIDdetail_data'];
		
		//构造要请求的参数数组,无需改动
		$parameter = array(
			"service" => trim($alipay_config['service']),
			"partner" => trim($alipay_config['partner']),
			"notify_url"	=> trim($alipay_config['notify_url']),
			"seller_user_id"	=> trim($alipay_config['seller_user_id']),
			"refund_date"	=> trim($alipay_config['refund_date']),
			"batch_no"	=> $batch_no,
			"batch_num"	=> $batch_num,
			"detail_data"	=> $detail_data,
			"_input_charset"	=> trim(strtolower($alipay_config['input_charset']))
		);
		
		//建立请求
		$alipaySubmit = new AlipaySubmit($alipay_config);
		$html_text = $alipaySubmit->buildRequestForm($parameter,"get", "确认");
        header("Content-type: text/html; charset=utf-8");
        echo $html_text;
        exit;
	}

支付宝有密退款只有异步通知,所以还得配一份接受异步通知的代码:

	//退款通知
	function alipay_refund_notify() {
        //配置信息
		$alipay_config['partner'] = '合作者身份ID';
		$alipay_config['seller_user_id'] = $alipay_config['partner'];
		$alipay_config['key'] = '支付密钥';
		$alipay_config['sign_type'] = strtoupper('MD5');
		$alipay_config['refund_date'] = date("Y-m-d H:i:s",time());  //退款日期 时间格式 yyyy-MM-dd HH:mm:ss
		$alipay_config['service'] = 'refund_fastpay_by_platform_pwd';  //退款接口名
		$alipay_config['input_charset'] = strtolower('utf-8');
		$alipay_config['cacert'] = '/payments/alipay/lib' . '\\cacert.pem';
		$alipay_config['transport'] = 'http';
        require_once("/payments/alipay/lib/alipay_submit.class.php");  //此文件在支付宝旧版接口的SDK中
		
		//计算得出通知验证结果
		$alipayNotify = new AlipayNotify($alipay_config);
		$verify_result = $alipayNotify->verifyNotify();
		
		if($verify_result) {//验证成功
			//批次号
			$batch_no = $_POST['batch_no'];
			//批量退款数据中转账成功的笔数
			$success_num = $_POST['success_num'];
			//批量退款数据中的详细信息
			$result_details = $_POST['result_details'];
			$notify_result = array(
				'batch_no' => $batch_no,
				'success_num' => $success_num,
				'result_details' => $result_details,
				'trade_status' => '1',
			);
		}else{
			$notify_result = array(
				'trade_status' => '0',
			);
		}
		return $notify_result;
	}

由于支付宝有密退款接口是通过传递支付宝交易号这个参数作为必要条件的,但一般我们的平台中只保存了外部订单号,所以还得有一个查询接口,即通过外部订单号查询支付宝交易号,示例代码如下:

	//查询接口,因为有密退款接口需要支付宝交易号,而平台中只保存了外部订单号,需通过外部订单号查询支付宝交易号
	function trade_query($out_trade_no) {
        //配置信息
		$alipay_config['partner'] = '合作者身份ID';
		$alipay_config['key'] = '支付密钥';
		$alipay_config['sign_type'] = strtoupper('MD5');
		$alipay_config['input_charset'] = strtolower('utf-8');
		$alipay_config['cacert'] = '/payments/alipay/lib' . '\\cacert.pem';
		$alipay_config['transport'] = 'http';
        require_once("/payments/alipay/lib/alipay_submit.class.php");

		//构造要请求的参数数组,无需改动
		$parameter = array(
			"service" => "single_trade_query",
			"partner" => trim($alipay_config['partner']),
			//"trade_no"	=> $trade_no,
			"out_trade_no"	=> $out_trade_no,
			"_input_charset"	=> trim(strtolower($alipay_config['input_charset']))
		);

		//建立请求
		$alipaySubmit = new AlipaySubmit($alipay_config);
		$html_text = $alipaySubmit->buildRequestHttp($parameter);

		//解析XML
		//注意:该功能PHP5环境及以上支持,需开通curl、SSL等PHP配置环境。建议本地调试时使用PHP开发软件
		$doc = new DOMDocument();
		$doc->loadXML($html_text);
		if( ! empty($doc->getElementsByTagName( "alipay" )->item(0)->nodeValue) ) {
			return $doc->getElementsByTagName( "trade_no" )->item(0)->nodeValue;
		}else{
			return '';
		}
	}

以上看来,支付宝老版有密退款接口不仅操作上比较麻烦,代码实现上也相对麻烦一些。而新版接口就简单多了,但新版接口需要配置支付宝开放平台对应的APPID下的RSA密钥对,而且,支付宝新版接口的SDK也跟之前老版的完全不一样了,示例代码如下:

    /**
     *    退款接口
     */
	function apply_refund($refund_info) {
		//引入配置,以下所引用的文件都在支付宝新版接口的SDK中
		require_once dirname(__FILE__).'/config.php';
		require_once dirname(__FILE__).'/pagepay/service/AlipayTradeService.php';
		require_once dirname(__FILE__).'/pagepay/buildermodel/AlipayTradeRefundContentBuilder.php';
		
		//构造参数
		$RequestBuilder=new AlipayTradeRefundContentBuilder();
		$RequestBuilder->setOutTradeNo($refund_info['order_sn']);  //外部订单号,可选,与支付宝交易号二选一
		$RequestBuilder->setTradeNo($refund_info['trade_no']);  //支付宝交易号,可选,与外部订单号二选一
		$RequestBuilder->setRefundAmount($refund_info['refund_fee']);  //需要退款的金额,该金额不能大于订单金额,必填
		$RequestBuilder->setOutRequestNo($refund_info['refund_sn']);  //标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传
		$RequestBuilder->setRefundReason($refund_info['refund_reason']);  //退款的原因说明
		
		$aop = new AlipayTradeService($config);
		
		/**
		 * alipay.trade.refund (统一收单交易退款接口)
		 * @param $builder 业务参数,使用buildmodel中的对象生成。
		 * @return $response 支付宝返回的信息
		 */
		$response = $aop->Refund($RequestBuilder);
		return $response;  //json对象
		/* 返回结果示例
{"code": "10000", "msg": "Success", "buyer_logon_id": "159****5620", "buyer_user_id": "2088101117955611", "fund_change": "Y", "gmt_refund_pay": "2019-08-17 15:45:57", "out_trade_no": "1920707058", "refund_fee": 88.88, "send_back_fee": "0.00", "trade_no": "2019081722001413990559810225"}
		*/
	}

配置文件config.php内容示例:

$config = array (	
		//应用ID,您的APPID。
		'app_id' => PAYMENT_ALIPAY_APPID,

		//商户私钥
		'merchant_private_key' => MERCHANT_PRIVATE_KEY,
		
		//异步通知地址
		'notify_url' => "",
		
		//同步跳转
		'return_url' => "",

		//编码格式
		'charset' => "UTF-8",

		//签名方式
		'sign_type'=>"RSA2",

		//支付宝网关
		'gatewayUrl' => "https://openapi.alipay.com/gateway.do",

		//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
		'alipay_public_key' => ALIPAY_PUBLIC_KEY,
);

备注:支付宝新版退款接口是可以通过传递外部订单号这个参数的,当然也可以传递支付宝交易号,二选一,且无需异步通知。

2、接下来再说说微信的退款接口,其实跟支付宝新版的接口逻辑上是一样的,只不过多了个异步通知而已,示例代码如下:

	//退款接口
	function apply_refund($refund_info) {
		require_once ("/payments/wxjsapi/lib/WxPay.Api.php");
		$input = new WxPayRefund();  //申请退款
		$input->SetOut_trade_no($refund_info['order_sn']); //商户订单号
		$input->SetOut_refund_no($refund_info['refund_sn']); //退款编号
		$input->SetTotal_fee(intval(bcmul($refund_info['total_fee'], 100 ,0))); //总金额
		$input->SetRefund_fee(intval(bcmul($refund_info['refund_fee'], 100 ,0))); //退款金额
		$input->SetOp_user_id(MCHID); //操作员,默认为商户号
		$input->SetNotify_url('http://ken.01h.net/wx_refund_notify.php');  //异步通知url地址
		$result = WxPayApi::refund($input);
		return $result;
		/* 返回结果示例
Array ( [appid] => wxe344e3******a730 [cash_fee] => 27 [cash_refund_fee] => 27 [coupon_refund_count] => 0 [coupon_refund_fee] => 0 [mch_id] => 133***3701 [nonce_str] => ONKuZw5SSNgRquH4 [out_refund_no] => 201926720674 [out_trade_no] => 1920645422 [refund_channel] => Array ( ) [refund_fee] => 27 [refund_id] => 50000601022019080211155839761 [result_code] => SUCCESS [return_code] => SUCCESS [return_msg] => OK [sign] => F65D86BDD42F47F1308431575A76AF35 [total_fee] => 27 [transaction_id] => 4200000345201907268893222782 )
		*/
	}

配置文件WxPay.Config.php的内容示例如下:

/**
* 	配置账号信息
*/
class WxPayConfig
{
	//=======【基本信息设置】=====================================
	//
	/**
	 * TODO: 修改这里配置为您自己申请的商户信息
	 * 微信公众号信息配置
	 * 
	 * APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
	 * 
	 * MCHID:商户号(必须配置,开户邮件中可查看)
	 * 
	 * KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)
	 * 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
	 * 
	 * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置),
	 * 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
	 * @var string
	 */
        
	const APPID = APPID;
	const MCHID = MCHID;
	const KEY = KEY;
	const APPSECRET = APPSECRET;
	
	//=======【证书路径设置】=====================================
	/**
	 * TODO:设置商户证书路径
	 * 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
	 * API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
	 * @var path
	 */
	const SSLCERT_PATH = SSLCERT_PATH;  //API证书文件(apiclient_cert.pem)路径
	const SSLKEY_PATH = SSLKEY_PATH;  //API证书文件(apiclient_key.pem)路径
	
	//=======【curl代理设置】===================================
	/**
	 * TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
	 * 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
	 * 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
	 * @var unknown_type
	 */
	const CURL_PROXY_HOST = "0.0.0.0";//"10.152.18.220";
	const CURL_PROXY_PORT = 0;//8080;
	
	//=======【上报信息配置】===================================
	/**
	 * TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
	 * 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
	 * 开启错误上报。
	 * 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
	 * @var int
	 */
	const REPORT_LEVENL = 1;
}

处理退款异步通知的示例代码如下:

    /**
     * 微信退款通知
     */
    function wx_refund_notify() {
        if($GLOBALS['HTTP_RAW_POST_DATA']){
			$data = json_decode(json_encode(simplexml_load_string($GLOBALS['HTTP_RAW_POST_DATA'], 'SimpleXMLElement', LIBXML_NOCDATA)), true);  //将XML转为array
			$xml = $this->refund_decrypt($data['req_info'], MD5('商户密钥'));  //解码req_info信息,参看http://ken.01h.net/%e5%be%ae%e4%bf%a1%e9%80%80%e6%ac%be%e7%bb%93%e6%9e%9c%e9%80%9a%e7%9f%a5req_info%e8%a7%a3%e5%af%86%ef%bc%88php%e7%89%88%ef%bc%89/
			$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);  //将XML转为array
			//……
		}
		echo "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";  //告诉微信不要再发通知了
        exit;
    }

至此结束,正好写完吃饭!