0%

支付宝模块

2018年2月27日 下午9:40

相关内容:

  1. 内网穿透
  2. 远程debug,远程部署
  3. idea配置2

学习的知识点

熟悉支付宝对接的核心文档,调通支付宝支付功能的官方Demo
解析支付宝SDK对接源码
RSA1和RSA2验证签名及加解密
避免支付宝重复通知和数据校验
natapp外网穿透和tomcat remote debug
生成二维码,并持久化到图片服务器

一些重要的官方文档

总文档:http://learning.happymmall.com/alipaydoc.html


沙箱登录:https://openhome.alipay.com/platform/appDaily.htm
沙箱环境使用说明:https://doc.open.alipay.com/doc2/detail.htm?treeId=200&articleId=105311&docType=1
如何使用沙箱环境:https://support.open.alipay.com/support/hotProblemDetail.htm?spm=a219a.7386793.0.0.uS5uZ6&id=251932&tagId=100248
当面付产品介绍:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.hV5Clx&treeId=193&articleId=105072&docType=1
扫码支付接入指引:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.Ia6Wqy&treeId=193&articleId=106078&docType=1
当面付快速接入:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.bROnXf&treeId=193&articleId=105170&docType=1
当面付接入必读:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.hV5Clx&treeId=193&articleId=105322&docType=1
当面付进阶功能:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.YFmkxI&treeId=193&articleId=105190&docType=1
当面付异步通知-仅用于扫码支付:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.BykVSR&treeId=193&articleId=103296&docType=1
当面付SDK&DEMO:https://support.open.alipay.com/docs/doc.htm?spm=a219a.7386797.0.0.k0rwWc&treeId=193&articleId=105201&docType=1
服务端SDK:https://doc.open.alipay.com/doc2/detail?treeId=54&articleId=103419&docType=1
生成RSA密钥:https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1
线上创建应用说明:https://doc.open.alipay.com/doc2/detail.htm?treeId=200&articleId=105310&docType=1#s0


支付宝扫码重要细节

  1. 主动轮训和回调的区别
  2. 避免单边账
  3. 同步请求的加签和验证签名
  4. 回调的验证
  5. 过虑重复通知
  6. 一定好验证并保证接受的异步通知是支付宝发出的
  7. 回调请求的放回

支付宝依赖的jar包:

  1. 第一部分:否则会报红线错误❌
    1. 位置:webapp_WEB-INF_lib
    2. 添加方式:
    3. 最重要的:如何将一个本地jar发布到服务器上!!!
      1. POM.xml的这个配置目的:是让项目发布的时候能够添加上alipay的jar包,否则会出现class not found的问题
      2. 关于打包发布的理解看这篇文章,很好@使用tomcat发布自己的Java项目 - CSDN博客
    4. 为啥要起lib为文件夹的名字
      1. 与项目发布后jar包所在的文件夹名相同,仅此而已,舒服
  2. 第二部分:
    1. 交给maven配置,自动添加
    2. 所在的位置POM.xml
    3. 注意:这个一定要使用配套的版本,这些jar包是和alipay的jar包相配合使用的

具体代码功能详解:

一共有三个对外接口:

  1. pay接口:返回订单号和ftp服务器中的二维码路径
  2. alipayCallback:插入payinfo信息,使alipay不再回调
  3. queryOrderPayStatus:判断订单状态

具体的看代码,注释我写的很详细

  1. OrderController.java
  2. OrderServiceImpl.java
    OrderController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    @RequestMapping("pay.do")
    @ResponseBody
    //session:判断用户类型 orderNo:订单号 request:获取文件夹路径
    //最后的返回:得到一个responseService,其中的data类型是Map<String ,String> resultMap 其中保存这订单号和ftp服务器中的二维码路径
    public ServerResponse pay(HttpSession session, Long orderNo, HttpServletRequest request){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user ==null){
    return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc());
    }
    //获取文件夹的上传路径。表示获得服务器的绝对路径的意思。但是最终我们要从服务器上传到FTP中去 blog.csdn.net/inite/article/details/75577970
    String path = request.getSession().getServletContext().getRealPath("upload");
    return iOrderService.pay(orderNo,user.getId(),path);
    }

    @RequestMapping("alipay_callback.do")
    @ResponseBody
    /*
    这个是alipay服务器调用的回调接口,不是我们自己调用的
    1.让我们在payinfo数据表中存入付款信息
    2.alipay服务器根据返回的结果,判断还用不用接着回调了
    request来取得alipay的回调过程中传给我们的参数
    整个过程参考:https://docs.open.alipay.com/194/103296
    */
    public Object alipayCallback(HttpServletRequest request){
    Map<String,String> params = Maps.newHashMap();

    Map requestParams = request.getParameterMap();
    for(Iterator iter = requestParams.keySet().iterator();iter.hasNext();){
    String name = (String)iter.next();
    String[] values = (String[]) requestParams.get(name);
    String valueStr = "";
    for(int i = 0 ; i <values.length;i++){

    valueStr = (i == values.length -1)?valueStr + values[i]:valueStr + values[i]+",";
    }
    params.put(name,valueStr);
    }
    logger.info("支付宝回调,sign:{},trade_status:{},参数:{}",params.get("sign"),params.get("trade_status"),params.toString());

    //非常重要,验证回调的正确性,是不是支付宝发的.并且呢还要避免重复通知.

    params.remove("sign_type");
    try {
    boolean alipayRSACheckedV2 = AlipaySignature.rsaCheckV2(params, Configs.getAlipayPublicKey(),"utf-8",Configs.getSignType());

    if(!alipayRSACheckedV2){
    return ServerResponse.createByErrorMessage("非法请求,验证不通过,再恶意请求我就报警找网警了");
    }
    } catch (AlipayApiException e) {
    logger.error("支付宝验证回调异常",e);
    }

    //todo 验证各种数据


    //在这个函数中处理了:过虑重复通知 然后将payinfo数据表需要的数据保存在payinfo中,插入到数据库中
    ServerResponse serverResponse = iOrderService.aliCallback(params);
    if(serverResponse.isSuccess()){
    return Const.AlipayCallback.RESPONSE_SUCCESS;
    }
    return Const.AlipayCallback.RESPONSE_FAILED;
    }

    //查询订单状态
    @RequestMapping("query_order_pay_status.do")
    @ResponseBody
    public ServerResponse<Boolean> queryOrderPayStatus(HttpSession session, Long orderNo){
    User user = (User)session.getAttribute(Const.CURRENT_USER);
    if(user ==null){
    return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc());
    }

    ServerResponse serverResponse = iOrderService.queryOrderPayStatus(user.getId(),orderNo);
    if(serverResponse.isSuccess()){
    return ServerResponse.createBySuccess(true);
    }
    return ServerResponse.createBySuccess(false);
    }

OrderServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

//输入:orderNo订单号 userId从session中获取的用户id path二维码的上传路径文件夹
//输出:得到一个responseService,其中的data类型是Map<String ,String> resultMap 其中保存这订单号和ftp服务器中的二维码路径
public ServerResponse pay(Long orderNo,Integer userId,String path){
//responseService中的data
Map<String ,String> resultMap = Maps.newHashMap();
//验证orderNo并放入resultMap中
Order order = orderMapper.selectByUserIdAndOrderNo(userId,orderNo);
if(order == null){
return ServerResponse.createByErrorMessage("用户没有该订单");
}
resultMap.put("orderNo",String.valueOf(order.getOrderNo()));

/*
下面这段是从alipay的demo中摘抄的内容:
我们做了以下工作:
1.拼凑请求alipay服务器的各种参数
2.创建扫码支付请求builder,设置请求参数
3.对alipay进行访问,然后取得返回结果
*/

// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
// 需保证商户系统端不能重复,建议通过数据库sequence生成,
String outTradeNo = order.getOrderNo().toString();

// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
String subject = new StringBuilder().append("happymmall扫码支付,订单号:").append(outTradeNo).toString();

// (必填) 订单总金额,单位为元,不能超过1亿元
// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
String totalAmount = order.getPayment().toString();

// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
String undiscountableAmount = "0";

// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
String sellerId = "";

// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
String body = new StringBuilder().append("订单").append(outTradeNo).append("购买商品共").append(totalAmount).append("元").toString();

// 商户操作员编号,添加此参数可以为商户操作员做销售统计
String operatorId = "test_operator_id";

// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
String storeId = "test_store_id";

// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
ExtendParams extendParams = new ExtendParams();
extendParams.setSysServiceProviderId("2088100200300400500");

// 支付超时,定义为120分钟
String timeoutExpress = "120m";

// 商品明细列表,需填写购买商品详细信息,
List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();

List<OrderItem> orderItemList = orderItemMapper.getByOrderNoUserId(orderNo,userId);
for(OrderItem orderItem : orderItemList){
GoodsDetail goods = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(),
BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(),new Double(100).doubleValue()).longValue(),
orderItem.getQuantity());
goodsDetailList.add(goods);
}

// 创建扫码支付请求builder,设置请求参数
AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
.setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
.setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
.setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
.setTimeoutExpress(timeoutExpress)
.setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
.setGoodsDetailList(goodsDetailList);

//对alipay进行访问,然后取得返回结果
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
case SUCCESS:
logger.info("支付宝预下单成功: )");

AlipayTradePrecreateResponse response = result.getResponse();
dumpResponse(response);

File folder = new File(path);
if(!folder.exists()){
folder.setWritable(true);
folder.mkdirs();
}

//需要修改为运行机器上的路径
//细节细节细节
String qrPath = String.format(path+"/qr-%s.png",response.getOutTradeNo());
String qrFileName = String.format("qr-%s.png",response.getOutTradeNo());
ZxingUtils.getQRCodeImge(response.getQrCode(), 256, qrPath);
//上一句将二维码就已经将二维码保存到path位置了,名字也有了,那么现在我们用path+ qrFileName就可以将这个文件取出来保存,然后通过FTP工具类上传到FTP中去
File targetFile = new File(path,qrFileName);
try {
FTPUtil.uploadFile(Lists.newArrayList(targetFile));
} catch (IOException e) {
logger.error("上传二维码异常",e);
}
logger.info("qrPath:" + qrPath);
String qrUrl = PropertiesUtil.getProperty("ftp.server.http.prefix")+targetFile.getName();
resultMap.put("qrUrl",qrUrl);
return ServerResponse.createBySuccess(resultMap);
case FAILED:
logger.error("支付宝预下单失败!!!");
return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");

case UNKNOWN:
logger.error("系统异常,预下单状态未知!!!");
return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");

default:
logger.error("不支持的交易状态,交易返回异常!!!");
return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");
}
}

// 简单打印应答
private void dumpResponse(AlipayResponse response) {
if (response != null) {
logger.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
if (StringUtils.isNotEmpty(response.getSubCode())) {
logger.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
response.getSubMsg()));
}
logger.info("body:" + response.getBody());
}
}

//在这个函数中处理了:过虑重复通知 然后将payinfo数据表需要的数据保存在payinfo中,插入到数据库中
public ServerResponse aliCallback(Map<String,String> params){
Long orderNo = Long.parseLong(params.get("out_trade_no"));
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
Order order = orderMapper.selectByOrderNo(orderNo);
if(order == null){
return ServerResponse.createByErrorMessage("非快乐慕商城的订单,回调忽略");
}
if(order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()){
return ServerResponse.createBySuccess("支付宝重复调用");
}
if(Const.AlipayCallback.TRADE_STATUS_TRADE_SUCCESS.equals(tradeStatus)){
order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment")));
order.setStatus(Const.OrderStatusEnum.PAID.getCode());
orderMapper.updateByPrimaryKeySelective(order);
}

PayInfo payInfo = new PayInfo();
payInfo.setUserId(order.getUserId());
payInfo.setOrderNo(order.getOrderNo());
payInfo.setPayPlatform(Const.PayPlatformEnum.ALIPAY.getCode());
payInfo.setPlatformNumber(tradeNo);
payInfo.setPlatformStatus(tradeStatus);

payInfoMapper.insert(payInfo);

return ServerResponse.createBySuccess();
}


public ServerResponse queryOrderPayStatus(Integer userId,Long orderNo){
Order order = orderMapper.selectByUserIdAndOrderNo(userId,orderNo);
if(order == null){
return ServerResponse.createByErrorMessage("用户没有该订单");
}
if(order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()){
return ServerResponse.createBySuccess();
}
return ServerResponse.createByError();
}