今天在看了移动支付的文档,对整个流程都有了自己的理解,在这里记录下来自己的总结,把里面的逻辑都整理一遍
一、支付宝支付
先去看官方的支付文档,链接如下
https://openhome.alipay.com/platform/home.htm
1、先说前期准备,关键就是要生成一对公钥和私钥,这个看官方文档,现在官方有个自动生成工具,其实挺方便的。注意的是如果是java开发,生成的私钥要转成pkcs8格式。
(1)pkcs8的私钥自己保存,填到自己支付宝开发项目 里面的public static final String RSA_PRIVATE 这个字段里
(2)下面的蓝色2个选项,其实都是公钥。前个是自己上传的公钥,后一个是对应的支付宝公钥,这个支付宝公钥才是我们开发中需要的,这个切记。
(3)点红色方框,把自己生成的公钥上传给支付宝。
(4)点蓝色方框,得到支付宝的公钥,填到public static final String RSA_PUBLIC 这个字段里
2、准备完成了,接下来就是看请求参数的文档,这里告诉我们究竟要如何上传参数给支付宝后台,文档链接如下。
https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.kNJ0Uq&treeId=59&articleId=103663&docType=1
其实,支付宝接入的核心就是要组成下面图片所示的字符串,把这个字符串当参数传递调用支付宝Api即可。
我们来分析这个字符串。红色边框里面是签名内容sign和加密名称sign_type。(sign是根据我们上传参数生成的,也就是说所有我们上传的参数都需要一起签名;sign_type是签名方法,这里是固定值RSA)。而红色边框之外的是我们自己根据需要拼接的参数数据。接下来就是2个疑问
(1)红色边框之外的内容如何生成?
(2)红色边框里面的签名又是如何生成?
我们一个个来,先说红色边框之外的内容,把所有 值以key= “value”进行组合,之后用“&”字符连接起来,支持无序。 根据demo,我们使用一个String orderInfo = getOrderInfo("测试的商品", "该测试商品的详细描述", "0.01")方法得到这个字符串,而这个orderInfo就是红色边框之外的内容。
这个方法需要传递3个参数,第1个是商品的名字或者订单编号等,第2个是描述信息,第3个是支付的总金额。那么这个getOrderInfo的方法是怎么实现的,看下面代码。
private String getOrderInfo(String subject, String body, String price) {
// 签约合作者身份ID
String orderInfo = "partner=" + "\"" + PARTNER + "\"";
// 签约卖家支付宝账号
orderInfo += "&seller_id=" + "\"" + SELLER + "\"";
// 商户网站唯一订单号
orderInfo += "&out_trade_no=" + "\"" + getOutTradeNo() + "\"";
// 商品名称
orderInfo += "&subject=" + "\"" + subject + "\"";
// 商品详情
orderInfo += "&body=" + "\"" + body + "\"";
// 商品金额
orderInfo += "&total_fee=" + "\"" + price + "\"";
// 服务器异步通知页面路径
orderInfo += "¬ify_url=" + "\"" + "http://notify.msp.hk/notify.htm" + "\"";
// 服务接口名称, 固定值
orderInfo += "&service=\"mobile.securitypay.pay\"";
// 支付类型, 固定值
orderInfo += "&payment_type=\"1\"";
// 参数编码, 固定值
orderInfo += "&_input_charset=\"utf-8\"";
// 设置未付款交易的超时时间
// 默认30分钟,一旦超时,该笔交易就会自动被关闭。
// 取值范围:1m~15d。
// m-分钟,h-小时,d-天,1c-当天(无论交易何时创建,都在0点关闭)。
// 该参数数值不接受小数点,如1.5h,可转换为90m。
orderInfo += "&it_b_pay=\"30m\"";
// extern_token为经过快登授权获取到的alipay_open_id,带上此参数用户将使用授权的账户进行支付
// orderInfo += "&extern_token=" + "\"" + extern_token + "\"";
// 支付宝处理完请求后,当前页面跳转到商户指定页面的路径,可空
orderInfo += "&return_url=\"m.alipay.com\"";
// 调用银行卡支付,需配置此参数,参与签名, 固定值 (需要签约《无线银行卡快捷支付》才能使用)
// orderInfo += "&paymethod=\"expressGateway\"";
return orderInfo;
}
可以看到,上面的这个方法就是根据请求参数说明,拼接字符串而已。
接下来就是生成红色边框里面的内容,这里就是对上传的参数进行签名,注意这里的签名要放在我们自己的后台服务端上进行,不能放在app端,demo里面用了一个String sign = sign(orderInfo);方法得到签名的内容。最后对这个签名的内容进行URL的转义,千万不要忘记了,文档说明有说明注意看下图的黑色标记,demo用了这个方法sign = URLEncoder.encode(sign, "UTF-8");得到转义后的签名
通过上面的2个步骤,我们得到了一个orderInfo 字符串,是上传的参数信息。 还有一个sign,就是签名,而且这个签名做了转义处理。但是这2个字符串不符合我们上传的格式啊,因为这2个字符串还没有拼接起来,所以最后要把这2个字符串拼接得到一个PayInfo字符串
String payInfo = orderInfo + "&sign=\"" + sign + "\"&" + getSignType();
这个getSignType()方法也很简单,就是加上“RSA”即可,如下
private String getSignType() {
return "sign_type=\"RSA\"";
}
可以看到,走到这里,这个payInfo就符合格式了,也就是最后调用支付宝Api的参数,如下代码。
Runnable payRunnable = new Runnable() {
@Override
public void run() {
// 构造PayTask 对象
PayTask alipay = new PayTask(PayDemoActivity.this);
// 调用支付接口,获取支付结果
String result = alipay.pay(payInfo, true);//payInfo就是我们通过上面2个步骤得到的字符串,这里当成参数传入
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
// 必须异步调用
Thread payThread = new Thread(payRunnable);
payThread.start();
以上就是移动支付,关于支付代码的一些个人总结,大家可以结合官方代码看看
官方的demo点击下载,可以结合代码看看,整体思路和逻辑就更清楚了。
二、微信移动支付
首先当然去看官方文档了,链接如下
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1
接入的前期准备也不说,这里只说逻辑思路,微信支付需要注意的事项
1、提交和返回数据都为XML格式,根节点名为xml。
2、采用POST方法提交
3、签名的参数,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序)
4、交易金额默认为人民币交易,接口中参数支付金额单位为【分】,参数值不能带小数。对账单中的交易金额单位为【元】
5、我们可以使用传统的httpClient发送post请求,
byte[] buf = Util.httpPost(url, entity); //url就是请求的网络接口地址,entity就是根据需求封装的xml格式的字符串
String content = new String(buf); //服务端返回字节数组,我们再转成字符串,这个字符串就是我们得到的响应数据
说清楚了注意事项,接下来理清一下支付的流程逻辑,其实很简单,步骤如下
1、统一下单,商户系统先调用该接口在微信支付服务后台生成预支付交易单,
2、返回正确的预支付订单号,在APP里面调起支付接口。
那么一个个来吧,先看统一下单,生成预支付订单的文档,我们需要分析需要上传哪些参数,
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
从上图得知,最后的sign字段里面的数据就是微信支付接入的核心。那么问题就是,这个签名是怎么来的?
文档里面也说得非常清楚,其实就是2个步骤
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
我们看看上面的2个步骤是怎么通过代码来实现的,demo里首先走了这一句 ,
String entity = genProductArgs(); //这个方法的结果也就是说上面图片所示的全部内容,包括了签名。我们看看这个方法是怎么实现的
List packageParams = new LinkedList();
packageParams.add(new BasicNameValuePair("appid", Constants.APP_ID));
packageParams.add(new BasicNameValuePair("body", "APP pay test"));
packageParams.add(new BasicNameValuePair("mch_id", Constants.MCH_ID));
packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));
packageParams.add(new BasicNameValuePair("notify_url", "http://121.40.35.3/test"));
packageParams.add(new BasicNameValuePair("out_trade_no",genOutTradNo()));
packageParams.add(new BasicNameValuePair("spbill_create_ip","127.0.0.1"));
packageParams.add(new BasicNameValuePair("total_fee", "1"));
packageParams.add(new BasicNameValuePair("trade_type", "APP"));
String sign = genPackageSign(packageParams); //这一句就是把所有数据进行签名
private String genPackageSign(List params) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue()); //对参数按照key=value的格式组合 所以里面的参数排序一定要先排好,顺利不能乱
sb.append('&');
}
sb.append("key=");
sb.append(Constants.API_KEY);
String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
return packageSign; //在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写
}
可以看到,这个方法就是签名算法的具体实现,逻辑上并不难。最后得到的这个结果是个字符串,也就是sing字段里面的值,所以我们需要把它添加到上传的集合里面,所以程序继续走下面一句
packageParams.add(new BasicNameValuePair("sign", sign));
String xmlstring =toXml(packageParams);这个方法就是把集合里面的数据封装成xml的格式,我们来看看这个方法如何实现
private String toXml(List params) {
StringBuilder sb = new StringBuilder();
sb.append("");
for (int i = 0; i < params.size(); i++) {
sb.append("<"+params.get(i).getName()+">");
sb.append(params.get(i).getValue());
sb.append(""+params.get(i).getName()+">");
}
sb.append(" ");
return sb.toString();
}
其实很简单,循环遍历整个集合,分别把name 和value组合即可。最后得到的xmlstring 字符串就要上传的字符串,也就是entity的值,接下来直接走
byte[] buf = Util.httpPost(url, entity) ; //这个entity就是上传的xml字符串,里面包含了所有参数和签名字段
String content = new String(buf); //这个content 就是得到了微信后台服务器返回的所有参数
接下来我们看程序走这一句
Map
最后把这个map再赋值给resultunifiedorde,这个resultunifiedorder也是一个map。问题来了,这个resultunifiedorde有什么用?里面有个字段prepay_id就是支付订单号,调微信支付的接口,其中的一个字段的值就是它了,总结:前面的这么多步骤,其实核心就是一个 ,得到预支付订单号。
拿到了预支付订单号,接下来就是走支付接口,需要的参数如下
我们看到,这里调用接口,最后的一个参数要又是sign,好吧,其实又封装sign字段之前的所有字段来生成签名,代码如下
private void genPayReq() {
req.appId = Constants.APP_ID;
req.partnerId = Constants.MCH_ID;
req.prepayId = resultunifiedorder.get("prepay_id");
//req.packageValue = "prepay_id="+resultunifiedorder.get("prepay_id");
/**
* 这里的package参数值必须是Sign=WXPay,否则IOS端调不起微信支付,
* (参数值是"prepay_id="+resultunifiedorder.get("prepay_id")的时候Android可以,IOS不可以)
*/
req.packageValue = "Sign=WXPay";
req.nonceStr = genNonceStr();
req.timeStamp = String.valueOf(genTimeStamp())
List signParams = new LinkedList();
signParams.add(new BasicNameValuePair("appid", req.appId));
signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
signParams.add(new BasicNameValuePair("package", req.packageValue));
signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));
req.sign = genAppSign(signParams)//这里就是把之前的参数又封装和签名 也就是说demo里面的 genAppSign 和genPackageSign 这2个方法其实都是一样,不信可以去demo里看看
}
区别在于
(1)前者生成预支付订单号封装好所有的参数,需要组成xml格式字符串,使用HttpCliet去发送数据。
(2)而实际支付接口,我们需要封装成一个PayReq 对象reg,这个reg对象里面变量就是要上传的参数,然后使用微信支付类即可,
IWXAPI msgApi = WXAPIFactory.createWXAPI(this, null);
msgApi.registerApp(Constants.APP_ID);
msgApi.sendReq(req);
不用再使用HttpClient
走到这里,支付基本完成了,最后程序会执行我们自己项目里面的wxapi包的WXEntryActivity.java 里面的内容
以上的就是微信支付流程,需要大家去结合代码熟悉一下
这个是微信的支付demo下载
最后最后的总结
1、支付宝和微信都需要对上传参数做签名,都是以key value排列,用&号连接多个参数, 具体上传参数到各自的文档里看即可。
2、参加签名的参数,支付宝是无序,而微信有序。
3、支付宝只做了一次签名,而微信其实做了2次签名(第一次接口调用,使用了xml格式和HttpClient请求,第2次调用了msApi去发起实际支付)。