小提示
- 前提须知:支付宝开放平台及其SDK使用(没看此篇难懂以下讲解)
- 支付宝开放平台链接:
https://developers.alipay.com/platform/home.htm
- 一个应用,包含一个公钥和一个密钥,也包含一个买家和一个卖家,只是沙箱模拟,有了公钥即可进行验签操作,本次演示有3处用到验签(1.同步回调,2.异步回调,3.将api生成的表单提交给支付宝网关)
逻辑图
- 发起支付的时候需要传递6个参数(
订单编号,付款金额,订单名称,商品描述,同步回调地址,异步回调地址
) 给到支付宝api,然后支付宝会生成一个String类型的form表单(并且携带提交表单功能的js代码,此from表单不会显示在页面上看到,一生成就马上提交给支付宝网关了)返回,这个过程也称之为验签 - 若此验签操作没问题后,则会在页面显示让你登陆你的支付宝账号和密码,随后输入支付密码进行支付
- 支付成功后,则开始分别调用
同步回调
和异步回调
,两个回调方法内都会分别进行验签判断,确保是支付宝方来请求回调url才可进行后面的逻辑操作 同步回调
用来返回要显示的页面效果,异步回调
用来做后台的逻辑操作(比如支付完后的订单,修改其状态)
- 若要执行退款操作,则需要传递3个参数(
交易订单号,退款金额,退款原因
)给退款接口进行逻辑操作,退款成功则返回sucess
配置
pom.xml
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.10.170.ALL</version>
</dependency>
pay-service-dev.yaml
server:
port: 8051
ribbon:
eager-load:
enabled: true
rocketmq:
producer:
group: pay-group
alipay:
app_id : 支付宝开放平台应用的appId
merchant_private_key : 私钥数据
alipay_public_key : 公钥数据
sign_type : RSA2
charset : utf-8
gatewayUrl : https://openapi.alipaydev.com/gateway.do
bootstrap.yml
spring:
application:
name: pay-service
cloud:
nacos:
config:
server-addr: 192.168.8.12:8848
file-extension: yaml
shared-configs:
- data-id: nacos-discovery-config-dev.yaml
profiles:
active: dev
- 其他服务,要传入同步回调和异步回调Url,给到支付宝微服务去进行回调
- 所以其他服务为了解决硬编码问题,将同步和异步回调的url自定义配置在了yml配置更加美观
- 其他需要用到支付宝微服务的服务的application.yml(做参考就行)
pay:
returnUrl: 同步回调的接口url
notifyUrl: 异步回调的接口url
frontEndPayUrl: 同步回调方法里,要显示给用户页面的url路径
开搞
- pay-service微服务架构图
AlipayProperties
- 通过
@ConfigurationProperties(prefix = "alipay")
注入了yml配置文件对应的值给以下属性
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Setter
@Getter
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayProperties {
private String appId;
private String merchantPrivateKey;
private String alipayPublicKey;
private String signType ;
private String charset;
private String gatewayUrl ;
}
AlipayConfig
- 将此包含了公钥等数据(可用作验签)的支付宝AlipayClient对象交给spring管理
@Configuration
public class AlipayConfig {
@Bean
public AlipayClient alipayClient(AlipayProperties alipayProperties){
return new DefaultAlipayClient(alipayProperties.getGatewayUrl(), alipayProperties.getAppId(), alipayProperties.getMerchantPrivateKey(), "json", alipayProperties.getCharset(), alipayProperties.getAlipayPublicKey(), alipayProperties.getSignType());
}
}
控制器各接口代码演示
payOnline接口
:作为支付
接口,接收PayVo对象
(6个参数),通过alipayClient对象
(内包含公钥)操作返回一个String类型的表单(包含js提交表单操作功能,此表单直接提交数据给支付宝网关,也就是验签操作),这里也相当于验签操作,成功以后,用户浏览器则出现登陆支付宝账号和密码页面,即可输入支付密码完成支付(商家余额增加,用户余额减少)rsaCheck接口
:用作同步
和异步
回调时候的验签判断
操作,防止中途数据被篡改,保证是支付宝方来访问的回调url操作refundOnline接口
:用作退款
接口,接收RefundVo对象
(3个参数),通过alipayClient对象
(包含公钥,给支付宝作验签确保是哪个用户的余额)操作,返回boolean类型代表退款是否成功
@RestController
@RequestMapping("/alipay")
public class AlipayController {
@Autowired
private AlipayClient alipayClient;
@Autowired
private AlipayProperties alipayProperties;
@RequestMapping("/payOnline")
Result<String> payOnline(@RequestBody PayVo payVo) throws AlipayApiException {
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(payVo.getReturnUrl());
alipayRequest.setNotifyUrl(payVo.getNotifyUrl());
String out_trade_no = payVo.getOutTradeNo();
String total_amount = payVo.getTotalAmount();
String subject = payVo.getSubject();
String body = payVo.getBody();
alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\","
+ "\"total_amount\":\"" + total_amount + "\","
+ "\"subject\":\"" + subject + "\","
+ "\"body\":\"" + body + "\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
String result = alipayClient.pageExecute(alipayRequest).getBody();
return Result.success(result);
}
@RequestMapping("/rsaCheck")
Result<Boolean> rsaCheck(@RequestBody Map<String, String> params) throws AlipayApiException {
boolean signVerified = AlipaySignature.rsaCheckV1(params,
alipayProperties.getAlipayPublicKey(),
alipayProperties.getCharset(),
alipayProperties.getSignType());
return Result.success(signVerified);
}
@RequestMapping("/refundOnline")
public Result<Boolean> refundOnline(@RequestBody RefundVo refundVo) throws AlipayApiException {
AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
String out_trade_no = refundVo.getOutTradeNo();
String trade_no = "";
String refund_amount = refundVo.getRefundAmount();
String refund_reason = refundVo.getRefundReason();
String out_request_no = refundVo.getOutTradeNo();
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"trade_no\":\""+ trade_no +"\","
+ "\"refund_amount\":\""+ refund_amount +"\","
+ "\"refund_reason\":\""+ refund_reason +"\","
+ "\"out_request_no\":\""+ out_request_no +"\"}");
AlipayTradeRefundResponse alipayTradeRefundResponse = alipayClient.execute(alipayRequest);
return Result.success(alipayTradeRefundResponse.isSuccess());
}
}
所对应的封装对象
PayVo支付接口参数对象
@Setter@Getter
public class PayVo {
private String outTradeNo;
private String totalAmount;
private String subject;
private String body;
private String returnUrl;
private String notifyUrl;
}
RefundVo退款接口参数对象
@Setter@Getter
public class RefundVo implements Serializable {
private String outTradeNo;
private String refundAmount;
private String refundReason;
}
Result返回封装对象
@Setter
@Getter
public class Result<T> implements Serializable {
public static final int SUCCESS_CODE = 200;
public static final String SUCCESS_MESSAGE = "操作成功";
public static final int ERROR_CODE = 500000;
public static final String ERROR_MESSAGE = "系统异常";
private int code;
private String msg;
private T data;
public Result(){}
private Result(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> Result<T> success(T data){
return new Result(SUCCESS_CODE,SUCCESS_MESSAGE,data);
}
public static <T> Result<T> success(String msg, T data){
return new Result(SUCCESS_CODE,msg,data);
}
public static Result error(CodeMsg codeMsg){
return new Result(codeMsg.getCode(),codeMsg.getMsg(),null);
}
public static Result defaultError(){
return new Result(ERROR_CODE,ERROR_MESSAGE,null);
}
public boolean hasError(){
return this.code!=SUCCESS_CODE;
}
}
同步和异步回调接口url
- 这里演示定义同步和异步回调接口,是在另一个服务中定义的(可做参考)
- 先注入yml配置类中所定义的同步回调所要显示的页面url路径
- 在回调接口内进行验签判断,确保是支付宝发来的请求,避免中途被篡改了数据
@Value("${pay.frontEndPayUrl}")
private String frontEndPayUrl;
同步回调接口
@RequestMapping("/return_url")
public void returnUrl(HttpServletResponse response, @RequestParam Map<String, String> params) throws IOException {
System.out.println("同步回调");
String orderNo = params.get("out_trade_no");
Result<Boolean> result = payFeignApi.rsaCheck(params);
if (result.getData()) {
response.sendRedirect(frontEndPayUrl + orderNo);
} else {
response.sendRedirect("localhost/50x.html");
}
response.sendRedirect(frontEndPayUrl + orderNo);
}
异步回调接口
@RequestMapping("/notify_url")
public void notify_url(@RequestParam Map<String, String> params, HttpServletResponse response) throws IOException {
System.out.println("异步回调" + new Date());
PrintWriter out = response.getWriter();
Result<Boolean> result = payFeignApi.rsaCheck(params);
if (result != null || !result.hasError()) {
if (result.getData()) {
String orderNo = params.get("out_trade_no");
orderInfoService.changePayStatus(orderNo);
out.println("success");
} else {
out.println("fail");
}
} else {
out.println("fail");
}
}