上一篇介绍了java实现微信app支付,这篇介绍支付宝的app 支付,支付宝在支付方面比微信方便太多,如下:
引入maven依赖:
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.4.49.ALL</version>
</dependency>
application.yml配置文件:
alipay:
config:
url: https://openapi.alipay.com/gateway.do
appId: 2019123456474282
privateKey: 商户自己生成的私钥
pubKey: 支付宝公钥
charset: UTF-8
signType: RSA2
notifyUrl: https://test.shuai.com/aliPayCallback
format: json
支付宝配置文件:
package com.bcn.yeyks.apppay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author fuchangshuai
* @date 2019/3/6 10:02 AM
*/
@Data
@Component
@ConfigurationProperties(prefix = "alipay.config")
public class AlipayAppConfig {
private String url;
private String appId;
private String privateKey;
private String pubKey;
private String charset;
private String signType;
// 异步回调地址
private String notifyUrl;
private String vipNotifyUrl;
// 返回格式
private String format;
}
回调参数对象:
package com.bcn.yeyks.apppay.config;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Author fuchangshuai
* @date 2019/3/6 1:00 PM
*/
@Data
public class AlipayNotifyParam {
private String appId;
private String tradeNo; // 支付宝交易凭证号
private String outTradeNo; // 原支付请求的商户订单号
private String outBizNo; // 商户业务ID,主要是退款通知中返回退款申请的流水号
private String buyerId; // 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字
private String buyerLogonId; // 买家支付宝账号
private String sellerId; // 卖家支付宝用户号
private String sellerEmail; // 卖家支付宝账号
private String tradeStatus; // 交易目前所处的状态,见交易状态说明
private BigDecimal totalAmount; // 本次交易支付的订单金额
private BigDecimal receiptAmount; // 商家在交易中实际收到的款项
private BigDecimal buyerPayAmount; // 用户在交易中支付的金额
private BigDecimal refundFee; // 退款通知中,返回总退款金额,单位为元,支持两位小数
private String subject; // 商品的标题/交易标题/订单标题/订单关键字等
private String body; // 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
private Date gmtCreate; // 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtPayment; // 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtRefund; // 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
private Date gmtClose; // 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
private String fundBillList; // 支付成功的各个渠道金额信息,array
private String passbackParams; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
}
支付宝app支付类:
package com.bcn.yeyks.apppay.service.impl;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.bcn.yeyks.apppay.config.AlipayAppConfig;
import com.bcn.yeyks.apppay.service.AlipayService;
import com.bcn.yeyks.exception.ServiceException;
import com.bcn.yeyks.model.RefundRequestNo;
import com.bcn.yeyks.util.FormatUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
/**
* @Author fuchangshuai
* @date 2019/3/6 9:22 AM
*/
@Service
@Slf4j
public class AliPayServiceImpl implements AlipayService {
@Autowired
private AlipayAppConfig alipayAppConfig;
private AlipayClient alipayClient;
@PostConstruct
public void init() {
alipayClient = new DefaultAlipayClient(alipayAppConfig.getUrl(),
alipayAppConfig.getAppId(),
alipayAppConfig.getPrivateKey(),
alipayAppConfig.getFormat(),
alipayAppConfig.getCharset(),
alipayAppConfig.getPubKey(),
alipayAppConfig.getSignType());
}
@Override
public String tradeAppPay(String orderNo, Integer totalFee) {
log.info("支付宝app支付");
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("app支付");
model.setSubject("App支付测试Java");
model.setOutTradeNo(orderNo);
model.setTotalAmount(FormatUtils.fenToYuan(totalFee));
request.setBizModel(model);
request.setNotifyUrl(alipayAppConfig.getNotifyUrl());
AlipayTradeAppPayResponse response;
try {
response = alipayClient.sdkExecute(request);
} catch (AlipayApiException e) {
log.error("支付宝支付失败{}", e.getErrMsg());
throw new ServiceException(e.getErrMsg());
}
if (response.isSuccess()) {
log.info("调用成功" + response.getBody());
return response.getBody();
} else {
log.error("调用失败" + response.getBody());
return null;
}
}
@Override
public Boolean tradeRefund(String orderNo, Integer refundFee) {
log.info("支付宝退款 {}");
String outRequestNo = orderNo + RefundRequestNo.outNo;
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(orderNo);
model.setRefundAmount(FormatUtils.fenToYuan(refundFee));
model.setRefundReason("正常退款");
model.setOutRequestNo(outRequestNo);
refundRequest.setBizModel(model);
AlipayTradeRefundResponse response;
try {
response = alipayClient.execute(refundRequest);
} catch (AlipayApiException e) {
log.error("支付宝退款失败{}", e.getLocalizedMessage());
return false;
}
if (response != null && response.getCode().equals("10000")) {
log.info("调用成功" + response);
return true;
} else {
log.error("调用失败" + response);
return false;
}
}
}
支付宝回调:
package com.bcn.yeyks.apppay.controller;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.bcn.yeyks.apppay.config.AlipayAppConfig;
import com.bcn.yeyks.apppay.config.AlipayNotifyParam;
import com.bcn.yeyks.dal.domain.OrderInfo;
import com.bcn.yeyks.dal.domain.VipOrder;
import com.bcn.yeyks.service.OrderService;
import com.bcn.yeyks.service.VipOrderService;
import com.bcn.yeyks.util.FormatUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author fuchangshuai
* @date 2019/3/6 11:36 AM
*/
@Slf4j
@RestController
public class AlipayCallbackController {
private ExecutorService executorService = Executors.newFixedThreadPool(20);
@Autowired
private AlipayAppConfig alipayAppConfig;
@Autowired
private OrderService orderService;
@Autowired
private VipOrderService vipOrderService;
@RequestMapping(value = "/aliPayCallback", method = RequestMethod.POST)
@ResponseBody
public String callback(HttpServletRequest request) {
Map<String, String> params = convertRequestParamsToMap(request); // 将异步通知中收到的待验证所有参数都存放到map中
String paramsJson = JSON.toJSONString(params);
log.info("支付宝回调,{}", paramsJson);
try {
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayAppConfig.getPubKey(),
alipayAppConfig.getCharset(), alipayAppConfig.getSignType());
log.info("signVerified={}", signVerified);
if (signVerified) {
log.info("支付宝回调签名认证成功");
// 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
check(params);
// 另起线程处理业务
log.info("另起线程处理业务");
executorService.execute(() -> {
AlipayNotifyParam param = buildAlipayNotifyParam(params);
String tradeStatus = param.getTradeStatus();
// 支付成功
if (tradeStatus.equals("TRADE_SUCCESS")) {
log.info("支付成功");
//TODO 用户处理自己逻辑
} else {
log.info("支付失败");
//TODO 用户处理自己逻辑
}
});
// 如果签名验证正确,立即返回success,后续业务另起线程单独处理
return "success";
} else {
log.info("支付宝回调签名认证失败, paramsJson:{}", paramsJson);
return "failure";
}
} catch (AlipayApiException e) {
log.info("支付宝回调发生异常{}", e.getLocalizedMessage());
return "failure";
}
}
// 将request中的参数转换成Map
private Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<>();
//1.从支付宝回调的request域中取值
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
retMap.put(name, valueStr);
}
return retMap;
}
private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
String json = JSON.toJSONString(params);
return JSON.parseObject(json, AlipayNotifyParam.class);
}
private void check(Map<String, String> params) throws AlipayApiException {
String outTradeNo = params.get("out_trade_no");
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
OrderInfo order = orderService.selectOrderByOrderNo(outTradeNo);
if (order == null) {
throw new AlipayApiException("out_trade_no错误");
}
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
Integer totalAmount = FormatUtils.yuan2Fen(new BigDecimal(params.get("total_amount")));
log.info("totalAmount{}", totalAmount);
if (!totalAmount.equals(order.getSnapshotTotalFee())) {
throw new AlipayApiException("error total_amount");
}
// 3、验证app_id是否为该商户本身。
if (!params.get("app_id").equals(alipayAppConfig.getAppId())) {
throw new AlipayApiException("app_id不一致");
}
}
}
FormatUtils工具类:
package com.yeyks.common.utils;
import java.math.BigDecimal;
import java.util.Map;
/**
* @Author fuchangshuai
* @date 2019/3/6 10:28 AM
*/
public class FormatUtils {
/**
* 1. 钱的元到分
* 2. 佣金比例的百分之到万分之
*
* @param yuan
* @return
*/
public static int yuan2Fen(BigDecimal yuan) {
if (yuan == null) return 0;
return yuan.multiply(BigDecimal.valueOf(100)).intValue();
}
public static Integer yuanToFen(String yuan) {
return (new BigDecimal(yuan)).setScale(2, 4).multiply(new BigDecimal(100)).intValue();
}
public static int yuan2Fen(Double yuan) {
if (yuan == null) {
return 0;
}
BigDecimal bigDecimal = new BigDecimal(String.valueOf(yuan));
return yuan2Fen(bigDecimal);
}
public static int yuan2Fen(int yuan) {
if (yuan <= 0) return 0;
return yuan * 100;
}
/**
* 1. 钱的元到分
* 2. 佣金比例的万分之到百分之
*
* @param fen
* @return
*/
public static BigDecimal fen2Yuan(Integer fen) {
if (fen == null) {
return BigDecimal.valueOf(0);
}
return BigDecimal.valueOf(fen).divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_FLOOR);
}
public static Integer fen2YuanInt(Integer fen) {
if (fen == null) {
return 0;
}
return fen / 100;
}
public static String fenToYuan(Integer fen) {
BigDecimal bigDecimal = fen2Yuan(fen);
bigDecimal = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP);
return bigDecimal.toString();
}
/**
* 返回 (金额 * 百分比)(分)
*
* @param amount 金额 分
* @param rate 百分比
* @return
*/
public static Integer amountToRate(Integer amount, Integer rate) {
if ((amount == null) || (rate == null)) {
return null;
}
BigDecimal multiply = BigDecimal.valueOf(amount).multiply(BigDecimal.valueOf(rate).divide(BigDecimal.valueOf(100)));
return multiply.intValue();
}
/**
* 返回 (金额 * 万分比)(分)
*
* @param amount 金额 分
* @param rate 万分比
* @return
*/
public static Integer amountToRateV2(Integer amount, Integer rate) {
if ((amount == null) || (rate == null)) {
return null;
}
BigDecimal multiply = BigDecimal.valueOf(amount).multiply(BigDecimal.valueOf(rate).divide(BigDecimal.valueOf(10000)));
return multiply.intValue();
}
}
到这里支付宝app支付实现成功,修改商户信息后直接可以引用实现,小伙伴们可以试试