1、设置支付宝环境参数,并导入支付SDK依赖
//支付宝环境参数设置
ali.pay.appId=AppID
ali.pay.appPrivateKey=应用私钥
ali.pay.publicKey=支付宝公钥
ali.pay.returnUrl=回调地址
ali.pay.notifyUrl=异步地址
ali.pay.gateway=网关(测试和正式环境的网关不同,需要注意)
ali.pay.signType=RSA2
ali.pay.charset=UTF-8
ali.pay.format=json
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.8.10.ALL</version>
</dependency>
2、支付宝支付状态常量类
package com.ganguomob.dev.dsprwpt.domain.service.alipay;
import org.springframework.util.StringUtils;
/**
* Created by luotj on 2019-08-15.
*/
public enum AliTradeStatus {
WAIT_BUYER_PAY("WAIT_BUYER_PAY", "交易创建,等待买家付款"),
TRADE_CLOSED("TRADE_CLOSED", "未付款交易超时关闭,或支付完成后全额退款"),
TRADE_SUCCESS("TRADE_SUCCESS", "交易支付成功"),
TRADE_FINISHED("TRADE_FINISHED", "交易结束,不可退款");
private String code;
private String msg;
AliTradeStatus(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
public static AliTradeStatus fromCode(String code) {
if (StringUtils.pathEquals(code, WAIT_BUYER_PAY.code)) {
return WAIT_BUYER_PAY;
}
if (StringUtils.pathEquals(code, TRADE_CLOSED.code)) {
return TRADE_CLOSED;
}
if (StringUtils.pathEquals(code, TRADE_SUCCESS.code)) {
return TRADE_SUCCESS;
}
if (StringUtils.pathEquals(code, TRADE_FINISHED.code)) {
return TRADE_FINISHED;
}
throw new RuntimeException("code error");
}
}
3、请求实体类
package com.ganguomob.dev.dsprwpt.ui.dto.request.api.alipay;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author: create by luotj
* date: 2019-12-25 18:39
**/
@Data
public class AliPayRequest {
//支付金额
private BigDecimal totalAmount;
//客户支付编码
private String outTradeNo;
//订单名称
private String subject;
//商品描述
private String body;
//充值记录id
private Long rechargeId;
//支付方式:1.支付宝
private Integer type;
}
4、支付宝回调响应类
package com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay;
import com.ganguomob.dev.dsprwpt.domain.service.alipay.AliTradeStatus;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* @author: create by luotj
* date: 2019-12-28 16:02
**/
@ApiModel
@Data
public class AlipayCallbackResponse {
// notify_time 通知时间 Date 是 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss 2015-14-27 15:45:58
private String notifyTime;
// notify_type 通知类型 String(64) 是 通知的类型 trade_status_sync
private String notifyType;
// notify_id 通知校验ID String(128) 是 通知校验ID ac05099524730693a8b330c5ecf72da9786
private String notifyId;
// app_id 支付宝分配给开发者的应用Id String(32) 是 支付宝分配给开发者的应用Id 2014072300007148
private String appId;
// charset 编码格式 String(10) 是 编码格式,如utf-8、gbk、gb2312等 utf-8
private String charset;
// version 接口版本 String(3) 是 调用的接口版本,固定为:1.0 1.0
private String version;
// sign_type 签名类型 String(10) 是 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
private String signType;
// sign 签名 String(256) 是 请参考文末 异步返回结果的验签 601510b7970e52cc63db0f44997cf70e
private String sign;
// trade_no 支付宝交易号 String(64) 是 支付宝交易凭证号 2013112011001004330000121536
private String tradeNo;
// out_trade_no 商户订单号 String(64) 是 原支付请求的商户订单号 6823789339978248
private String outTradeNo;
// out_biz_no 商户业务号 String(64) 否 商户业务ID,主要是退款通知中返回退款申请的流水号 HZRF001
private String outBizNo;
// buyer_id 买家支付宝用户号 String(16) 否 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字 2088102122524333
private String buyerId;
// buyer_logon_id 买家支付宝账号 String(100) 否 买家支付宝账号 15901825620
private String buyerLogonId;
// seller_id 卖家支付宝用户号 String(30) 否 卖家支付宝用户号 2088101106499364
private String sellerId;
// seller_email 卖家支付宝账号 String(100) 否 卖家支付宝账号 zhuzhanghu@alitest.com
private String sellerEmail;
// trade_status 交易状态 String(32) 否 交易目前所处的状态,见下张表 交易状态说明 TRADE_CLOSED
private AliTradeStatus tradeStatus;
// total_amount 订单金额 Number(9,2) 否 本次交易支付的订单金额,单位为人民币(元) 20
private String totalAmount;
// receipt_amount 实收金额 Number(9,2) 否 商家在交易中实际收到的款项,单位为元 15
private String receiptAmount;
// invoice_amount 开票金额 Number(9,2) 否 用户在交易中支付的可开发票的金额 10.00
private String invoiceAmount;
// buyer_pay_amount 付款金额 Number(9,2) 否 用户在交易中支付的金额 13.88
private String buyerPayAmount;
// point_amount 集分宝金额 Number(9,2) 否 使用集分宝支付的金额 12.00
private String pointAmount;
// refund_fee 总退款金额 Number(9,2) 否 退款通知中,返回总退款金额,单位为元,支持两位小数 2.58
private String refundFee;
// subject 订单标题 String(256) 否 商品的标题/交易标题/订单标题/订单关键字等,是请求时对应的参数,原样通知回来 当面付交易
private String subject;
// body 商品描述 String(400) 否 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来 当面付交易内容
private String body;
// gmt_create 交易创建时间 Date 否 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss 2015-04-27 15:45:57
private String gmtCreate;
// gmt_payment 交易付款时间 Date 否 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss 2015-04-27 15:45:57
private String gmtPayment;
// gmt_refund 交易退款时间 Date 否 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S 2015-04-28 15:45:57.320
private String gmtRefund;
// gmt_close 交易结束时间 Date 否 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss 2015-04-29 15:45:57
private String gmtClose;
// fund_bill_list 支付金额信息 String(512) 否 支付成功的各个渠道金额信息,详见下表 资金明细信息说明 [{“amount”:“15.00”,“fundChannel”:“ALIPAYACCOUNT”}]
private String fundBillList;
// passback_params 回传参数 String(512) 否 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝 merchantBizType%3d3C%26merchantBizNo%3d2016010101111
private String passbackParams;
// voucher_detail_list 优惠券信息 String 否 本交易支付时所使用的所有优惠券信息,详见下表 优惠券信息说明 [{“amount”:“0.20”,“merchantContribute”:“0.00”,“name”:“一键创建券模板的券名称”,“otherContribute”:“0.20”,“type”:“ALIPAY_DISCOUNT_VOUCHER”,“memo”:“学生卡8折优惠”]
private String voucherDetailList;
}
5、支付查询结果响应类
package com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 阿里支付查询响应
*
* @author: create by luotj
* date: 2020-01-14 14:32
**/
@ApiModel
@Data
public class AliQueryResponse {
@ApiModelProperty("交易支付成功:1,其他类型返回0")
public Integer status;
@ApiModelProperty("支付返回data")
public String data;
}
6、Controller层发起请求
package com.ganguomob.dev.dsprwpt.ui.controller.api.user;
import com.ganguomob.dev.dsprwpt.service.api.user.UserService;
import com.ganguomob.dev.dsprwpt.ui.dto.request.api.alipay.AliPayRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "01. 账户管理")
@RestController
@RequestMapping("/api/account")
public class AccountController {
@Autowired
private UserServiceImpl mUserService;
@ApiOperation(value = "支付充值")
@PutMapping("/pay")
public String payRecharge() {
//支付宝支付信息
AliPayRequest aliPayRequest = new AliPayRequest()
.setBody("测试Body")
.setOutTradeNo("45313153")
.setSubject("测试subject")
.setRechargeId(1L)
.setType(1)
.setTotalAmount(new BigDecimal(100));
return mUserService.alipayPreCreate(aliPayRequest);
}
}
7、异步回调地址Controller
package com.ganguomob.dev.dsprwpt.ui.controller.api.alipay;
import com.ganguomob.dev.dsprwpt.service.api.alipay.AliPayService;
import com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay.AliQueryResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author: create by luotj
* date: 2019-12-27 10:47
**/
@RestController
@Slf4j
@Api(tags = "12. 支付宝接口")
@RequestMapping("/api/alipay")
public class AdminAlipayNotificationController {
@Autowired
private UserServiceImpl mUserServiceImpl;
@ApiOperation(value = "异步消息回调")
@PostMapping("/notify/callback")
public String alipayCallback(HttpServletRequest request) {
return mUserServiceImpl.alipayCallback(request);
}
@ApiOperation(value = "查询订单支付状态")
@GetMapping("/check-status/{orderNo}")
public AliQueryResponse checkOrderPayingStatus(
@PathVariable("orderNo") @ApiParam(value = "订单号", required = true) String orderNo) {
return mUserServiceImpl.alipayQuery(orderNo);
}
}
8、Service层生成预支付,并返回信息给前端生成支付二维码(实现逻辑核心)
package com.ganguomob.dev.dsprwpt.service.api.user;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.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.AlipayTradePrecreateRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ganguomob.dev.base.infrastructure.exception.CommonBusinessException;
import com.ganguomob.dev.dsprwpt.constant.PayType;
import com.ganguomob.dev.dsprwpt.constant.RechargeStatus;
import com.ganguomob.dev.dsprwpt.domain.repository.fee.IFeeRecordRepository;
import com.ganguomob.dev.dsprwpt.domain.repository.user.IUserRepository;
import com.ganguomob.dev.dsprwpt.domain.service.alipay.AliTradeStatus;
import com.ganguomob.dev.dsprwpt.infrastructure.util.xinbao.XinBaoUtils;
import com.ganguomob.dev.dsprwpt.jooq.tables.pojos.FeeRecordPOJO;
import com.ganguomob.dev.dsprwpt.ui.dto.request.api.alipay.AliPayRequest;
import com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay.AliQueryResponse;
import com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay.AlipayCallbackResponse;
import com.google.common.base.CaseFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@Slf4j
@Service
public class UserServiceImpl{
@Value("${ali.pay.appId}")
private String appId;
@Value("${ali.pay.appPrivateKey}")
private String privateKey;
@Value("${ali.pay.publicKey}")
private String aliKey;
@Value("${ali.pay.returnUrl}")
private String returnUrl;
@Value("${ali.pay.notifyUrl}")
private String notifyUrl;
@Value("${ali.pay.gateway}")
private String gateWay;
@Value("${ali.pay.signType}")
private String signType;
@Value("${ali.pay.charset}")
private String charset;
@Value("${ali.pay.format}")
private String format;
/**
* 支付宝支付回调逻辑
* @param request
* @return
*/
@Override
public String alipayCallback(HttpServletRequest request) {
//将异步通知中收到的所有参数都存放到 map 中
Map<String, String> paramsMap = callbackRequestToMap(request);
alipayCheckRSAV1By(paramsMap);
AlipayCallbackResponse response = mapToAlipayResponse(paramsMap);
log.info("支付宝回调信息:订单序列号{}", response.getOutTradeNo());
if (response.getTradeStatus() == null) {
log.error("ali pay callback response status is null " + "\n"
+ "out trade no:" + response.getOutTradeNo());
return "fail";
}
String orderNumber = response.getOutTradeNo();
//支付查询是否支付成功
AliQueryResponse queryResponse = alipayQuery(orderNumber);
if (queryResponse.getStatus() == 1) {
//支付成功回调接口,并做状态的判断更改,逻辑
if (recordPOJO.getRechargeStatus().equals(RechargeStatus.PAY_FAIL)) {
//支付失败,逻辑
}
return "success";
} else {
log.info("订单序列号:{},支付失败", orderNumber);
return "fail";
}
}
//生成预支付逻辑方法
public String alipayPreCreate(AliPayRequest aliPayRequest) {
log.info("预支付生成二维码");
//校验数据
checkPayParamsValid(aliPayRequest);
//创建客户端
AlipayClient alipayClient = new DefaultAlipayClient(gateWay, appId, privateKey, format,charset, aliKey, signType);
//SDK中的请求实体类
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
//判断订单号是否为空
Map<String, Object> paramMap = new HashMap<>();
if (aliPayRequest.getOutTradeNo().isEmpty()) {
paramMap.put("type", "2");
paramMap.put("data", "订单号不能为空");
return null;
}
//设置异步、回调地址
request.setNotifyUrl(notifyUrl);
request.setReturnUrl(returnUrl);
//设置主要参数,参数说明参考文档
paramMap.put("timeout_express", "5m");
paramMap.put("out_trade_no", aliPayRequest.getOutTradeNo());
paramMap.put("total_amount", aliPayRequest.getTotalAmount().doubleValue());
paramMap.put("subject", aliPayRequest.getSubject());
//将数据转换为json数据,并设置到request中
ObjectMapper mapper = new ObjectMapper();
String qrCode = null;
try {
//发起预支付请求
String bizContent = mapper.writeValueAsString(paramMap);
request.setBizContent(bizContent);
AlipayTradePrecreateResponse precreateResponse = alipayClient.execute(request);
//获取支付二维码内容
qrCode = precreateResponse.getQrCode();
} catch (AlipayApiException e) {
log.error("AlipayApiException,错误信息:{},ErrMsg:{},msg:{}",
e.getErrCode(), e.getErrMsg(), e.getMessage());
} catch (JsonProcessingException e) {
log.error("转换数据失败,错误信息:{}", e.getMessage());
}
//返回支付二维码内容(前段根据该内容生成支付二维码)
return qrCode;
}
/**
* 阿里支付查询
*
* @return
*/
@Override
public AliQueryResponse alipayQuery(String outTradeNo) {
AlipayClient alipayClient = new DefaultAlipayClient(gateWay, appId, privateKey, format,
charset, aliKey, signType);
//创建查询对象
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("out_trade_no", outTradeNo);
//转换为json数据
ObjectMapper mapper = new ObjectMapper();
AliQueryResponse queryResponse = new AliQueryResponse();
try {
String queryMap = mapper.writeValueAsString(paramMap);
request.setBizContent(queryMap);
//执行查询
AlipayTradeQueryResponse response = alipayClient.execute(request);
JSONObject jsonObject = JSON.parseObject(response.getBody())
.getJSONObject("alipay_trade_query_response");
String code = jsonObject.getString("code");
String msg = jsonObject.getString("msg");
if ("10000".equals(code) && "Success".equals(msg)) {
//支付成功,逻辑
queryResponse.setStatus(1)
.setData(tradeStatus);
} else {
//其它状态,逻辑
queryResponse.setStatus(0)
.setData(tradeStatus);
}
} catch (Exception e) {
log.error("数据转换异常:msg{}", e.getMessage());
}
return queryResponse;
}
private Map<String, String> callbackRequestToMap(HttpServletRequest request) {
// 获取支付宝POST过来反馈信息
Map<String, String> params = new HashMap<>();
Map requestParams = request.getParameterMap();
for (Iterator 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(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name), valueStr);
}
return params;
}
//检查签名
public void alipayCheckRSAV1By(Map<String, String> paramsMap) {
boolean signVerified = false;
try {
//调用SDK验证签名
signVerified = AlipaySignature.rsaCheckV1(paramsMap, aliKey, charset, signType);
log.info("pay callback rsa check flag:" + signVerified);
} catch (AlipayApiException e) {
e.printStackTrace();
log.error("pay callback rsa check error" + "\n" +
"error code:" + e.getErrCode() + "\n" +
"error msg:" + e.getErrMsg());
throw new RuntimeException();
}
}
//校验数据
private void checkPayParamsValid(AliPayRequest params) {
//订单编码不能为空
if (Strings.isBlank(params.getOutTradeNo())) {
throw new RuntimeException("订单编码不能为空");
}
//商品信息不能为空
if (Strings.isBlank(params.getBody())) {
throw new RuntimeException("商品信息不能为空");
}
//订单名称不能为空
if (Strings.isBlank(params.getSubject())) {
throw new RuntimeException("订单名称不能为空");
}
//支付价格必须大于0
if (params.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("支付价格必须大于0");
}
}
private AlipayCallbackResponse mapToAlipayResponse(Map<String, String> params) {
AlipayCallbackResponse response = new AlipayCallbackResponse();
response.setNotifyTime(params.get("notify_time"));
response.setNotifyType(params.get("notify_type"));
response.setNotifyId(params.get("notify_id"));
response.setAppId(params.get("app_id"));
response.setCharset(params.get("charset"));
response.setVersion(params.get("version"));
response.setSignType(params.get("sign_type"));
response.setSign(params.get("sign"));
response.setTradeNo(params.get("trade_no"));
response.setOutTradeNo(params.get("out_trade_no"));
response.setOutBizNo(params.getOrDefault("out_biz_no", ""));
response.setBuyerId(params.getOrDefault("buyerId", ""));
response.setBuyerLogonId(params.getOrDefault("buyer_logon_id", ""));
response.setSellerId(params.getOrDefault("seller_id", ""));
response.setSellerEmail(params.getOrDefault("seller_email", ""));
response.setTradeStatus(params.containsKey("trade_status") ?
AliTradeStatus.fromCode(params.get("trade_status")) : null);
response.setTotalAmount(params.getOrDefault("total_amount", ""));
response.setReceiptAmount(params.getOrDefault("receipt_amount", ""));
response.setInvoiceAmount(params.getOrDefault("invoice_amount", ""));
response.setBuyerPayAmount(params.getOrDefault("buyer_pay_amount", ""));
response.setPointAmount(params.getOrDefault("point_amount", ""));
response.setRefundFee(params.getOrDefault("refund_fee", ""));
response.setSubject(params.getOrDefault("subject", ""));
response.setBody(params.getOrDefault("body", ""));
response.setGmtCreate(params.getOrDefault("gmt_create", ""));
response.setGmtPayment(params.getOrDefault("gmt_payment", ""));
response.setGmtRefund(params.getOrDefault("gmt_refund", ""));
response.setGmtClose(params.getOrDefault("gmt_close", ""));
response.setFundBillList(params.getOrDefault("fund_bill_list", ""));
response.setPassbackParams(params.getOrDefault("passback_params", ""));
response.setVoucherDetailList(params.getOrDefault("voucher_detail_list", ""));
return response;
}
}