沙箱环境集成支付宝APP支付后端实现
前言
如今生活中无时无刻的在使用者支付功能,最常见的支付平台"支付宝""微信"今天就来介绍一下java后端如何集成支付宝应用(使用沙箱环境 无需企业认证)
获取支付宝相关信息
整体开发流程
1、获取核心参数
- APPID (商户appid是识别商户的唯一ID,是让支付宝识别,我们到底是哪一个商户,这样支付宝就能识别商户对应的账号、用户号、收款账号…等等一系列信息。)
- 商家公钥、私钥(对商户系统与支付宝进行信息交互的数字签名用的)
- 支付宝公钥
- 支付宝回调地址(是用来提供给支付宝调用地址)
- 网关地址 (是用来配置发送给支付宝的网关地址的)
- 加密签名算法RSA2
获取应用公钥、应用私钥
在沙箱应用中使用之前获取的应用公钥换取支付宝公钥(用于后续开发)
2、将支付宝SDK集成到项目中
支付宝将与服务端交互的接口(OpenAPI)封装在开发工具包(SDK)中,开发者无需自行实现同服务端交互的复杂逻辑,直接将 SDK 导入自己的工程后,通过 OpenAPI 的示例代码实现同支付宝服务端的交互。支付宝javaSDK下载地址
如果是采用maven 可直接将sdk 导入到pom文件中
<!-- alipay sdk 支付依赖 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.13.0.ALL</version>
</dependency>
<!--依赖支付的空判断-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
3、后台使用支付宝SDK与支付宝进行交互逻辑
下面这段话是参考其他博主描述参考文档地址
首先,我们来理一理开发的思路,按照我当前项目的需求,关于支付这一块大概操作流程是:用户在APP上选好要购买的商品,点击“立即购买”,跳转到订单详细页面。选择支付方式,点击“确定支付”跳转到支付宝APP,付款完成后,跳转回APP,完成支付。这个过程,当用户点击“确定支付”时,APP需要调用商户后台接口。
这时候就是我们所需要做的事情:先是生成商户系统一笔未支付的订单,获得商户订单ID(商户系统生成)和订单的一些其他信息,然后再调用支付宝的SDK提供的数字签名方法,将需要传给支付宝的信息进行加签,然后把加签后的字符串返回给APP。APP拉起支付宝APP,再把这个加签的字符串传给支付宝,完成支付。APP接收到同步通知后,还需要再次调用商户后台的接口(虽然同步通知也有付款情况,但需要以后台通知为准),校验订单最终的付款情况。按照支付宝API上所说,当完成支付后,支付宝会做2个操作,一个是同步返回信息给APP,一个是异步通知商户后台返回支付状态等信息,并且最终的支付结果是以异步通知为准。所以我们还需要考虑到一点,就是当用户支付成功之后,商户系统暂时没有接收到支付宝的异步通知时。我们需要拿着这个商户订单ID主动调用SDK支付宝的查询接口,去获取该订单的支付情况,并最终返回给APP。这个查询的接口应该是给APP收到同步通知后,请求商户系统后台进行校验的时候调用的。
根据我们上面思考所得,后台只需要对外提供3个接口即可
1.用户点击“立即购买”时调用商户后台接口,后台返回加签后的订单信息字符串
2.在支付完成之后,支付宝异步通知商户后台订单的付款情况
3.在支付完成之后,跳转回APP时,APP调用商户后台进行最终付款校验
下面开始编写代码
1、将支付宝公共信息参数写入类中
public class AlipayConfig {
// 1.商户appid
public static String APPID = "2021xxxx693113";
// 2.私钥 pkcs8格式的
public static String RSA_PRIVATE_KEY = "MIIE...";
// 3.支付宝公钥
public static String ALIPAY_PUBLIC_KEY = "MIIB...";
// 4.服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参 数,必须外网可以正常访问
public static String notify_url = "http://xxx:8080/Health/alipay/notify_url.do";
// 5.页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义 参数,必须外网可以正常访问
// 商户可以自定义同步跳转地址
public static String return_url = "http://xxx:8080/Health/alipay/return_url.do";
// 6.请求支付宝的网关地址
public static String URL = "https://openapi.alipaydev.com/gateway.do";
// 7.编码
public static String CHARSET = "UTF-8";
// 8.返回格式
public static String FORMAT = "json";
// 9.加密类型
public static String SIGNTYPE = "RSA2";
}
2、实现第一个接口:用户点击“立即购买”时调用商户后台接口,后台返回加签后的订单信息字符串。
写一个生成订单号的类 每次有交易让程序自动生成一个OutTradeNo
public class GenerateNum {
// 全局自增数
private static int count = 0;
// 每毫秒秒最多生成多少订单(最好是像9999这种准备进位的值)
private static final int total = 99;
// 格式化的时间字符串
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
// 获取当前时间年月日时分秒毫秒字符串
private static String getNowDateStr() {
return sdf.format(new Date());
}
// 记录上一次的时间,用来判断是否需要递增全局数
private static String now = null;
/*
生成一个订单号
*/
public static String generateOrder() {
String datastr = getNowDateStr();
if (datastr.equals(now)) {
count++;// 自增
} else {
count = 1;
now = datastr;
}
int countInteger = String.valueOf(total).length() - String.valueOf(count).length();// 算补位
String bu = "";// 补字符串
for (int i = 0; i < countInteger; i++) {
bu += "0";
}
bu += String.valueOf(count);
if (count >= total) {
count = 0;
}
return datastr + bu;
}
}
Controller层 大家可以更具自己的具体业务来编写
/**
* 获取支付宝加签后台的订单信息字符串
* @param request
* @param response
* @return
*/
@RequestMapping(value = "getOrderInfo.do", produces = "application/json,charset=UTF-8", method = RequestMethod.POST)
@ResponseBody
public String getOrderInfo(HttpServletRequest request, HttpServletResponse response) {
int userid = Integer.valueOf(request.getParameter("userid"));
String totalAmount = request.getParameter("totalAmount");
int effectiveDay = Integer.valueOf(request.getParameter("effectiveDay"));
PayDto payDto = new PayDto();
payDto.setEffectiveDay(effectiveDay);
payDto.setOutTradeNo(GenerateNum.generateOrder());
payDto.setUserid(userid);
payDto.setTotalAmount(totalAmount);
return alipayService.alipaytest(payDto);
}
Service层
@Override
public String alipaytest(PayDto payDto) {
String orderInfo = "";
String orderNumber = GenerateNum.generateOrder();
// 创建支付宝订单
AlipaymentOrder alipaymentOrder = new AlipaymentOrder();
alipaymentOrder.setUserid(payDto.getUserid());// 订单用户id
alipaymentOrder.setEffectiveDay(payDto.getEffectiveDay());// 获取有效天数
alipaymentOrder.setOutTradeNo(payDto.getOutTradeNo());// 后台生成的订单号
alipaymentOrder.setTradeStatus(0);// 交易状态
alipaymentOrder.setTotalAmount(Double.parseDouble(payDto.getTotalAmount()));// 订单总金额
alipaymentOrder.setReceiptAmount(0.00);// 实收金额
alipaymentOrder.setInvoiceAmount(0.00);// 开票金额
alipaymentOrder.setBuyerPayAmount(0.00);// 付款金额
alipaymentOrder.setRefundFee(0.00); // 总退款金额
try {
// 实例化客户端
AlipayClient alipayClient = getAlipayClient();
// new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID,
// AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET,
// AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.SIGNTYPE);
// 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
// SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("我是测试数据");
model.setSubject("App支付测试Java");// 商品名称
model.setOutTradeNo(payDto.getOutTradeNo());// 交易订单号
model.setTimeoutExpress("30m");// 交易超时时间
model.setTotalAmount(payDto.getTotalAmount());// 支付金额
model.setProductCode("QUICK_MSECURITY_PAY");// 销售产品码(固定值)
request.setBizModel(model);
request.setNotifyUrl(AlipayConfig.notify_url);// 异步回调地址
request.setReturnUrl(AlipayConfig.return_url);// 同步回调地址
// 这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
orderInfo = response.getBody();
System.out.println(response.getBody());// 就是orderString 可以直接给客户端请求,无需再做处理。
// 创建新的商户支付宝订单
this.alipayDao.createAlipayMentOrder(alipaymentOrder);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return orderInfo;
}
到此 我们的第一接口 后台返回加签后的字符串已经完成
3、实现第二个接口在支付完成之后,支付宝异步通知商户后台订单的付款情况
/**
* 支付宝支付成功后,异步请求该参数
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "notify_url.do", produces = "application/json,charset=UTF-8", method = RequestMethod.POST)
@ResponseBody
public String notify_url(HttpServletRequest request, HttpServletResponse response) {
// 1.从支付宝回调的request域中取值
// 获取支付宝返回的参数集合
Map<String, String[]> aliParams = request.getParameterMap();
// 用以存放转化后的参数集合
Map<String, String> conversionParams = new HashMap<String, String>();
for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext();) {
String key = iter.next();
String[] values = aliParams.get(key);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "uft-8");
conversionParams.put(key, valueStr);
}
// logger.info("==================返回参数集合:" + conversionParams);
String status = alipayService.notify(conversionParams);
return status;
}
Service层
/**
* 支付宝异步请求逻辑
*/
@Override
public String notify(Map<String, String> conversionParams) {
// 签名验证(对支付宝返回的数据验证,确定是支付宝返回的)
boolean signVerified = false;
try {
// 调用SDK验证签名
signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY,
AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (signVerified) {
// 验签通过
// 获取需要保存的数据
String appId = conversionParams.get("app_id");// 支付宝分配给开发者的应用Id
String notifyTime = conversionParams.get("notify_time");// 通知时间:yyyy-MM-dd HH:mm:ss
String gmtCreate = conversionParams.get("gmt_create");// 交易创建时间:yyyy-MM-dd HH:mm:ss
String gmtPayment = conversionParams.get("gmt_payment");// 交易付款时间
String gmtRefund = conversionParams.get("gmt_refund");// 交易退款时间
String gmtClose = conversionParams.get("gmt_close");// 交易结束时间
String tradeNo = conversionParams.get("trade_no");// 支付宝的交易号
String outTradeNo = conversionParams.get("out_trade_no");// 获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
String outBizNo = conversionParams.get("out_biz_no");// 商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
String buyerLogonId = conversionParams.get("buyer_logon_id");// 买家支付宝账号
String sellerId = conversionParams.get("seller_id");// 卖家支付宝用户号
String sellerEmail = conversionParams.get("seller_email");// 卖家支付宝账号
Double totalAmount = Double.parseDouble(conversionParams.get("total_amount"));// 订单金额:本次交易支付的订单金额,单位为人民币(元)
String receiptAmount = conversionParams.get("receipt_amount");// 实收金额:商家在交易中实际收到的款项,单位为元
String invoiceAmount = conversionParams.get("invoice_amount");// 开票金额:用户在交易中支付的可开发票的金额
String buyerPayAmount = conversionParams.get("buyer_pay_amount");// 付款金额:用户在交易中支付的金额
String tradeStatus = conversionParams.get("trade_status");// 获取交易状态
// 支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
AlipaymentOrder alipaymentOrder = alipayDao.selectByOutTradeNo(outTradeNo);
if (alipaymentOrder != null && totalAmount.equals(alipaymentOrder.getTotalAmount())
&& AlipayConfig.APPID.equals(appId)) {
// 修改数据库支付宝订单表(因为要保存每次支付宝返回的信息到数据库里,以便以后查证)
try {
System.out.println("yyyyyyyy" + notifyTime);
alipaymentOrder.setNotifyTime(dateformat(notifyTime));
alipaymentOrder.setGmtCreate(dateformat(gmtCreate));
alipaymentOrder.setGmtPayment(dateformat(gmtPayment));
alipaymentOrder.setGmtRefund(dateformat(gmtRefund));
alipaymentOrder.setGmtClose(dateformat(gmtClose));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
alipaymentOrder.setTradeNo(tradeNo);
alipaymentOrder.setOutBizNo(outBizNo);
alipaymentOrder.setBuyerLogonId(buyerLogonId);
alipaymentOrder.setSellerId(sellerId);
alipaymentOrder.setSellerEmail(sellerEmail);
alipaymentOrder.setTotalAmount(totalAmount);
alipaymentOrder.setReceiptAmount(Double.parseDouble(receiptAmount));
alipaymentOrder.setInvoiceAmount(Double.parseDouble(invoiceAmount));
alipaymentOrder.setBuyerPayAmount(Double.parseDouble(buyerPayAmount));
switch (tradeStatus) // 判断交易结果
{
case "TRADE_FINISHED": // 交易结束并不可退款
alipaymentOrder.setTradeStatus((byte) 3);
break;
case "TRADE_SUCCESS": // 交易支付成功
alipaymentOrder.setTradeStatus((byte) 2);
break;
case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
alipaymentOrder.setTradeStatus((byte) 1);
break;
case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
alipaymentOrder.setTradeStatus((byte) 0);
break;
default:
break;
}
int returnResult = this.alipayDao.updateAlipayMentOrder(alipaymentOrder);
if (tradeStatus.equals("TRADE_SUCCESS")) { // 只处理支付成功的订单: 修改交易表状态,支付成功
if (returnResult > 0) {
return "success";
} else {
return "fail";
}
} else {
return "fail";
}
} else {
return "fail";
}
} else { // 验签不通过
return "fail";
}
}
4、第三个接口在支付完成之后,跳转回APP时,APP调用商户后台进行最终付款校验。
/**
* 向支付宝发起订单查询
*/
@Override
public int checkAlipay(String outTradeno) {
String responseString = "";
AlipayClient alipayClient = getAlipayClient();
Map<String,String> paraMap = new HashMap<String,String>();
paraMap.put("out_trade_no",outTradeno);//商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
paraMap.put("trade_no",null);// trade_no 即便是空你也得写上 不能省略 !!!!!!!!!!!
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();// 创建API对应的request类
request.setBizContent(JSON.toJSONString(paraMap));
AlipayTradeQueryResponse alipayTradeQueryResponse;
try {
alipayTradeQueryResponse = alipayClient.execute(request);
responseString = alipayTradeQueryResponse.getBody();
System.out.println(responseString);
if (alipayTradeQueryResponse.isSuccess()) {
AlipaymentOrder alipaymentOrder = alipayDao.selectByOutTradeNo(outTradeno);
// 修改数据库支付宝订单表
alipaymentOrder.setTradeNo(alipayTradeQueryResponse.getTradeNo());
alipaymentOrder.setBuyerLogonId(alipayTradeQueryResponse.getBuyerLogonId());
alipaymentOrder.setTotalAmount(Double.parseDouble(alipayTradeQueryResponse.getTotalAmount()));
alipaymentOrder.setReceiptAmount(Double.parseDouble(alipayTradeQueryResponse.getReceiptAmount()));
alipaymentOrder.setInvoiceAmount(Double.parseDouble(alipayTradeQueryResponse.getInvoiceAmount()));
alipaymentOrder.setBuyerPayAmount(Double.parseDouble(alipayTradeQueryResponse.getBuyerPayAmount()));
switch (alipayTradeQueryResponse.getTradeStatus()) // 判断交易结果
{
case "TRADE_FINISHED": // 交易结束并不可退款
alipaymentOrder.setTradeStatus((byte) 3);
break;
case "TRADE_SUCCESS": // 交易支付成功
alipaymentOrder.setTradeStatus((byte) 2);
break;
case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
alipaymentOrder.setTradeStatus((byte) 1);
break;
case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
alipaymentOrder.setTradeStatus((byte) 0);
break;
default:
break;
}
this.alipayDao.updateAlipayMentOrder(alipaymentOrder); // 更新表记录
return alipaymentOrder.getTradeStatus();
} else {
}
} catch (AlipayApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // 通过alipayClient调用API,获得对应的response类
return 0;
}
到此 我们的三个接口已近全部完成 是不是没有想象中的那么复杂呢
后台集成支付宝支付遇到的坑
在通过订单号查询交易记录的时候我们穿了两个值 一个是outTradeNo(后台生成) 还有一个为TradeNo(支付宝生成)
本以为两个二选一即可 但是测试多边后 即使是trade_no传入null也是要传入 不然会报40004(业务处理失败)
Map<String,String> paraMap = new HashMap<String,String>();
paraMap.put("out_trade_no",outTradeno);//商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
paraMap.put("trade_no",null);// trade_no 即便是空你也得写上 不能省略 !!!!!!!!!!!
提醒使用沙箱环境进行测试的时候 必须使用沙箱环境给我们的账户进行测试
好啦 今天的分享就到这里 本人小白一个 如有问题请多多指教
QQ:2289091393 (请注明来意)