1.蚂蚁金服文档地址

文档入口:https://opendocs.alipay.com/open/270

沙箱文档入口:https://opendocs.alipay.com/open/200/105311

2.支付流程

2.1 首页开发服务中找到沙箱

2.2 页面中找到进入沙箱环境

2.3 生成RSA密钥

进入沙箱应用之后可以看到信息配置部分需要设置RSA2(SHA256)密钥,因为文档很详细了,我就不写了

具体步骤: https://opendocs.alipay.com/open/291/105971

3.支付功能移植到项目

官方Demo:https://opendocs.alipay.com/open/270/106291

官方Demo是使用jsp处理和响应请求的,有兴趣自己下载下来测试下,我这直接整合到Springboot里用Controller处理了

①加入支付功能相关依赖

参考文档:https://docs.open.alipay.com/54/103419

1
2
3
4
5
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.3.49.ALL</version>
</dependency>

②加入AlipayConfig

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
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "2016080400166558";

// 商户私钥,您的PKCS8格式RSA2私钥
public static String merchant_private_key = "====填写自己的====";

// [正式环境]支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
// [沙箱环境]支付宝公钥,查看地址:https://openhome.alipay.com/platform/appDaily.htm 信息配置->必看部分->RSA2(SHA256)密钥(推荐)->查看支付宝公钥
public static String alipay_public_key = "====填写自己的====";

// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// public static String notify_url = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";

// 使用内网穿透提供的对外暴露域名
public static String notify_url = "http://aatczu.natappfree.cc/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp";

// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
// public static String return_url = "http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";

// 使用内网穿透提供的对外暴露域名
public static String return_url = "http://aatczu.natappfree.cc/alipay.trade.page.pay-JAVA-UTF-8/return_url.jsp";

// 签名方式
public static String sign_type = "RSA2";

// 字符编码格式
public static String charset = "utf-8";

// [正式环境]支付宝网关
// public static String gatewayUrl = "https://openapi.alipay.com/gateway.do";
// [沙箱环境]支付宝网关
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";

③创建PayController

所在工程:distribution-crowd-7-webui

全类名:com.zzxx.crowd.controller.PayController

1
2
3
4
@Controller
public class PayController {

}

④处理付款请求的方法

所在类:com.zzxx.crowd.controller.PayController

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
@ResponseBody
@RequestMapping(value="/pay", produces="text/html")
public String pay(HttpServletRequest request) throws Exception {

//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);

//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);

//商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no = new String(request.getParameter("WIDout_trade_no").getBytes("ISO-8859-1"),"UTF-8");
//付款金额,必填
String total_amount = new String(request.getParameter("WIDtotal_amount").getBytes("ISO-8859-1"),"UTF-8");
//订单名称,必填
String subject = new String(request.getParameter("WIDsubject").getBytes("ISO-8859-1"),"UTF-8");
//商品描述,可空
String body = new String(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");

alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

//若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明
//alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
// + "\"total_amount\":\""+ total_amount +"\","
// + "\"subject\":\""+ subject +"\","
// + "\"body\":\""+ body +"\","
// + "\"timeout_express\":\"10m\","
// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节

//给支付宝发送请求进行支付操作
return alipayClient.pageExecute(alipayRequest).getBody();
}

⑤return方法

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
@ResponseBody
@RequestMapping("/pay/return")
public String payReturn(HttpServletRequest request) throws Exception {

// 获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> 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] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}

boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset,
AlipayConfig.sign_type); // 调用SDK验证签名

// ——请在这里编写您的程序(以下代码仅作参考)——
if (signVerified) {
// 商户订单号
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");

// 支付宝交易号
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");

// 付款金额
String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");

return "trade_no:" + trade_no + "<br/>out_trade_no:" + out_trade_no + "<br/>total_amount:" + total_amount;
} else {
return "验签失败";
}

}

⑥notify方法

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
@ResponseBody
@RequestMapping("/pay/notify")
public String payNotify(HttpServletRequest request) throws Exception {

// 获取支付宝POST过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> 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] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}

boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset,
AlipayConfig.sign_type); // 调用SDK验证签名

// ——请在这里编写您的程序(以下代码仅作参考)——

/*
* 实际验证过程建议商户务必添加以下校验: 1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额), 3、校验通知中的seller_id(或者seller_email)
* 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
* 4、验证app_id是否为该商户本身。
*/
if (signVerified) {// 验证成功
// 商户订单号
// String out_trade_no = new
// String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");

// 支付宝交易号
// String trade_no = new
// String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");

// 交易状态
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");

if (trade_status.equals("TRADE_FINISHED")) {
// 判断该笔订单是否在商户网站中已经做过处理
// 如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
// 如果有做过处理,不执行商户的业务程序

// 注意:
// 退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
} else if (trade_status.equals("TRADE_SUCCESS")) {
// 判断该笔订单是否在商户网站中已经做过处理
// 如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
// 如果有做过处理,不执行商户的业务程序

// 注意:
// 付款完成后,支付宝系统发送该交易状态通知
}

return "success";

} else {// 验证失败

return "fail";

// 调试用,写文本函数记录程序运行情况是否正常
// String sWord = AlipaySignature.getSignCheckContentV1(params);
// AlipayConfig.logResult(sWord);
}

}

⑦将AliPayConfig中的属性配置转移到yml文件

[1]@Value注解不能修饰静态资源

加载类比IOC容器初始化要早,静态资源初始化时IOC容器还没有初始化好,@Value注解的值设置不进去。

[2]解决办法

实现接口org.springframework.beans.factory.InitializingBean

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class AlipayConfig implements InitializingBean {

@Value("${alipay.app_id}")
private String appId;
//……
@Override
public void afterPropertiesSet() throws Exception {
app_id = this.appId;
//……
}