一:支付宝依赖包
<!--支付宝 SDK-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.57.ALL</version>
</dependency>
二:支付宝实现的前置条件
1.这里需要准备一个备案过的域名
2.准备一个商户号
3.app-id
4.网关地址(有沙箱环境正式环境)
5.应用私钥
6.支付宝公钥
7.支付成功跳转的页面url(可以是域名或者地址)
8.下单成功后回调地址
alipay:
#正式应用:3213123121
#沙箱应用:3213213213
app-id: 2132131231
#正式地址: https://openapi.alipay.com/gateway.do
#沙箱地址:https://openapi-sandbox.dl.alipaydev.com/gateway.do
gateway-url: https://openapi.alipay.com/gateway.do
#应用正式私钥 333122
#应用沙箱私钥 222
#应用支付宝公钥 44532423
#应用支付宝沙箱公钥 3333
public-key: 22221321
#支付成功后跳转的页面路径(这里不写这个)
return-url: http://127.0.0.1:8089/#/personal/paySuccess
#异步通知接口
notify-url: http://127.0.0.1:8089/notify
三:支付宝具体实现(V3)
支付宝配置类
@Configuration
//加载配置文件
@ConfigurationProperties(prefix = "alipay")
public class AlipayClientConfig {
@Resource
private Environment config;
@Bean
public AlipayClient alipayClient() throws AlipayApiException {
AlipayConfig alipayConfig = new AlipayConfig();
//设置网关地址
alipayConfig.setServerUrl(config.getProperty("alipay.gateway-url"));
//设置应用Id
alipayConfig.setAppId(config.getProperty("alipay.app-id"));
//设置应用私钥
alipayConfig.setPrivateKey(config.getProperty("alipay.merchant-private-key"));
//设置请求格式,固定值json
alipayConfig.setFormat(AlipayConstants.FORMAT_JSON);
//设置字符集
alipayConfig.setCharset(AlipayConstants.CHARSET_UTF8);
//设置支付宝公钥
alipayConfig.setAlipayPublicKey(config.getProperty("alipay.public-key"));
//设置签名类型
alipayConfig.setSignType(AlipayConstants.SIGN_TYPE_RSA2);
//构造client
AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
return alipayClient;
}
}
实现类
@CrossOrigin
@RestController
@RequestMapping("/x/x")
@Api(tags = "支付宝API")
@Slf4j
public class AliPayController {
@Resource
private AliPayService aliPayService;
@Resource
private Environment config;
@Autowired
private PayOderMapper payOrderMapper;
@ApiOperation("统一收单下单并支付页面接口的调用")
@PostMapping("/create/{orderId}")
public Result createOrder(@PathVariable String orderId) {
log.info("统一收单下单并支付页面接口的调用");
//支付宝开放平台接受 request 请求对象后
// 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本
String formStr = aliPayService.createOrder(orderId);
//我们将form表单字符串返回给前端程序,之后前端将会调用自动提交脚本,进行表单的提交
//此时,表单会自动提交到action属性所指向的支付宝开放平台中,从而为用户展示一个支付页面
return Result.OK("formStr", formStr);
}
@ApiOperation("支付通知")
@PostMapping("/x/notify")
public String tradeNotify(@RequestParam Map<String, String> params) {
log.info("支付通知正在执行");
// log.info("通知参数 ===> {}", params);
String result = "failure";
// /**
// * 这种方式效率高一点
// */
// //签名方式
// String sign_type = "RSA2";
// //对待签名字符串数据通过&进行拆分
// Map requestParams = request.getParameterMap();
// LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
// //把拆分数据放在map集合内
// for (Object o : requestParams.keySet()) {
// String name = (String) o;
// 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] + ",";
// }
// map.put(name, valueStr);
// }
//
// try {
// String property = config.getProperty("alipay.public-key");
//
System.out.println(b1);
//
// // 回调的待验签字符串
// // 编码格式
// String charset = "utf-8";
// // 支付宝公钥
// String publicKey = "";
// // 签名方式
//
// // 验签方法
// boolean signVerified = AlipaySignature.rsaCheckV1(map, property, AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
// if (signVerified) {
// // TODO 验签成功后
// System.out.println("success");
// } else {
// System.out.println("fail");
// }
// } catch (AlipayApiException e) {
// log.error("支付宝异步验签失败:{}", e.getMessage());
// return result;
// }
try {
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
config.getProperty("alipay.public-key"),
AlipayConstants.CHARSET_UTF8,
AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名
if (!signVerified) {
//验签失败则记录异常日志,并在response中返回failure.
log.error("支付成功异步通知验签失败!");
return result;
}
// 验签成功后
log.info("支付成功异步通知验签成功!");
//按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,
//1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
String outTradeNo = params.get("out_trade_no");
PayOrder payOrder= payOrderMapper.selectOne(new LambdaQueryWrapper<PayOrder >().eq(PayOrder ::getOrderCode, outTradeNo));
if (mailPayOrder == null) {
log.error("订单不存在");
return result;
}
//2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
String totalAmount = params.get("total_amount");
int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
int totalFeeInt = payOrder.getTotalMoney().multiply(new BigDecimal("100")).intValue();
if (totalAmountInt != totalFeeInt) {
log.error("金额校验失败");
return result;
}
// 3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方
// String sellerId = params.get("seller_id");
// String sellerIdProperty = config.getProperty("alipay.seller-id");
// if (!sellerId.equals(sellerIdProperty)) {
// log.error("商家pid校验失败");
// return result;
// }
//4 验证 app_id 是否为该商户本身
String appId = params.get("app_id");
String appIdProperty = config.getProperty("alipay.app-id");
if (!appId.equals(appIdProperty)) {
log.error("appid校验失败");
return result;
}
//在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,
// 支付宝才会认定为买家付款成功。
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus)) {
log.error("支付未成功");
return result;
}
//处理业务 修改订单状态 记录支付日志
aliPayService.processAliOrder(params);
//校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
result = "success";
} catch (AlipayApiException e) {
e.printStackTrace();
}
return result;
}
/**
* 用户取消订单
*
* @param orderNo
* @return
*/
@ApiOperation("用户取消订单")
@PostMapping("/x/{orderNo}")
public Result cancel(@PathVariable String orderNo) {
log.info("取消订单");
aliPayService.cancelOrder(orderNo);
return Result.ok("订单已取消");
}
/**
* 查询订单
*
* @param orderNo
* @return
*/
@ApiOperation("查询订单:测试订单状态用")
@GetMapping("/{orderNo}")
public Result queryOrder(@PathVariable String orderNo) {
log.info("查询订单");
String result = aliPayService.queryOrder(orderNo);
return Result.ok(result);
}
// /**
// * 申请退款
// *
// * @param orderNo
// * @param reason
// * @return
// */
// @ApiOperation("申请退款")
// @PostMapping("/trade/refund/{orderNo}/{reason}")
// public Result refunds(@PathVariable String orderNo, @PathVariable String reason) {
//
// log.info("申请退款");
// aliPayService.refund(orderNo, reason);
// return Result.ok();
// }
//
// /**
// * 查询退款
// *
// * @param orderNo
// * @return
// * @throws Exception
// */
// @ApiOperation("查询退款:测试用")
// @GetMapping("/refund/{orderNo}")
// public Result queryRefund(@PathVariable String orderNo) throws Exception {
//
// log.info("查询退款");
//
// String result = aliPayService.queryRefund(orderNo);
// return Result.ok(result);
// }
// /**
// * 根据账单类型和日期获取账单url地址
// *
// * @param billDate
// * @param type
// * @return
// */
// @ApiOperation("获取账单url")
// @GetMapping("/bill/downloadurl/query/{billDate}/{type}")
// public Result queryTradeBill(
// @PathVariable String billDate,
// @PathVariable String type) {
// log.info("获取账单url");
// String downloadUrl = aliPayService.queryBill(billDate, type);
// return Result.ok(downloadUrl);
// }
}
实现类
@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
@Resource
private AlipayClient alipayClient;
@Resource
private Environment config;
@Resource
private PaymentInfoService paymentInfoService;
@Resource
private RefundInfoService refundsInfoService;
@Autowired
private PayOrderTmpMapper PayOrderTmpMapper;
@Autowired
private PayOrderMapper PayOrderMapper;
@Autowired
private PayOrderService PayOrderService;
@Autowired
private PayOrderTmpService PayOrderTmpService;
private final ReentrantLock lock = new ReentrantLock();
@Transactional(rollbackFor = Exception.class)
@Override
public String createOrder(String orderCode) {
try {
//1.传单价、价格、总价、单位名称、
//生成订单
log.info("查询订单总金额");
PayOrderTmp PayOrderTmp = PayOrderTmpMapper.selectOrderInfo(orderCode);
//更改订单的支付方式
PayOrderMapper.updateOrderPayType(orderCode, PayType.ALIPAY.getPayType());
//调用支付宝接口
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
//配置需要的公共请求参数
//支付完成后,支付宝向发起异步通知的地址
request.setNotifyUrl(config.getProperty("alipay.notify-url"));
//支付完成后,我们想让页面跳转的页面,配置returnUrl
request.setReturnUrl(config.getProperty("alipay.return-url"));
//组装当前业务方法的请求参数
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderCode);
bizContent.put("total_amount", mailPayOrderTmp.getTotalMoney());
bizContent.put("subject", mailPayOrderTmp.getProductName());
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toString());
//执行请求,调用支付宝接口
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
log.info("调用成功,返回结果 ===> " + response.getBody());
return response.getBody();
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
throw new RuntimeException("创建支付交易失败");
}
} catch (AlipayApiException e) {
e.printStackTrace();
throw new RuntimeException("创建支付交易失败");
}
}
/**
* 处理订单
*
* @param params
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void processAliOrder(Map<String, String> params) {
log.info("处理订单");
//获取订单号
String orderNo = params.get("out_trade_no");
/*在对业务数据进行状态检查和处理之前,
要采用数据锁进行并发控制,
以避免函数重入造成的数据混乱*/
//尝试获取锁:
// 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
if (lock.tryLock()) {
try {
//处理重复通知
//接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
PayOrder PayOrder = PayOrderMapper.selectOne(new LambdaQueryWrapper<MailPayOrder>().eq(MailPayOrder::getOrderCode, orderNo));
if (!OrderStatus.NOTPAY.getType().equals(mailPayOrder.getOrderStatus())) {
return;
}
//交易金额
String totalAmount = params.get("total_amount");
//更新订单状态
PayOrderService.updateOrderStatus(mailPayOrder, new BigDecimal(totalAmount));
//处理订单业务
PayOrderTmpService.finishOrder(orderNo);
//记录支付日志
paymentInfoService.createPaymentInfoForAliPay(params, mailPayOrder.getPayUserId());
} finally {
//要主动释放锁
lock.unlock();
}
}
}
/**
* 用户取消订单
*
* @param orderNo
*/
@Override
public void cancelOrder(String orderNo) {
//调用支付宝提供的统一收单交易关闭接口
this.closeOrder(orderNo);
//更新用户订单状态
// orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
}
/**
* 查询订单
*
* @param orderNo
* @return 返回订单查询结果,如果返回null则表示支付宝端尚未创建订单
*/
@Override
public String queryOrder(String orderNo) {
try {
log.info("查单接口调用 ===> {}", orderNo);
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderNo);
request.setBizContent(bizContent.toString());
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
log.info("调用成功,返回结果 ===> " + response.getBody());
return response.getBody();
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
// throw new RuntimeException("查单接口的调用失败");
return null;//订单不存在
}
} catch (AlipayApiException e) {
e.printStackTrace();
throw new RuntimeException("查单失败");
}
}
/**
* 根据订单号调用支付宝查单接口,核实订单状态
* 如果订单未创建,则更新商户端订单状态
* 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
* 如果订单已支付,则更新商户端订单状态,并记录支付日志
*
* @param orderNo
*/
@Override
public void checkOrderStatus(String orderNo) {
log.warn("根据订单号核实订单状态 ===> {}", orderNo);
String result = this.queryOrder(orderNo);
//订单未创建
if (result == null) {
log.warn("核实订单未创建 ===> {}", orderNo);
//更新本地订单状态
PayOrderMapper.updateOrderStatus(orderNo, OrderStatus.CLOSED.getType());
}
//解析查单响应结果
Gson gson = new Gson();
HashMap<String, LinkedTreeMap> resultMap = gson.fromJson(result, HashMap.class);
LinkedTreeMap alipayTradeQueryResponse = resultMap.get("alipay_trade_query_response");
String tradeStatus = (String) alipayTradeQueryResponse.get("trade_status");
if (AliPayTradeState.NOTPAY.getType().equals(tradeStatus)) {
log.warn("核实订单未支付 ===> {}", orderNo);
//如果订单未支付,则调用关单接口关闭订单
this.closeOrder(orderNo);
// 并更新商户端订单状态
PayOrderMapper.updateOrderStatus(orderNo, OrderStatus.CLOSED.getType());
}
if (AliPayTradeState.SUCCESS.getType().equals(tradeStatus)) {
log.warn("核实订单已支付 ===> {}", orderNo);
MailPayOrder mailPayOrder = mailPayOrderMapper.selectOne(new LambdaQueryWrapper<MailPayOrder>().eq(MailPayOrder::getOrderCode, orderNo));
//如果订单已支付,则更新商户端订单状态
PayOrderMapper.updateOrderStatus(orderNo, OrderStatus.SUCCESS.getType());
//并记录支付日志
paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse, mailPayOrder.getPayUserId());
}
}
/**
* 关单接口的调用
*
* @param orderNo 订单号
*/
private void closeOrder(String orderNo) {
try {
log.info("关单接口的调用,订单号 ===> {}", orderNo);
AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderNo);
request.setBizContent(bizContent.toString());
AlipayTradeCloseResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
log.info("调用成功,返回结果 ===> " + response.getBody());
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
//throw new RuntimeException("关单接口的调用失败");
}
} catch (AlipayApiException e) {
e.printStackTrace();
throw new RuntimeException("关单接口的调用失败");
}
}
/**
* 退款
*
* @param orderNo
* @param reason
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) {
try {
log.info("调用退款API");
//创建退款单
RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
//调用统一收单交易退款接口
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
//组装当前业务方法的请求参数
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderNo);//订单编号
BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
//BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
bizContent.put("refund_reason", reason);//退款原因(可选)
request.setBizContent(bizContent.toString());
//执行请求,调用支付宝接口
AlipayTradeRefundResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
log.info("调用成功,返回结果 ===> " + response.getBody());
//更新订单状态
// orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
//更新退款单
refundsInfoService.updateRefundForAliPay(
refundInfo.getRefundNo(),
response.getBody(),
AliPayTradeState.REFUND_SUCCESS.getType()); //退款成功
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
//更新订单状态
// orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
//更新退款单
refundsInfoService.updateRefundForAliPay(
refundInfo.getRefundNo(),
response.getBody(),
AliPayTradeState.REFUND_ERROR.getType()); //退款失败
}
} catch (AlipayApiException e) {
e.printStackTrace();
throw new RuntimeException("创建退款申请失败");
}
}
/**
* 查询退款
*
* @param orderNo
* @return
*/
@Override
public String queryRefund(String orderNo) {
try {
log.info("查询退款接口调用 ===> {}", orderNo);
AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderNo);
bizContent.put("out_request_no", orderNo);
request.setBizContent(bizContent.toString());
AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
log.info("调用成功,返回结果 ===> " + response.getBody());
return response.getBody();
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
//throw new RuntimeException("查单接口的调用失败");
return null;//订单不存在
}
} catch (AlipayApiException e) {
e.printStackTrace();
throw new RuntimeException("查单接口的调用失败");
}
}
/**
* 申请账单
*
* @param billDate
* @param type
* @return
*/
@Override
public String queryBill(String billDate, String type) {
try {
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("bill_type", type);
bizContent.put("bill_date", billDate);
request.setBizContent(bizContent.toString());
AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
log.info("调用成功,返回结果 ===> " + response.getBody());
//获取账单下载地址
Gson gson = new Gson();
HashMap<String, LinkedTreeMap> resultMap = gson.fromJson(response.getBody(), HashMap.class);
LinkedTreeMap billDownloadurlResponse = resultMap.get("alipay_data_dataservice_bill_downloadurl_query_response");
String billDownloadUrl = (String) billDownloadurlResponse.get("bill_download_url");
return billDownloadUrl;
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
throw new RuntimeException("申请账单失败");
}
} catch (AlipayApiException e) {
e.printStackTrace();
throw new RuntimeException("申请账单失败");
}
}
}
枚举类
@AllArgsConstructor
@Getter
public enum AliPayTradeState {
/**
* 支付成功
*/
SUCCESS("TRADE_SUCCESS"),
/**
* 未支付
*/
NOTPAY("WAIT_BUYER_PAY"),
/**
* 已关闭
*/
CLOSED("TRADE_CLOSED"),
/**
* 退款成功
*/
REFUND_SUCCESS("REFUND_SUCCESS"),
/**
* 退款失败
*/
REFUND_ERROR("REFUND_ERROR");
/**
* 类型
*/
private final String type;
}
@AllArgsConstructor
@Getter
public enum OrderStatus {
/**
* 未支付
*/
NOTPAY(1, "未支付"),
/**
* 支付成功
*/
SUCCESS(2, "支付成功"),
/**
* 已关闭
*/
CLOSED(3, "超时已关闭"),
/**
* 已取消
*/
CANCEL(4, "用户已取消"),
/**
* 退款中
*/
REFUND_PROCESSING(5, "退款中"),
/**
* 已退款
*/
REFUND_SUCCESS(6, "已退款"),
/**
* 退款异常
*/
REFUND_ABNORMAL(7, "退款异常");
private Integer type;
/**
* 类型
*/
private final String message;
}
@AllArgsConstructor
@Getter
public enum PayType {
/**
* 微信
*/
WXPAY(1, "微信"),
/**
* 支付宝
*/
ALIPAY(2, "支付宝");
/**
* 类型
*/
private final Integer payType;
/**
* 信息
*/
private final String message;
}
@AllArgsConstructor
@Getter
public enum WechatPayUrlEnum {
/**
* native
*/
NATIVE("native"),
/**
* app
*/
APP("app"),
/**
* h5
*/
H5("h5"),
/**
* jsapi
*/
JSAPI("jsapi"),
/**
* 小程序jsapi
*/
SUB_JSAPI("sub_jsapi"),
/**
* APIV3下单
*/
PAY_TRANSACTIONS("/pay/transactions/"),
/**
* APIV2下单
*/
NATIVE_PAY_V2("/pay/unifiedorder"),
/**
* 查询订单
*/
ORDER_QUERY_BY_NO("/pay/transactions/out-trade-no/"),
/**
* 关闭订单
*/
CLOSE_ORDER_BY_NO("/pay/transactions/out-trade-no/%s/close"),
/**
* 申请退款
*/
DOMESTIC_REFUNDS("/refund/domestic/refunds"),
/**
* 查询单笔退款
*/
DOMESTIC_REFUNDS_QUERY("/refund/domestic/refunds/"),
/**
* 申请交易账单
*/
TRADE_BILLS("/bill/tradebill"),
/**
* 申请资金账单
*/
FUND_FLOW_BILLS("/bill/fundflowbill");
/**
* 类型
*/
private final String type;
}
四:微信依赖包
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
五:微信实现的前置条件
1.应用编号
2.商户号id
3.APIV3秘钥
4.微信支付回调
5.秘钥路径(申请的秘钥的时候可以生成一个证书,apiclient_key.pem文件放在路径下面)
6.商户证书序列号
appId: 123123213
#商户号
mchId: 12312321
# APIv3密钥
apiV3Key: 2213123
# 微信支付V3-url前缀
baseUrl: https://api.mch.weixin.qq.com/v3
# 支付通知回调, pjm6m9.natappfree.cc 为内网穿透地址
notifyUrl: http://127.0.1:1100/payNotify
# 退款通知回调, pjm6m9.natappfree.cc 为内网穿透地址
refundNotifyUrl: http://127.0.1:1100/payNotify/refundNotify
# 密钥路径,resources根目录下
keyPemPath: apiclient_key.pem
#商户证书序列号
serialNo: 213213213121213212
六:微信具体实现
配置类:
/**
* @Author:
* @Description:
**/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
/**
* 应用编号
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* 服务商商户号
*/
private String slMchId;
/**
* APIv2密钥
*/
private String apiKey;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知回调地址
*/
private String notifyUrl;
/**
* 退款回调地址
*/
private String refundNotifyUrl;
/**
* API 证书中的 key.pem
*/
private String keyPemPath;
/**
* 商户序列号
*/
private String serialNo;
/**
* 微信支付V3-url前缀
*/
private String baseUrl;
/**
* 获取商户的私钥文件
* @param keyPemPath
* @return
*/
public PrivateKey getPrivateKey(String keyPemPath){
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if(inputStream==null){
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取证书管理器实例
* @return
*/
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(mchId);
}
/**
* 获取支付http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, serialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
}
微信支付回调
@Api(tags = "微信回调接口(API3)")
@RestController
@Slf4j
@RequestMapping("/wx/notify")
public class NotifyController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WxService wxService;
@Resource
private Verifier verifier;
private final ReentrantLock lock = new ReentrantLock();
@ApiOperation(value = "支付回调", notes = "支付回调")
@ApiOperationSupport(order = 5)
@PostMapping("/payNotify")
public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("支付回调");
// 处理通知参数
Map<String, Object> bodyMap = getNotifyBody(request);
if (bodyMap == null) {
return falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
//处理订单
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
wxService.processWxOrder(resourceMap);
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return trueMsg(response);
}
// @ApiOperation(value = "退款回调", notes = "退款回调")
// @ApiOperationSupport(order = 5)
// @PostMapping("/refundNotify")
// public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
// log.info("退款回调");
//
// // 处理通知参数
// Map<String, Object> bodyMap = getNotifyBody(request);
// if (bodyMap == null) {
// return falseMsg(response);
// }
//
// log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
// if (lock.tryLock()) {
// try {
// // 解密resource中的通知数据
// String resource = bodyMap.get("resource").toString();
// Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
// String orderNo = resourceMap.get("out_trade_no").toString();
// String transactionId = resourceMap.get("transaction_id").toString();
//
// // TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
//
// log.warn("=========== 根据订单号,做幂等处理 ===========");
// } finally {
// //要主动释放锁
// lock.unlock();
// }
// }
//
// //成功应答
// return trueMsg(response);
// }
private Map<String, Object> getNotifyBody(HttpServletRequest request) {
//处理通知参数
String body = HttpUtils.readData(request);
log.info("退款回调参数:{}", body);
// 转换为Map
Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
});
// 微信的通知ID(通知的唯一ID)
String notifyId = bodyMap.get("id").toString();
// 验证签名信息
WechatPayValidator wechatPayValidator
= new WechatPayValidator(verifier, notifyId, body);
if (!wechatPayValidator.validate(request)) {
log.error("通知验签失败");
return null;
}
log.info("通知验签成功");
return bodyMap;
}
private Map<String, String> falseMsg(HttpServletResponse response) {
Map<String, String> resMap = new HashMap<>(8);
//失败应答
response.setStatus(500);
resMap.put("code", "ERROR");
resMap.put("message", "通知验签失败");
return resMap;
}
private Map<String, String> trueMsg(HttpServletResponse response) {
Map<String, String> resMap = new HashMap<>(8);
//成功应答
response.setStatus(200);
resMap.put("code", "SUCCESS");
resMap.put("message", "成功");
return resMap;
}
}
微信支付下单
@Api(tags = "微信支付接口(API3)")
@RestController
@RequestMapping("/wx/pay")
@Slf4j
public class PayController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WechatPayRequest wechatPayRequest;
@Autowired
private PayOrderTmpMapper PayOrderTmpMapper;
@Autowired
private PayOrderMapper PayOrderMapper;
/**
* type:h5、jsapi、app、native、sub_jsapi
*
* @param type
* @return
*/
@ApiOperation(value = "统一下单-统一接口", notes = "统一下单-统一接口")
@ApiOperationSupport(order = 10)
@GetMapping("/transactions")
public Map<String, Object> transactions(String type, String orderCode) {
log.info("统一下单API,支付方式:{}", type);
// 统一参数封装
//校验下单
PayOrderTmp PayOrderTmp = PayOrderTmpMapper.selectOrderInfo(orderCode);
if (PayOrderTmp == null) {
return null;
}
//更改订单的支付方式
PayOrderMapper.updateOrderPayType(orderCode, PayType.WXPAY.getPayType());
Map<String, Object> params = new HashMap<>(8);
params.put("appid", wechatPayConfig.getAppId());
params.put("mchid", wechatPayConfig.getMchId());
params.put("description", mailPayOrderTmp.getProductName());
params.put("out_trade_no", orderCode);
params.put("notify_url", wechatPayConfig.getNotifyUrl());
int totalAmountInt = mailPayOrderTmp.getTotalMoney().multiply(new BigDecimal("100")).intValue();
Map<String, Object> amountMap = new HashMap<>(4);
// 金额单位为分
amountMap.put("total",totalAmountInt);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 场景信息
Map<String, Object> sceneInfoMap = new HashMap<>(4);
// 客户端IP
sceneInfoMap.put("payer_client_ip", "127.0.0.1");
// 商户端设备号(门店号或收银设备ID)
sceneInfoMap.put("device_id", "127.0.0.1");
// 除H5与JSAPI有特殊参数外,其他的支付方式都一样
if (type.equals(WechatPayUrlEnum.H5.getType())) {
Map<String, Object> h5InfoMap = new HashMap<>(4);
// 场景类型:iOS, Android, Wap
h5InfoMap.put("type", "IOS");
sceneInfoMap.put("h5_info", h5InfoMap);
} else if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
Map<String, Object> payerMap = new HashMap<>(4);
//授权公众号id
// payerMap.put("openid", "123123123");
// params.put("payer", payerMap);
}
params.put("scene_info", sceneInfoMap);
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
// 重写type值,因为小程序会多一个下划线(sub_type)
String[] split = type.split("_");
String newType = split[split.length - 1];
String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType)), paramsStr);
Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
});
Map<String, Object> signMap = paySignMsg(resMap, type);
resMap.put("type", type);
resMap.put("signMap", signMap);
resMap.put("orderCode",orderCode);
return resMap;
}
private Map<String, Object> paySignMsg(Map<String, Object> map, String type) {
// 设置签名信息,Native与H5不需要
if (type.equals(WechatPayUrlEnum.H5.getType()) || type.equals(WechatPayUrlEnum.NATIVE.getType())) {
return null;
}
long timeMillis = System.currentTimeMillis();
String appId = wechatPayConfig.getAppId();
String timeStamp = timeMillis / 1000 + "";
String nonceStr = timeMillis + "";
String prepayId = map.get("prepay_id").toString();
String packageStr = "prepay_id=" + prepayId;
// 公共参数
Map<String, Object> resMap = new HashMap<>();
resMap.put("nonceStr", nonceStr);
resMap.put("timeStamp", timeStamp);
// JSAPI、SUB_JSAPI(小程序)
if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
resMap.put("appId", appId);
resMap.put("package", packageStr);
// 使用字段appId、timeStamp、nonceStr、package进行签名
String paySign = createSign(resMap);
resMap.put("paySign", paySign);
resMap.put("signType", "HMAC-SHA256");
}
// APP
if (type.equals(WechatPayUrlEnum.APP.getType())) {
resMap.put("appid", appId);
resMap.put("prepayid", prepayId);
// 使用字段appId、timeStamp、nonceStr、prepayId进行签名
String sign = createSign(resMap);
resMap.put("package", "Sign=WXPay");
resMap.put("partnerid", wechatPayConfig.getMchId());
resMap.put("sign", sign);
resMap.put("signType", "HMAC-SHA256");
}
return resMap;
}
/**
* 获取加密数据
*/
private String createSign(Map<String, Object> params) {
try {
Map<String, Object> treeMap = new TreeMap<>(params);
List<String> signList = new ArrayList<>(5);
for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
signList.add(entry.getKey() + "=" + entry.getValue());
}
String signStr = String.join("&", signList);
signStr = signStr + "&key=" + wechatPayConfig.getApiV3Key();
System.out.println(signStr);
Mac sha = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(wechatPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha.init(secretKey);
byte[] array = sha.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
signStr = sb.toString().toUpperCase();
System.out.println(signStr);
return signStr;
} catch (Exception e) {
throw new RuntimeException("加密失败!");
}
}
}
实现类
@Service
@Slf4j
public class WxServiceImpl implements WxService {
@Autowired
private PayOrderMapper PayOrderMapper;
@Autowired
private PayOrderTmpService PayOrderTmpService;
@Resource
private PaymentInfoService paymentInfoService;
@Autowired
private PayOrderService PayOrderService;
@Override
@Transactional(rollbackFor = Exception.class)
public void processWxOrder(Map<String, Object> map) {
String orderNo = map.get("out_trade_no").toString();
//接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
PayOrder mailPayOrder = PayOrderMapper.selectOne(new LambdaQueryWrapper<PayOrder>().eq(PayOrder::getOrderCode, orderNo));
if (!OrderStatus.NOTPAY.getType().equals(PayOrder.getOrderStatus())) {
return;
}
//交易金额
Object object = map.get("amount");
JSONObject jsonObject = JSONObject.parseObject(object.toString());
Integer totalMoney = (Integer) jsonObject.get("payer_total");
BigDecimal total = new BigDecimal(totalMoney).divide(new BigDecimal("100"));
//更新订单状态
PayOrderService.updateOrderStatus(mailPayOrder, total);
//处理订单业务
PayOrderTmpService.finishOrder(orderNo);
//记录支付日志
paymentInfoService.createPaymentInfoForWxPay(map, total,mailPayOrder.getPayUserId());
}
}
七:注意事项
1.在做微信支付的时候为了安全性,在处理回调的时候记得加上分布式锁,还有做幂等性处理,防止出现重复处理订单的现象,并且处理订单逻辑最好做个日志操作,方便追溯订单,加上事务回滚注解,防止异常后任然出现扣费现象
2.退款接口不能随便暴露,最好是做一个二次确认,防止被爬虫或者别人恶意使用,导致金钱损失。