写过微信支付再写支付宝支付就好理解了很多。而且支付宝提供的sdk很好用,几行代码就可以了~~,写的代码还没有测试,应该问题不大,如果有错误希望各位指正。
开发之前必要的配置请参考支付宝APP支付官方文档
对几个容易混淆的参数进行说明:
1. 应用私钥: 通过工具生成,生成之后请保存好,在支付宝开发平台找不到。
2. 应用公钥:通过工具生成,生成之后需要填写到支付宝开发平台中。
3. 支付宝公钥:支付宝生成的,可以在支付宝开发平台中看到。
如果对非对称加密算法很熟悉的应该很好理解这几个参数。
这个图是APP支付的一个流程说明,图画的比较详细就不再叙述了。
开始写代码,因为我只帮别人写一部分,所以只完成工具类,具体controller没有,下面是参数封装的工具类。
/**
* 请求参数组装
*
* @param outTradeNo 商户网站唯一订单号
* @param totalAmount 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
* @param subject 商品的标题/交易标题/订单标题/订单关键字等。
* @param notifyUrl 回调地址
* @throws UnsupportedEncodingException
*/
public static Map<String, String> setParam(String outTradeNo, String totalAmount, String subject,
String notifyUrl) throws UnsupportedEncodingException {
Map<String, String> map = new HashMap<String, String>();
map.put("app_id", Config.APP_ID);
map.put("method", Config.PAY_METHOD);
map.put("charset", Config.CHARSET);
map.put("sign_type", Config.SIGN_TYPE);
map.put("timestamp", Config.geTtimestamp());
map.put("version", "1.0");
map.put("notify_url", notifyUrl);
// 业务请求参数封装
Map<String, Object> biz_content = new HashMap<>();
biz_content.put("subject", URLEncoder.encode(subject, "UTF-8"));
biz_content.put("out_trade_no", outTradeNo);
biz_content.put("total_amount", totalAmount);
biz_content.put("product_code", "QUICK_MSECURITY_PAY");
map.put("biz_content", biz_content.toString());
String sign = null;
try {
//调用支付宝SDK获取签名 RSA签名params 待签名参数map privateKey 私钥 charset 签名编码格式
sign = AlipaySignature.rsaSign(map, Config.APP_PRIVATE_KEY, Config.CHARSET);
} catch (AlipayApiException e) {
e.printStackTrace();
System.out.println(e.getMessage());
logger.error("======签名错误=====" + e.getMessage());
}
map.put("sign", URLEncoder.encode(sign, "UTF-8"));
return map;
}
注意(很重要)
- 商户在请求参数中,自己附属的一些额外参数,不要和支付宝系统中约定的key(下表中 公共请求参数\请求参数)重名,否则将可能导致未知的异常。需要对内容进行encode编码处理。
商户支付请求参数的安全注意点: - 请求参数的sign字段请务必在服务端完成签名生成(不要在客户端本地签名);
- 支付请求中的订单金额total_amount,请务必依赖服务端,不要轻信客户端上行的数据(客户端本地上行数据在用户手机环境中无法确保一定安全)。
服务器接收异步支付通知
对于App支付产生的交易,支付宝会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。
另外需要注意的:
1.必须保证服务器异步通知页面(notify_url)上无任何字符,如空格、HTML标签、开发系统自带抛出的异常提示信息等,
2.程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。
在收到支付宝的通知时需要进行验签和业务处理,验签支付宝的SDK也提供了 ,是不是很方便,
/**
* 验签
* 校验支付宝发过来的签名是否正确,使用支付宝的公钥验签
* @param request
* @return true 验签通过
*/
public static boolean signVerified(HttpServletRequest request) {
Enumeration<?> pNames = request.getParameterNames();
Map<String, String> param = new HashMap<String, String>();
try {
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
param.put(pName, request.getParameter(pName));
}
boolean signVerified = AlipaySignature.rsaCheckV1(param, Config.ALIPAY_PUBLIC_KEY, Config.CHARSET); // 校验签名是否正确
return signVerified;
} catch (Exception e) {
logger.error("============验签错误=============" + e.getMessage());
System.out.println(e.getMessage());
return false;
}
}
回调的controller 只写了一部分,仅供参考
@RequestMapping(value = "/pay/notify", method = RequestMethod.POST)
public void orderPayNotify(HttpServletRequest request, HttpServletResponse response) {
logger.info("==============收到支付宝的异步通知==========================");
// 获取到返回的所有参数 先判断是否交易成功trade_status 再做签名校验
if ("TRADE_SUCCESS".equals(request.getParameter("trade_status"))) {
try {
boolean signVerified = PayUtils.signVerified(request);
if (signVerified) {
// 验签成功后
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
// 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
// 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
// 对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,
logger.info("=====订单支付成功:====" + request.getParameterMap().toString());
//通知支付宝后台,已收到通知
response.getWriter().println("success");
} else {
// TODO 验签失败则记录异常日志,
logger.info("======验签失败=======");
}
} catch (Exception e) {
e.printStackTrace();
logger.info("========通知处理失败=========="+e.getMessage());
}
}else{
logger.info("========交易失败==========");
}
}
服务器写好了 ~