理论基础
1.沙箱
沙箱 :执行程序的虚拟环境,这个环境就像一个装满细沙的箱子,你可以在里面玩沙子(运行程序),
但沙子不会溢出箱子影响到外面的世界(真实系统)
支付宝沙箱 :支付宝开放平台为开发者提供的一个 模拟真实支付宝环境的测试平台 ,用于开发和测试支
付功能
2.配置部分参数原因
为什么下面使用支付宝
要配置支付宝沙箱 appid ,应用私钥 appPrivateKey ,支付宝公钥 alipayPublicKey
支付宝沙箱 appid
作用 :应用程序在支付宝开放平台上的唯一标识(就像你的 身份证号码 ,用于区分不同的应用和用户。
在支付宝沙箱环境中,每个沙箱应用都有一个独特的 appId ,用于标识和区分不同的测试应用)
配置原因 :当你发起支付请求时,支付宝服务器会根据你 提供的 appId 来识别你的应用,并验证你的身
份 。只有验证通过,支付宝才会继续处理你的支付请求
公钥私钥
为什么 要配置支付宝公钥和应用私钥 : 确保支付工程的 安全性 ,验证交易信息的 真实性 等
下面详细说说 公钥私钥怎样实现安全性真实性
公钥、私钥:通过加密算法得到的一个密钥 对
公钥:是密钥对中 公开 的部分,就像一把公开的锁头形状,任何人都可以使用它来锁上信息(加密)
私钥:是密钥对中 非公开 的部分,就像锁头的钥匙,只有持有者(即私钥的拥有者)才能使用它来打开
信息(解密)
具体有两个作用:
加密内容
公钥加密,私钥解密
类比皇帝持有私钥,臣子持有公钥
臣子 A 、 B 、 C 加密内容,只有皇帝能看,数据安全 √ (反之皇帝加密的内容臣子都能看,不安全 × )
所以加密内容,用公钥加密,私钥解密
数字签名
私钥生成数字签名,公钥解密 (不要理解成私钥加密,公钥解密,私钥只是生成数字签名,没有对内容
加密)
发送方 会使用自己的私钥来对《信息的哈希值》进行加密,即《加密后的信息的哈希值》是 数字签名
(信件印章,但《信的内容没有加密》哈)
接收方 在收到信息后,会使用发送方的公钥来解密数字签名 = 》 能解密 代表发送方就是正确的人,能信
任,而且得到了《信息的哈希值》。
同时 ,接收方还会对收到的信件信息进行哈希运算,以生成一个信息的哈希值。然后,接收方会 比较 这
两个哈希值 = 》如果 一样 则在发送途中信件信息没有被篡改
使用支付宝
一. 配置沙箱账号
打开沙箱地址: https://open.alipay.com/develop/sandbox/app
需要获取: AppId 、支付宝网关地址、应用私钥、支付宝公钥
二. 配置内网穿透账号
为什么配置内网穿透?
因为不配置的话,支付宝访问不到你的本地程序。所以要把本地的服务 暴露到公网 ,让支付宝可以调用
我们的接口给我们传递数据
1.注册 https://natapp.cn/
2.配置免费通道
3.下载natapp并配置authtoken
4.本地启动,复制代理的 url地址
三. 引入支付宝SDK
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.35.79.ALL</version>
</dependency>
四. 配置SpringBoot
4.1. application.yml
alipay :appId : xxxappPrivateKey : xxxalipayPublicKey : xxxnotifyUrl : http : //mbwxuj.natappfree.cc/alipay/notify
notifyUrl : 异步通知的 URL ,写后端路径(为了让支付宝能访问到,前面加上让项目暴露公网上的域
名)。
异步通知 是什么 :
与同步通知(即支付请求发出后,等待支付结果返回)不同,异步通知 不需要 商家系统等待支付结果的
返回,而是允许商家系统继续执行其他任务。
当支付完成时,支付平台会主动向商家提供的通知地址(即 notifyUrl )发送支付结果通知。 这个通知包
含了支付结果的所有关键信息 ,比如支付是否成功、支付的金额、支付的订单号等等。
商家收到这个通知后,就可以根据通知中的信息来更新自己网站上的订单状态了,但是 注意 要 验签 商家
在接收到支付宝的异步通知后,需要进行验签操作,以验证通知的真实性。验签过程中,商家需要使用
支付宝提供的公钥来验证支付宝签名的真实性。
4.2. AliPay.java 读取配置
package com.example.pojo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "alipay")
public class AliPay {
// 支付宝的AppId
private String appId;
// 应用私钥
private String appPrivateKey;
// 支付宝公钥
private String alipayPublicKey;
// 支付宝通知本地的接口完整地址
private String notifyUrl;
}
4.3. AliPayController
三大功能:
1. 支付功能
2. 异步通知商家支付完成,更新订单状态和数据库数据
3. 退款功能
package com.example.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.example.mapper.OrdersMapper;
import com.example.pojo.AliPay;
import com.example.pojo.CustomException;
import com.example.pojo.Orders;
import com.example.pojo.dto.Result;
import com.example.service.OrdersService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/alipay")
public class AliPayController {
// 支付宝沙箱网关地址
private static final String GATEWAY_URL = "https://openapisandbox.dl.alipaydev.com/gateway.do";
private static final String FORMAT = "JSON";
private static final String CHARSET = "UTF-8";
//签名方式
private static final String SIGN_TYPE = "RSA2";
@Resource
private AliPay aliPay;
@Resource
private OrdersService ordersService;
@Resource
private OrdersMapper ordersMapper;
@GetMapping("/pay") // /alipay/pay?orderNo=xxx
public void pay(String orderNo, HttpServletResponse httpResponse) throws
Exception {
// 查询订单信息
Orders orders = ordersService.selectByOrderNo(orderNo);
if (orders == null) {
return;
}
// 1. 创建支付宝客户端Client(通用SDK提供的Client),负责调用支付宝的API
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL,
aliPay.getAppId(),
aliPay.getAppPrivateKey(), FORMAT, CHARSET,
aliPay.getAlipayPublicKey(), SIGN_TYPE);
// 2. 发送请求的 Request类(里面设置我们传给支付宝的一些参数)
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); //
request.setNotifyUrl(aliPay.getNotifyUrl());//支付宝通知本地的接口完整地址
/* AlipayTradePagePayModel model = new AlipayTradePagePayModel();
// 设置商户订单号
model.setOutTradeNo(orders.getOrderNo());
// 设置订单总金额
model.setTotalAmount(orders.getTotal().toString());
// 设置订单标题
model.setSubject(orders.getGoodsName());
// 设置产品码
model.setProductCode("FAST_INSTANT_TRADE_PAY");
request.setBizModel(model);*/
JSONObject bizContent = new JSONObject();
bizContent.set("out_trade_no", orders.getOrderNo()); // 我们自己生成的订单
编号
bizContent.set("total_amount", orders.getTotal()); // 订单的总金额
bizContent.set("subject", orders.getGoodsName()); // 支付的名称
bizContent.set("product_code", "FAST_INSTANT_TRADE_PAY"); // 固定配置
request.setBizContent(bizContent.toString());
request.setReturnUrl("http://localhost:8080/orders"); // 支付完成后自动跳转
到本地页面的路径
// 执行请求,拿到响应的结果,返回给浏览器
String form = "";
try {
form = alipayClient.pageExecute(request).getBody(); // 调用SDK生成表
单,输入账号密码
} catch (AlipayApiException e) {
e.printStackTrace();
}
httpResponse.setContentType("text/html;charset=" + CHARSET);
httpResponse.getWriter().write(form);// 直接将完整的表单html输出到页面
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
@PostMapping("/notify") // 注意这里必须是POST接口
public void payNotify(HttpServletRequest request) throws Exception {
if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
System.out.println("=========支付宝异步回调========");
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
params.put(name, request.getParameter(name));
}
String sign = params.get("sign");
String content = AlipaySignature.getSignCheckContentV1(params);
boolean checkSignature = AlipaySignature.rsa256CheckContent(content,
sign, aliPay.getAlipayPublicKey(), "UTF-8"); // 验证签名
// 支付宝验签
if (checkSignature) {
// 验签通过
System.out.println("交易名称: " + params.get("subject"));
System.out.println("交易状态: " + params.get("trade_status"));
System.out.println("支付宝交易凭证号: " + params.get("trade_no"));
System.out.println("商户订单号: " + params.get("out_trade_no"));
System.out.println("交易金额: " + params.get("total_amount"));
System.out.println("买家在支付宝唯一id: " +
params.get("buyer_id"));
System.out.println("买家付款时间: " + params.get("gmt_payment"));
System.out.println("买家付款金额: " +
params.get("buyer_pay_amount"));
String tradeNo = params.get("out_trade_no"); // 订单编号
String gmtPayment = params.get("gmt_payment"); // 支付时间
String alipayTradeNo = params.get("trade_no"); // 支付宝交易编号
// 更新订单状态为已支付,设置支付信息
Orders orders = ordersService.selectByOrderNo(tradeNo);
orders.setStatus("已支付");
orders.setPayTime(gmtPayment);
orders.setPayNo(alipayTradeNo);
ordersService.updateById(orders);
}
}
}
/**
* 退款接口
*/
@PutMapping("/refund")
public Result refund(String orderNo) throws CustomException {
Orders orders = ordersService.selectByOrderNo(orderNo);
if (ObjectUtil.isNull(orders)) {
throw new CustomException("500", "未找到订单");
}
// 1. 创建Client,通用SDK提供的Client,负责调用支付宝的API
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL,
aliPay.getAppId(),
aliPay.getAppPrivateKey(), FORMAT, CHARSET,
aliPay.getAlipayPublicKey(), SIGN_TYPE);
// 2. 创建 Request并设置Request参数
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
JSONObject bizContent = new JSONObject();
bizContent.set("out_trade_no", orders.getOrderNo()); // 我们自己生成的订单
编号
bizContent.set("refund_amount", orders.getTotal()); // 订单的总金额
bizContent.set("trade_no", orders.getPayNo()); // 支付宝支付订单号
bizContent.set("out_request_no", IdUtil.fastSimpleUUID()); // 随机数
request.setBizContent(bizContent.toString());
try {
// 退款调用接口
AlipayTradeRefundResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
System.out.println("订单号" + orderNo + "退款成功");
ordersMapper.updatePayState(orderNo,"已退款", DateUtil.now());
return Result.success();
}else{
System.out.println(response.getBody());
return Result.error(response.getCode(),response.getBody());
}
} catch (AlipayApiException e) {
e.printStackTrace();
System.out.println("退款失败");
}
return Result.error();
}
}
五.页面效果
调用支付宝接口生成的form表单