富友支付AS微信支付
刚好公司有个需求,需要把公众号的微信支付,改成富友AS微信支付(我自己起的名字),而且是要用C#去写。对于只用过PHP和Python的我来说,那是一脸懵逼。最后研究了一下富友开放平台的文档,折腾了一天终于还是把问题解决了。C#的代码有点略多,简单点我还是贴个PHP代码出来,主要是流程和踩坑点,也是做个记录,省的下次再去折腾。
PS:富友官方的Demo有点坑,和文档不匹配,不过按照文档去修改下Demo其实也很简单就能实现了。
富友AS微信支付:起这个名字的原因是,通过调用富友的统一下单接口,然后在返回数据中取到调起微信支付所需要的参数去调起微信支付。
这里我只说实现的流程和注意的地方,最后代码封装什么的自己去做吧,不管用什么语言都差不多。
微信公众号/服务窗统一下单接口:https://fundwx.fuiou.com/wxPreCreate
富友开放平台:https://open.fuioupay.com/partner/
首先自然是定义报文了,报文如下:
$data=array();
$data['version']="1.0";
$data['ins_cd']="08A9999999";//机构号,接入机构在富友的唯一代码
$data['mchnt_cd']="0002900F0370542";//商户号, 富友分配给二级商户的商户号
$data['term_id']="88888888";//终端号
$data['random_str']='abcdefg';//随机字符串
$data['goods_des']="描述";//商品描述
$data['mchnt_order_no']= time();//内部订单号
$data['order_amt']=2000;//金额单位 fen
$data['term_ip']="127.0.0.1";
$data['txn_begin_ts']=date('YmdHis',time());//订单生成时间
$data['notify_url']="wx.test.com";//回调URL
$data['trade_type'] = 'JSAPI';//订单类型
$data['sub_appid'] = '';//wx2308b45bb46d7592
$data['sub_openid'] = "";//obrYV5uNkmBXZhLrEibWlLn0AbUg
$data['addn_inf']="";
$data['curr_type']="CNY";
$data['goods_detail']="";
$data['goods_tag']="";
$data['limit_pay'] = "";
$data['openid'] = "";
$data['product_id'] = "";
这里是所有签名要用到的字段,按照文档所说:
1.将接口中每一个字段(sign 及 reserved 开头字段除外),以字典顺序排 序之后,按照 key1=value1&key2=value2…..的顺序,进行拼接。
2.对得到的字符串进行 RSA 签名/验签 注:sign 及 reserved 开头字段除外的其他非必填字段也需要参与验签。 我司会根据后期业务需求,新增 reserved 开头字段,请提前做好兼容 (简而言之,我们会新增 reserved 开头字段的字段,这些字段都不参与验 签)。
菜鸟如我,在这个简单的地方就踩了一个坑,而踩坑的原因在于没有认真的去阅读文档,想当然的用微信支付的签名方式去处理了报文:微信支付中字段为空的不参与签名,而富友支付这里,除了sign和reserved打头的字段外都是需要参与签名的,这就导致了之后我的验签无法通过。
参数有了,现在进行字典排序
function signSort( $params){if (!is_array($params)){$params = array();}ksort($params);$signStr = "";foreach ($params as $k => $v) {$signStr .= $k.'='.$v."&";}//var_dump(substr($signStr,0,-1));return substr($signStr,0,-1);
}
$sign = signSort($data);
很简单,直接使用ksort()这个自带函数就好了,然后拼接成key1=value1&key2=value2……的格式,貌似大部分接口都是这么搞的。
现在是关键的一步:签名,这里直接贴上Demo中的类方法,下面直接用了
class A2Xml {private $xml = null;function __construct() {$this->xml = new XmlWriter();}//数组转xmlfunction toXml($data, $eIsArray=FALSE) {if(!$eIsArray) {$this->xml->openMemory();}foreach($data as $key => $value){if(is_array($value)){$this->xml->startElement($key);$this->toXml($value, TRUE);$this->xml->endElement();continue;}$this->xml->writeElement($key, $value);}if(!$eIsArray) {$this->xml->endElement();return $this->xml->outputMemory(true);}}//签名加密流程function sign($data){//读取密钥文件$pem = file_get_contents(dirname(__FILE__).'/keypem.pem');//获取私钥$pkeyid = openssl_pkey_get_private($pem);//var_dump($pkeyid);//MD5WithRSA私钥加密openssl_sign($data,$sign,$pkeyid,OPENSSL_ALGO_MD5);//返回base64加密之后的数据//echo($sign);$t=base64_encode($sign);//解密-1:error验证错误 1:correct验证成功 0:incorrect验证失败//$pubkey = openssl_pkey_get_public($pem);//$ok = openssl_verify($data,base64_decode($t),$pubkey,OPENSSL_ALGO_MD5);// var_dump($ok);return $t;}//通过curl模拟post的请求;function SendDataByCurl($url,$data){//对空格进行转义$url = str_replace(' ','+',$url);$ch = curl_init();//设置选项,包括URLcurl_setopt($ch, CURLOPT_URL, "$url");curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);curl_setopt($ch, CURLOPT_HEADER, 0);curl_setopt($ch,CURLOPT_TIMEOUT,3); //定义超时3秒钟curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// POST数据curl_setopt($ch, CURLOPT_POST, 1);// 把post的变量加上curl_setopt($ch, CURLOPT_POSTFIELDS, $data); //所需传的数组用http_bulid_query()函数处理一下,就ok了//执行并获取url地址的内容$output = curl_exec($ch);$errorCode = curl_errno($ch);//释放curl句柄curl_close($ch);if(0 !== $errorCode) {return $errorCode;}return $output;}}
//签名加密流程$xml = new A2Xml();function sign($data){//读取密钥文件$pem = file_get_contents(dirname(__FILE__).'/keypem.pem');//获取私钥$pkeyid = openssl_pkey_get_private($pem);//var_dump($pkeyid);//MD5WithRSA私钥加密openssl_sign($data,$sign,$pkeyid,OPENSSL_ALGO_MD5);//返回base64加密之后的数据//echo($sign);$t=base64_encode($sign);//解密-1:error验证错误 1:correct验证成功 0:incorrect验证失败//$pubkey = openssl_pkey_get_public($pem);//$ok = openssl_verify($data,base64_decode($t),$pubkey,OPENSSL_ALGO_MD5);// var_dump($ok);return $t;}$data['sign'] = $xml->sign($sign);
接下来需要转化数组为XML格式
//完整的xml格式
$requestXml = "<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?><xml>".$xml->toXml($data)."</xml>";
CURL模拟发送post请求
//经过两次urlencode()之后的字符串
$requestParam = "req=".urlencode(urlencode($requestXml ));
//通过curl的post方式发送接口请求
$url = "https://fundwx.fuiou.com/wxPreCreate";//返回的xml字符串
$resultXml = URLdecode(URLdecode($xml->SendDataByCurl($url,$requestParam )));
//将xml转化成对象
$ob= simplexml_load_string($resultXml);//输出结果
echo '<pre>';
var_dump($ob);
echo '</pre>';
整个过程很简单,用C#实现流程也一样的,官方Demo中的问题也就是报文的字段不全,以及订单类型字段不对,其他的按照官方Demo运行就OK了。