记录:前面写了微信支付,现在完成支付宝支付,记录实现步骤和自己的理解。
借鉴:https://cloud.tencent.com/developer/article/1448209
一、沙箱环境测试,把应用密钥,应用公钥 ,支付宝公钥保存好备用。(怎么来的百度一下,多得很)
二、代码实现
2.1导入依赖包
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.7.4.ALL</version>
</dependency>
2.2在resources下面新建一个alipay.properties文件
# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
appId: 2016110300790037
# 商户私钥,您的PKCS8格式RSA2私钥
privateKey: ......
# 支付宝公钥,查看地址:https://openhome.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
publicKey: ......
# 服务器异步通知页面路径需http://格式的完整路径。异步的后端接口,这里用的natapp做一个内网映射。
notifyUrl:http://dj29ri.natappfree.cc/order/notifyUrl
# 页面跳转同步通知页面路径 需http://格式的完整路径,没得前端页面地址就返回的后面项目index页面。
returnUrl: http://dj29ri.natappfree.cc
# 签名方式
signType: RSA2
# 字符编码格式
charset: utf-8
# 支付宝网关
gatewayUrl: https://openapi.alipaydev.com/gateway.do
2.3 订单接口
package cn.huangsong.util.pay.ali;
import com.alipay.api.internal.util.AlipaySignature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alipay.api.AlipayApiException;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 订单接口
*
* @author Louis
* @date Dec 12, 2018
*/
@Slf4j
@RestController()
@RequestMapping("/order")
public class OrderController {
@Autowired
private Alipay aliPay;
/**
* 阿里支付
* @param outTradeNo
* @param subject
* @param totalAmount
* @param body
* @return
* @throws AlipayApiException
*/
@PostMapping(value = "/alipay")
public String alipay(String outTradeNo, String subject, String totalAmount, String body) throws AlipayApiException {
//根据支付宝接口协议,必填的参数
AlipayBean alipayBean = new AlipayBean();
alipayBean.setOut_trade_no(outTradeNo);
alipayBean.setSubject(subject);
alipayBean.setTotal_amount(totalAmount);
alipayBean.setBody(body);
return aliPay.pay(alipayBean);
}
/**
* 回调接口
* @param request
* @return
*/
@PostMapping("/notifyUrl")
public String callback(HttpServletRequest request){
//获取回调参数
Map<String, String> params = convertRequestParamsToMap(request);
log.info("支付宝回调:已回调,参数为====" + params);
try {
//校验签名及是否支付成功
checkCallbackAlipay(params);
/*处理一些业务*/
return "success";
} catch (Exception e) {
log.info("支付宝回调>>>>>>>>>>>>>>出错了");
e.printStackTrace();
return "failure";
}
}
/**
* 判断是否成功和回调验证签名
*/
private void checkCallbackAlipay(Map<String, String> params) throws Exception {
//是否支付成功
if (!"TRADE_SUCCESS".equals(params.get("trade_status"))) {
throw new Exception("支付宝支付失败!");
}
//......效验其他
//校验签名
if (!rsaCheckV1(params, AlipayProperties.getPublicKey())) {
throw new Exception("支付宝回调签名认证失败");
}
}
/**
* 支付宝支付回调验证签名
* 验证签名;参考:https://opendocs.alipay.com/open/54/106370
* @param params 回调参数
* @param alipayPublickey 传入支付宝公钥
* @return true 正确 false 失败
* @throws AlipayApiException
*/
public static boolean rsaCheckV1(Map<String, String> params, String alipayPublickey) throws AlipayApiException {
return AlipaySignature.rsaCheckV1(params, alipayPublickey, "utf-8", "RSA2");
}
/**
* 获取支付宝回调参数
*/
private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<String, String>();
Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
for (Map.Entry<String, String[]> entry : entrySet) {
String name = entry.getKey();
String[] values = entry.getValue();
int valLen = values.length;
if (valLen == 1) {
retMap.put(name, values[0]);
} else if (valLen > 1) {
StringBuilder sb = new StringBuilder();
for (String val : values) {
sb.append(",").append(val);
}
retMap.put(name, sb.toString().substring(1));
} else {
retMap.put(name, "");
}
}
return retMap;
}
}
2.4支付接口
package cn.huangsong.util.pay.ali;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
/**
* 支付宝支付接口
* @author Louis
* @date Dec 12, 2018
*/
@Component
public class Alipay {
/**
* 支付接口
* @param alipayBean
* @return
* @throws AlipayApiException
*/
public String pay(AlipayBean alipayBean) throws AlipayApiException {
// 1、获得初始化的AlipayClient 支付需要的参数准备
String serverUrl = AlipayProperties.getGatewayUrl();
String appId = AlipayProperties.getAppId();
String privateKey = AlipayProperties.getPrivateKey();
String format = "json";
String charset = AlipayProperties.getCharset();
String alipayPublicKey = AlipayProperties.getPublicKey();
String signType = AlipayProperties.getSignType();
String returnUrl = AlipayProperties.getReturnUrl();
String notifyUrl = AlipayProperties.getNotifyUrl();
AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPublicKey, signType);
// 2、设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
// 页面跳转同步通知页面路径
alipayRequest.setReturnUrl(returnUrl);
// 服务器异步通知页面路径
alipayRequest.setNotifyUrl(notifyUrl);
// 封装参数
alipayRequest.setBizContent(JSON.toJSONString(alipayBean));
// 3、请求支付宝进行付款,并获取支付结果
String result = alipayClient.pageExecute(alipayRequest).getBody();
// 返回付款信息
return result;
}
}
2.5 上面步骤---2.3订单接口需要的订单相关信息参数
package cn.huangsong.util.pay.ali;
/**
* 支付实体对象
* 根据支付宝接口协议,其中的属性名,必须使用下划线,不能修改
* @author Louis
* @date Dec 12, 2018
*/
public class AlipayBean {
/**
* 商户订单号,必填
*
*/
private String out_trade_no;
/**
* 订单名称,必填
*/
private String subject;
/**
* 付款金额,必填
* 根据支付宝接口协议,必须使用下划线
*/
private String total_amount;
/**
* 商品描述,可空
*/
private String body;
/**
* 超时时间参数
*/
private String timeout_express= "10m";
/**
* 产品编号
*/
private String product_code= "FAST_INSTANT_TRADE_PAY";
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getTotal_amount() {
return total_amount;
}
public void setTotal_amount(String total_amount) {
this.total_amount = total_amount;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getTimeout_express() {
return timeout_express;
}
public void setTimeout_express(String timeout_express) {
this.timeout_express = timeout_express;
}
public String getProduct_code() {
return product_code;
}
public void setProduct_code(String product_code) {
this.product_code = product_code;
}
}
2.6 增加一个 PropertiesListener 监听器,监听AlipayProperties 类在应用启动时加载配置文件属性。
package cn.huangsong.util.pay.ali;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* 配置文件监听器,用来加载自定义配置文件
* @author Louis
* @date Dec 12, 2018
*/
@Component
public class PropertiesListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
AlipayProperties.loadProperties();
}
}
2.7 上面步骤---2.4支付接口需要的应用相关参数
应用启动加载配置文件中的配置到propertiesMap中,在通过getter获取数据。
package cn.huangsong.util.pay.ali;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
/**
* 应用启动加载文件
* @author Louis
* @date Dec 12, 2018
*/
@Component
public class AlipayProperties {
public static final String APP_ID = "appId";
public static final String PRIVARY_KEY = "privateKey";
public static final String PUBLIC_KEY = "publicKey";
public static final String NOTIFY_URL = "notifyUrl";
public static final String RETURN_URL = "returnUrl";
public static final String SIGN_TYPE = "signType";
public static final String CHARSET = "charset";
public static final String GATEWAY_URL = "gatewayUrl";
/**
* 保存加载配置参数
*/
private static Map<String, String> propertiesMap = new HashMap<String, String>();
/**
* 加载属性
*/
public static void loadProperties() {
// 获得PathMatchingResourcePatternResolver对象
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
// 加载resource文件(也可以加载resources)
Resource resources = resolver.getResource("classpath:alipay.properties");
PropertiesFactoryBean config = new PropertiesFactoryBean();
config.setLocation(resources);
config.afterPropertiesSet();
Properties prop = config.getObject();
// 循环遍历所有得键值对并且存入集合
for (String key : prop.stringPropertyNames()) {
propertiesMap.put(key, (String) prop.get(key));
}
} catch (Exception e) {
new Exception("配置文件加载失败");
}
}
/**
* 获取配置参数值
* @param key
* @return
*/
public static String getKey(String key) {
return propertiesMap.get(key);
}
public static String getAppId() {
return propertiesMap.get(APP_ID);
}
public static String getPrivateKey() {
return propertiesMap.get(PRIVARY_KEY);
}
public static String getPublicKey() {
return propertiesMap.get(PUBLIC_KEY);
}
public static String getNotifyUrl() {
return propertiesMap.get(NOTIFY_URL);
}
public static String getReturnUrl() {
return propertiesMap.get(RETURN_URL);
}
public static String getSignType() {
return propertiesMap.get(SIGN_TYPE);
}
public static String getCharset() {
return propertiesMap.get(CHARSET);
}
public static String getGatewayUrl() {
return propertiesMap.get(GATEWAY_URL);
}
}
2.8 拿到沙箱环境下的支付账号,虚拟的。(测试需要用)
三、测试页面 放到static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<H1>支付测试</H1>
<hr>
<div class="form-container">
<form id="form" action="http://localhost:8080/order/alipay" method="post">
*商户订单 :
<input type="text" name="outTradeNo" value="dzcp100010001"><br>
*订单名称 :
<input type="text" name="subject" value="华为手机"><br>
*付款金额 :
<input type="text" name="totalAmount" value="6666" ><br>
*商品描述 :
<input type="text" name="body" value="华为手机"><br>
<input type="button" value="支付宝支付" onclick="submitForm('order/alipay')">
</form>
</div>
</body>
<script language="javascript">
function submitForm(action) {
/*订单接口*/
document.getElementById("form").action = "http://localhost:8080/order/alipay";
document.getElementById("form").submit()
}
</script>
<style>
.form-container {
padding-top:10px;
}
input {
margin:10px;
}
</style>
</html>
测试结果
自动跳转到
提交支付跳转
支付成功后跳转到你指定的页面-->returnUrl: http://dj29ri.natappfree.cc
异步通知到你写的接口
整个流程结束。
注意:
如果出现了这个情况,你只需要退出登录的支付宝账号,或者换个浏览器就可以了。
本地项目端口映射到外网
可以用natapp。百度一下流程,下载打开,在免费注册账号获取一个免费的隧道就可以了。