本章是跟支付宝进行签约对接商户服务端(也就是自行开发的JAVA后端),做此记录。
文献基本都来源于支付宝,详情请看支付宝官方文档:APP支付
目录
系统交互图
服务端demo
配置参数
获取APPID
获取公密钥
使用demo
获取签名后的订单信息
验签
先来查看下主要系统交互图,方便后面的流程梳理。
文档位置:https://docs.open.alipay.com/204/105297/ 的快速接入,第四步:调用接口
看到这里,可以清楚的知道,java后端并不需要进行支付操作,只负责生成签名后的订单信息和最终验签,并最后异步接收支付通知。
下载 服务端mode 依赖包
文档地址:https://docs.open.alipay.com/54/106370/
我这JAVA MAVEN项目 , 直接引入依赖即可
com.alipay.sdk
alipay-sdk-java
4.8.10.ALL
服务端demo 文档地址:https://docs.open.alipay.com/54/106370/
代码块文档内有展示,这里就不显示了,(简易固定参数不做说明)主要展示咱们需要自助获取的参数:
先登录到支付宝开放平台,这里的实名认证,绑定手机号等操作这里就不讲解了。
如下图进行3步进入创建应用界面
创建完成之后,就获取到咱们要用的应用APPID
阿里文档链接参考:
第一步:生成 RSA 密钥(这里选择公钥证书方式)
第二步:上传应用公钥并获取支付宝公钥
上传完成后,可在设置页面下载到 应用公钥证书,支付宝公钥证书,支付宝根证书
使用证书的方式,直接查看demo就可以。
查看demo其中的一句
//SDK已经封装掉了公共参数,这里只需要传入业务参数。
//以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
该业务对象已经帮我们封装好了,也够用了,demo展示的set只是其中几个参数,其他可以继续赋值。
使用demo提供的AlipayTradeAppPayRequest好处是,自行帮我们做好了sign签名处理,我们无需再自己签名。
麻烦的问题在于证书路径的读取。如下标识:
//设置应用公钥证书路径
certAlipayRequest.setCertPath(app_cert_path);
//设置支付宝公钥证书路径
certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path);
//设置支付宝根证书路径
certAlipayRequest.setRootCertPath(alipay_root_cert_path);
如果项目是war,那还好,这三个参数直接搜索项目根路径比较简单,但如果是springboot打包成jar项目的启动方式的话,就比较困难。jar包如果想读取内部配置文件,只能通过流来读取,网上搜索的读取配置文件大多是InputStream,可我们要的是文件的路径而不是内容。在开发测试的时候,Linux环境只能读取绝对路径,Window可以读取相对路径。所以做了两个版本的路径切换。下面展示下效果。
// Linux文件绝对路径 --- ${jar包所在“根”路径}/crt/
// String outpath = System.getProperty("user.dir") + File.separator + "crt" + File.separator;
// window文件相对路径
String outpath = this.getClass().getResource("/").getPath()
// 设置应用公钥证书路径
// linux getPath: ${jar包所在“根”路径}/crt/appCertPublicKey_2019102168481752.crt,其他两个相同
// window getPath: G:\git\leopard-console\target\classes\appCertPublicKey_2019102168481752.crt
File appCertfile = new File(outpath + "appCertPublicKey_2019102168481752.crt");
if (appCertfile == null || appCertfile.getPath() == null) {
logger.error("------/crt/------找不到应用公钥证书");
return null;
}
// 设置应用公钥证书路径
certAlipayRequest.setCertPath(appCertfile.getPath());
本地window测试还方便,如果是linux记得切换路径,实例里面有个 “crt“ 文件夹,是我创建后将文件放进去的。
如果是通过dockerfile来构建docker读取配置。相应配置如下:
FROM java:7
VOLUME /tmp
ADD leopard-console*.jar /leopard-console.jar
ADD classes/alipayCertPublicKey_RSA2.crt /crt/alipayCertPublicKey_RSA2.crt
ADD classes/alipayRootCert.crt /crt/alipayRootCert.crt
ADD classes/appCertPublicKey_2019102168481752.crt /crt/appCertPublicKey_2019102168481752.crt
RUN bash -c 'touch /leopard-console.jar'
EXPOSE 8080
CMD ["java", "-jar","leopard-console.jar"]
将jar编译的配置文件,创建到 /crt 目录下,代码不变,就可以引用到。
自建的 JAR 项目地址:https://github.com/leopardF/alipay , 有需要参考的可自行下载。
该JAR以嵌入依赖为主,等下测试时候会看到引用。
开始测试请求,引入自建项目依赖
com.pay
alipay
0.0.1
编写测试代码:
/**
* 请求生成签名信息
*
* @return
*/
@RequestMapping(value = "/getOrderInfoSign")
public Message getOrderInfoSign() {
AliAppPayResponseCode appPayRequest = new AliAppPayRequest().appPayRequest(30, "测试-model", "预约3天,详情如下。。。",
"0.01", "201910221548751212", "paramUrl;paramUrl2", "http://cwfpx6.natappfree.cc/verifyOrderInfoSign");
return new Message("200", "aa", appPayRequest.alias());
}
浏览器输入: localhost:8080/getOrderInfoSign , 成功获取签名验证信息。
data就是移动端需要的签名信息。
我们再重新看下系统流程图:
第2、3步我们已经完成。
接下来开始我们的验签。
直接上代码:
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.alipay.AliAppPayRequest;
import com.alipay.config.AlipayConfig;
import com.alipay.enums.AliAppPayResponseCode;
import com.alipay.enums.AliPayPublicCode;
import com.leopard.util.bean.Message;
import com.leopard.util.enums.SystemCodeAndMsg;
/**
* ali支付
*/
@RestController
@RequestMapping(value = "/alipay")
public class AliPayAction {
private static final Logger logger = LoggerFactory.getLogger(AliPayAction.class);
/**
* 请求生成订单签名信息
*
* @param subject
* 标题
* @param body
* 内容
* @param totalAmount
* 金额
* @param passbackParams
* 附加参数
* @return
*/
@RequestMapping(value = "/getModelOrderInfoSign", method = RequestMethod.POST)
public Message getModelOrderInfoSign(@RequestParam(value = "subject") String subject,
@RequestParam(value = "body") String body, @RequestParam(value = "totalAmount") String totalAmount,
@RequestParam(value = "passbackParams") String passbackParams) {
AliAppPayResponseCode appPayRequest = new AliAppPayRequest().appPayRequest(30, subject, body, totalAmount,
"201910221548751212", passbackParams, "http://cwfpx6.natappfree.cc/verifyOrderInfoSign");
// AliAppPayResponseCode appPayRequest = new
// AliAppPayRequest().appPayRequest(30, "测试-model", "预约3天,详情如下。。。",
// "0.01", "201910221548751212", "paramUrl;paramUrl2",
// "http://cwfpx6.natappfree.cc/verifyOrderInfoSign");
return new Message("200", "aa", appPayRequest.alias());
}
/**
* 同步获取前端成功返回结果
*
* @param result
* 支付成功后的result的json串
* @return
*/
@RequestMapping(value = "/syncNotifyValidate", method = RequestMethod.POST)
public Message syncNotifyValidate(@RequestParam(value = "result") String result) {
JSONObject resultObject = JSONObject.parseObject(result);
String alipayTradeAppPayResponse = resultObject.getString("alipay_trade_app_pay_response");
if (StringUtils.isBlank(alipayTradeAppPayResponse)) {
return new Message(SystemCodeAndMsg.FAIL.code(), "响应参数不存在");
}
String sign = resultObject.getString("sign");
if (StringUtils.isBlank(sign)) {
return new Message(SystemCodeAndMsg.FAIL.code(), "验签信息不能为空");
}
String signType = resultObject.getString("sign_type");
if (!AlipayConfig.sign_type.equals(signType)) {
return new Message(SystemCodeAndMsg.FAIL.code(), "签名类型不匹配");
}
JSONObject responseObject = JSONObject.parseObject(alipayTradeAppPayResponse);
if (!AliPayPublicCode.Success.code().equals(responseObject.getString("code"))) {
logger.info("---syncNotifyValidate-----responseObject:" + responseObject.toString());
return new Message(responseObject.getString("code"), responseObject.getString("msg"));
}
// 以下验证业务逻辑需补充
Message publicValidateBaseInfo = publicValidateBaseInfo(responseObject);
if(!SystemCodeAndMsg.SUCCESS.code().equals(publicValidateBaseInfo.getCode())){
//验证失败
return publicValidateBaseInfo;
}
// 验证通过,买家付款成功,将信息记录到数据库
// 业务代码
// 反馈给前端
return new Message(SystemCodeAndMsg.SUCCESS.code(), SystemCodeAndMsg.SUCCESS.msg());
}
/**
* 异步获取支付宝POST通知。 此地址是一开始生成订单传入的异步地址,请保留好路径
*
* @param request
* @return
*/
@RequestMapping(value = "/asynNotifyValidate", method = RequestMethod.POST)
public String asynNotifyValidate(HttpServletRequest request) {
// 支付宝提供的验签方法,已经封装到依赖包,请求调用就行
AliAppPayResponseCode verifyOrderInfoSign = new AliAppPayRequest().verifyOrderInfoSign(request);
if (!AliAppPayResponseCode.SUCCESS.code().equals(verifyOrderInfoSign.code())) {
return "failure";
}
// 以下验证业务逻辑需补充
// 已经将验证过程中的订单信息和响应信息放入到alias内回传回来
JSONObject verifyOrderInfoJson = JSONObject.parseObject(verifyOrderInfoSign.alias());
Message publicValidateBaseInfo = publicValidateBaseInfo(verifyOrderInfoJson);
if(!SystemCodeAndMsg.SUCCESS.code().equals(publicValidateBaseInfo.getCode())){
//验证失败
return "failure";
}
//验证通过,开始业务代码,并标记订单支付成功,该信息可覆盖同步回调信息,较为准确
//最好将回调信息都保存在数据库做记录
//verifyOrderInfoJson已经保存好回调数据,直接放入数据库用保存即可
return "success";
}
/**
* 支付宝公用回调信息验签
*
* @param jsonObject
* @return
*/
private Message publicValidateBaseInfo(JSONObject jsonObject) {
// 1、商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号;
String outTradeNo = jsonObject.getString("out_trade_no");
// 业务查找订单号对应的信息,并进行匹配验证
// 2、判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额);
String totalAmount = jsonObject.getString("total_amount");
// 根据1获取到的信息,进行匹配验证
// 3、校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no
// 这笔单据对应的操作方(有的时候,一个商户可能有多个 seller_id/seller_email);
String sellerId = jsonObject.getString("seller_id");
if (!AliAppPayRequest.verifySellerId(sellerId)) {
logger.info("---publicValidateBaseInfo-----verifySellerId:" + jsonObject.toString());
return new Message(SystemCodeAndMsg.FAIL.code(), "验证信息有误");
}
// 4、验证 app_id
// 是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明同步校验结果是无效的,只有全部验证通过后,才可以认定买家付款成功。
String appId = jsonObject.getString("app_id");
if (!AliAppPayRequest.verifyAppId(appId)) {
logger.info("---publicValidateBaseInfo-----verifyAppId:" + jsonObject.toString());
return new Message(SystemCodeAndMsg.FAIL.code(), "验证信息有误");
}
return new Message(SystemCodeAndMsg.SUCCESS.code(), SystemCodeAndMsg.SUCCESS.msg());
}
}
这是自己写的验签demo,可直接更改使用到自己的业务内,其中的Message是自己封装的相应格式,这里就不罗列了。
验签请求可参考支付宝技术文档:
第四步:使用支付宝公钥验签
App支付服务端 DEMO & SDK
服务端:通知参数说明
代码已经将系统流程图的,9、10、12、13步完成。
联调测试,请自行跟移动端联调对接。
调用支付宝的联调日志查询地址为:https://openmonitor.alipay.com/acceptance/cloudparse.htm