SpringBoot整合支付宝之电脑网站支付
一、支付宝介绍
支付宝(中国)网络技术有限公司 [1] 是国内的第三方支付平台,致力于提供“简单、安全、快速”的支付解决方案 [2] 。支付宝公司从2004年建立开始,始终以“信任”作为产品和服务的核心。旗下有“支付宝”与“支付宝钱包”两个独立品牌。自2014年第二季度开始成为当前全球最大的移动支付厂商。
当用户提交订单会跳转到选择支付渠道页面!
当用户点击立即支付时生成支付的二维码
使用支付宝app 进行扫码支付
1.2 过程分析
1.3 对接支付宝的准备工作
1、申请条件
- 企业或个体工商户可申请;
- 提供真实有效的营业执照,且支付宝账户名称需与营业执照主体一致;
- 网站能正常访问且页面信息有完整商品内容;
- 网站必须通过ICP备案,个体户备案需与账户主体一致。 (团购类网站不支持个体工商户签约)
支付手续费
1.4 申请步骤:
1、支付宝商家中心中申请 https://www.alipay.com/
2、https://b.alipay.com/signing/productSetV2.htm?mrchportalwebServer=https%3A%2F%2Fmrchportalweb.alipay.com
一个工作日后登录到蚂蚁金服开发者中心中:
可以查看到一个已经签约上线的应用。 其中非常重要的是这个APPID,需要记录下来之后的程序中要用到这个参数。
点击查看
到此为止,电商网站可以访问支付宝的最基本的准备已经完成。
2、支付功能实现
支付宝开发手册:https://docs.open.alipay.com/270/105900/
支付宝有了同步通知为什么还需要异步通知?
同步回调两个作用
第一是从支付宝的页面上返回自己的网站继续后续操作;
第二是携带支付状态的get参数;让自己的网站用于验证;
同步通知后;还需要异步通知主要是为了防止出现意外情况;
因为涉及到金钱;这是一个对安全和稳定要求比较严格的场景;
如果同步通知的过程中;用户不小心关闭了浏览器;或者浏览器卡死了;
异步也能收到通知;记录支付状态;
即便是用户端没问题;万一自己的服务器网络异常了一下呢?
如果自己的服务器没有正确返回接受到通知的状态;
支付宝的服务器会在一段时间内持续的往自己的服务器发送异步通知;
一直到成功;
顺便去确认了下;这个一段时间是:
25 小时以内完成 8 次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h)
2.1 思路分析
1.将支付数据保存到数据库,以便跟支付宝进行对账
2.生成要支付的二维码
生成二维码需要的参数列表请参考官方文档
https://opendocs.alipay.com/open/270/105899
2.2 编写支付宝支付接口
2.2.1 导入sdk包
https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java
<!--导入支付宝支付sdk-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.8.73.ALL</version>
</dependency>
2.2.2 制作AlipayClient工具类
在resource中添加alipay.properties
alipay_url=https://openapi.alipay.com/gateway.do
app_id=2021001163617452
app_private_key=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8Z7EZmanxyFGsK4LrIUeKKrrGxWAHIgPmUV8TtZDs+jeplJSw1ckSY63QhEU444D5qd6xruJHBuB33HG+ik4n8N8nRWi3AtMgpC061oq2DcgtIKMmQHO7/poYDwbpDZrOWXIyiNshFfUOSTUpnrS8UvEks6n6xR/G72r2FG07oZzO7g3XsPMr73wpYajMYC/bhTm6CJGEWZikONNDFkQpVHa+zgitwsqlBuvBvVwGwOHA9B8aRfokwAMl6BDXKoH8BNnSEMpWSTRSwbssayXAQWNU7XKDKGozbn4U2dEbl8GCFzikI/T7ybTNm5gs46ZZBGlq/YB4+v4D3t74Vl6nAgMBAAECggEAOidzhehliYkAlLk1huhV0bMQxewEkQ8RzxTM2SORIWS2q7R+FPtYPkHgU92QFFg85lNltsi5dZ0MylKUFXFRYIi8CL4m7V6E1q12fJPeawVkBXHuig8Y6i1TWRvCUUtuvkTjt++AW/0QECHOtBMVzI95eY+vZwVToq8h/+UcNmxKyVt66Qpo4+r+cUvlvGX5mXgQVC5Ftf/MtHA1i+kjtzBITC0xAvmSXKzjN1YhtcS9rXyMHXBiFhXLdmvOXjkn0Okosr2+tmesXfSwDGhH3ZlOdHzit4D602RNl0nTA1dOUWHuCncs1TrWbriax86P/EYvmzMiHWCVTmmNJC0bMQKBgQD0HAXKNsYsdjCQOV4t3SMqOKaul67x/KA20PmMZVfQ2sQkyjyFgWpL8C16Rzf3zI7df+zF5SkvhFY4+LRZVwX5okEFYTzAZ/NYouj1/DABYOPq0E0sY18/xtq7FJ/CIk8qmCqcczqoyaoxoaC1zAt9E4CYE89iEOnO+GhcI3H3LwKBgQDFlQzvbXhWRyRFkeft/a52XLnyj6t9iP7wNGbGCSeoMDrAu3ZgoqacUPWj5MgSFZdT48H9rF4pPixXoe3jfUNsWBUHqD1F2drDz7lpL0PbpSsgy6ei+D4RwTADsuyXwrkvrWrGro+h6pNJFyly3nea/gloDtJTzfhFFwtNfmqyCQKBgBXzMx4UwMscsY82aV6MZO4V+/71CrkdszZaoiXaswPHuB1qxfhnQ6yiYyR8pO62SR5ns120Fnj8WFh1HJpv9cyVp20ZakIO1tXgiDweOh7VnIjvxBC6usTcV6y81QS62w2Ec0hwIBUvVQtzciUGvP25NDX4igxSYwPGWHP4h/XnAoGAcQN2aKTnBgKfPqPcU4ac+drECXggESgBGof+mRu3cT5U/NS9Oz0Nq6+rMVm1DpMHAdbuqRikq1aCqoVWup51qE0hikWy9ndL6GCynvWIDOSGrLWQZ2kyp5kmy5bWOWAJ6Ll6r7Y9NdIk+NOkw614IFFaNAj2STUw4uPxdRvwD3ECgYEArwOZxR3zl/FZfsvVCXfK8/fhuZXMOp6Huwqky4tNpVLvOyihpOJOcIFj6ZJhoVdmiL8p1/1S+Sm/75gx1tpFurKMNcmYZbisEC7Ukx7RQohZhZTqMPgizlVBTu5nR3xkheaJC9odvyjrWQJ569efXo30gkW04aBp7A15VNG5Z/U=
alipay_public_key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkWs+3gXMosiWG+EbfRyotWB0waqU3t7qMQSBxU0r3JZoND53jvWQfzrGZ8W+obMc+OgwupODDVxhG/DEKVBIptuUQYdvAjCSH98m2hclFcksspuCy9xS7PyflPE47pVzS6vA3Slvw5OFQ2qUcku4paWnBxguLUGPjEncij5NcyFyk+/k57MmrVJwCZaI+lFOS3Eq2IXc07tWXO4s/2SWr3EJiwJutOGBdA1ddvv1Urrl0pWpEFg30pJB6J7YteuxdEL90kuO5ed/vnTK5qgQRvEelROkUW44xONk1784v28OJXmGICmNL1+KyM/SFbFOSgJZSV1tEXUzvL/xvzFpLwIDAQAB
# 同步回调地址 填自己项目跳转成功页面的地址
return_payment_url=http://api.gmall.com/api/payment/alipay/callback/return
# 异步回调地址 必须要真实的域名 可用内网穿透实现
notify_payment_url=http://xsiv7k.natappfree.cc/api/payment/alipay/callback/notify
# 当用户获取到回调地址之后,调整到支付成功页面。
return_order_url=http://payment.gmall.com/pay/success.html
2.2.3 创建配置类
@Configuration
@PropertySource("classpath:alipay.properties")
public class AlipayConfig {
@Value("${alipay_url}")
private String alipay_url;
@Value("${app_private_key}")
private String app_private_key;
@Value("${app_id}")
private String app_id;
public final static String format="json";
public final static String charset="utf-8";
public final static String sign_type="RSA2";
public static String return_payment_url;
public static String notify_payment_url;
public static String return_order_url;
public static String alipay_public_key;
@Value("${alipay_public_key}")
public void setAlipay_public_key(String alipay_public_key) {
AlipayConfig.alipay_public_key = alipay_public_key;
}
@Value("${return_payment_url}")
public void setReturn_url(String return_payment_url) {
AlipayConfig.return_payment_url = return_payment_url;
}
@Value("${notify_payment_url}")
public void setNotify_url(String notify_payment_url) {
AlipayConfig.notify_payment_url = notify_payment_url;
}
@Value("${return_order_url}")
public void setReturn_order_url(String return_order_url) {
AlipayConfig.return_order_url = return_order_url;
}
// <bean id="alipayClient" class="com.alipay.api.AlipayClient"> </bean>
@Bean
public AlipayClient alipayClient(){
// AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY, SIGN_TYPE); //获得初始化的AlipayClient
AlipayClient alipayClient=new DefaultAlipayClient(alipay_url,app_id,app_private_key,format,charset, alipay_public_key,sign_type );
return alipayClient;
}
}
2.2.4 编写支付接口
public interface AlipayService {
//orderId为唯一订单交易编号
String createaliPay(Long orderId) throws AlipayApiException;
}
2.2.5支付接口实现类
@Service
public class AlipayServiceImpl implements AlipayService {
@Autowired
private AlipayClient alipayClient;
@Override
public String createaliPay(Long orderId) throws AlipayApiException {
// 生产二维码
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();//创建API对应的request
// 同步回调
alipayRequest.setReturnUrl(AlipayConfig.return_payment_url);
// 异步回调
alipayRequest.setNotifyUrl(AlipayConfig.notify_payment_url);//在公共参数中设置回跳和通知地址
// 参数
// 声明一个map 集合
HashMap<String, Object> map = new HashMap<>();
//订单交易编号
map.put("out_trade_no",orderId);
map.put("product_code","FAST_INSTANT_TRADE_PAY");
//要支付的金额
map.put("total_amount",0.01);
map.put("subject","test");
alipayRequest.setBizContent(JSON.toJSONString(map));
return alipayClient.pageExecute(alipayRequest).getBody(); //调用SDK生成表单;
}
}
控制器
@Controller
@RequestMapping("/api/payment/alipay")
public class AlipayController {
@Autowired
private AlipayService alipayService;
@RequestMapping("submit/{orderId}")
@ResponseBody
public String submitOrder(@PathVariable(value = "orderId") Long orderId, HttpServletResponse response){
String from = "";
try {
from = alipayService.createaliPay(orderId);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return from;
}
}
3. 支付后回调—同步回调
3.1 控制器AlipayController
/**
* 支付宝回调
* @return
*/
@RequestMapping("callback/return")
public String callBack() {
// 同步回调给用户展示信息
return "redirect:" + AlipayConfig.return_order_url;
}
/**
* 支付成功页
* @return
*/
@GetMapping("pay/success.html")
public String success() {
return "payment/success";
}
4. 支付宝回调—异步回调
异步回调有两个重要的职责:
确认并记录用户已付款,通知电商模块。新版本的支付接口已经取消了同步回调的支付结果传递。所以用户付款成功与否全看异步回调。
接收到回调要做的事情:
1、验证回调信息的真伪
2、验证用户付款的成功与否
3、把新的支付状态写入支付信息表{paymentInfo}中。
4、通知电商
5、给支付宝返回回执。
4.1 控制器AlipayController
/**
* 支付宝异步回调 必须使用内网穿透
* @param paramMap
* @param request
* @return
*/
@RequestMapping("callback/notify")
@ResponseBody
public String alipayNotify(@RequestParam Map<String, String> paramMap) {
System.out.println("回来了!");
boolean signVerified = false; //调用SDK验证签名
try {
signVerified = AlipaySignature.rsaCheckV1(paramMap, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
} catch (AlipayApiException e) {
e.printStackTrace();
}
// 交易状态
String trade_status = paramMap.get("trade_status");
String out_trade_no = paramMap.get("out_trade_no");
// true
if (signVerified) {
// TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
if ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {
// 但是,如果交易记录表中 PAID 或者 CLOSE 获取交易记录中的支付状态 通过outTradeNo来查询数据
// select * from paymentInfo where out_trade_no=?
//写自己的查询业务
/* PaymentInfo paymentInfo = paymentService.getPaymentInfo(out_trade_no, PaymentType.ALIPAY.name());
if (paymentInfo.getPaymentStatus().equals(PaymentStatus.PAID.name()) || paymentInfo.getPaymentStatus().equals(PaymentStatus.ClOSED.name())){
return "failure";
}
*/
// 正常的支付成功,我们应该更新交易记录状态
/* paymentService.paySuccess(out_trade_no,PaymentType.ALIPAY.name(), paramMap);
*/
return "success";
}
} else {
// TODO 验签失败则记录异常日志,并在response中返回failure.
return "failure";
}
return "failure";
}
5. 退款
直接在浏览器发起请求即可!
商户与客户协商一致的情况下,才可以退款!
5.1 接口
boolean refund(Long orderId);
5.2 实现类
@Override
public boolean refund(Long orderId) {
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
HashMap<String, Object> map = new HashMap<>();
//订单编号
map.put("out_trade_no", orderId);
//退款金额
map.put("refund_amount", 0.01);
//退款理由
map.put("refund_reason", "测试");
// out_request_no
request.setBizContent(JSON.toJSONString(map));
AlipayTradeRefundResponse response = null;
try {
response = alipayClient.execute(request);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response.isSuccess()) {
// 更新交易记录 : 关闭 写自己的业务逻辑
/* PaymentInfo paymentInfo = new PaymentInfo();
paymentInfo.setPaymentStatus(PaymentStatus.ClOSED.name());
paymentService.updatePaymentInfo(orderInfo.getOutTradeNo(), paymentInfo);
*/
return true;
} else {
return false;
}
}
5.3 控制器
// 发起退款!http://localhost:8205/api/payment/alipay/refund/20
@RequestMapping("refund/{orderId}")
@ResponseBody
public Result refund(@PathVariable(value = "orderId")Long orderId) {
// 调用退款接口
boolean flag = alipayService.refund(orderId);
return Result.ok(flag);
}