微信支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
本篇介绍两种对接方式。
第一种:根据官方文档自定义对接
定义微信支付服务
@Slf4j
@Component
@RefreshScope
public class WxPayService {
//商户id
@Value("${wechat.merchantId}")
private String merchantId;
//应用ID
@Value("${wechat.appId}")
private String appId;
//商户API私钥
@Value("${wechat.privateKey}")
private String privateKey;
//微信支付平台证书
@Value("${wechat.cert}")
private String payCertificate;
//商户证书序列号
@Value("${wechat.mchSerialNo}")
private String certificateSn;
//微信支付平台证书
@Value("${wechat.notifyUrl}")
private String notifyUrl;
//key
@Value("${wechat.apiV3Key}")
private String apiV3Key;
private CloseableHttpClient httpClient;
/**
* 初始化参数
*/
@PostConstruct
public void initData() {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKey));
X509Certificate certificate = PemUtil.loadCertificate(new FileInputStream(payCertificate));
List<X509Certificate> list = new ArrayList<>();
list.add(certificate);
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, certificateSn, merchantPrivateKey)
.withWechatPay(list).build();
} catch (FileNotFoundException e) {
log.error(e.getMessage());
}
}
/**
* 获取预支付交易会话标识
*
* @param order
* @return
* @throws Exception
*/
@Override
public String prepay(OrderInfo order) throws Exception {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
JSONObject amount = new JSONObject();
//订单总金额,单位为分
amount.put("total", order.getPayment());
//CNY:人民币,境内商户号仅支持人民币
amount.put("currency", "CNY");
JSONObject param = new JSONObject();
param.put("mchid", merchantId);
param.put("appid", appId);
//商品描述
param.put("description", order.getDescription());
//回调地址
param.put("notify_url", notifyUrl);
//商户订单号
param.put("out_trade_no", order.getOrderId());
param.put("amount", amount);
log.info("prepay request=>{}", param);
CloseableHttpResponse response = null;
try {
httpPost.setEntity(new StringEntity(param.toString(), "UTF-8"));
response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject resBody = JSON.parseObject(bodyAsString);
return resBody.getString("prepay_id");
} finally {
if (response != null) {
response.close();
}
}
}
/**
* 签名
*
* @param time
* @param prepayId
* @param nonce
* @return
* @throws Exception
*/
@Override
public String signature(Long time, String prepayId, String nonce) throws Exception {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKey));
String text = String.format("%s\n%s\n%s\n%s\n", appId, time / 1000, nonce, prepayId);
log.info("signature text=>{}", text);
return sign(text, merchantPrivateKey);
}
/**
* 解码
*
* @param associated
* @param nonce
* @param ciphertext
* @return
* @throws Exception
*/
@Override
public String decrypt(String associated, String nonce, String ciphertext) throws Exception {
return new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)).decryptToString(associated.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
}
/**
* 关闭订单
*
* @param orderId
* @return
* @throws Exception
*/
@Override
public Boolean close(Long orderId) throws Exception {
String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close";
HttpPost httpPost = new HttpPost(String.format(url, orderId));
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
JSONObject reqBody = new JSONObject();
reqBody.put("mchid", merchantId);
httpPost.setEntity(new StringEntity(reqBody.toString(), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
log.info(response.toString());
// 返回204则表示成功
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.NO_CONTENT.value()) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}
private String sign(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
//用私钥对信息生成数字签名
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(dataBytes);
return Base64.getEncoder().encodeToString(signature.sign());
}
/**
* 退款申请
*
* @param order
* @param reason
* @return
* @throws Exception
*/
@Override
public Boolean refund(OrderInfo order, String reason) throws Exception {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
JSONObject amount = new JSONObject();
//原订单金额
amount.put("total", order.getPayment());
//退款金额
amount.put("refund", order.getPayment());
//退款币种
amount.put("currency", "CNY");
JSONObject reqBody = new JSONObject();
//退款结果回调url
reqBody.put("notify_url", notifyUrl);
//商户订单号
reqBody.put("out_trade_no", order.getOrderId());
//商户退款单号
reqBody.put("out_refund_no", order.getOrderId());
//退款原因,非必填
reqBody.put("reason", reason);
reqBody.put("amount", amount);
httpPost.setEntity(new StringEntity(reqBody.toString(), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
log.info(response.toString());
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject resBody = JSON.parseObject(bodyAsString);
log.info(resBody.toString());
return Boolean.TRUE;
}
}
调用示例
/**
* 创建微信订单
*
* @param userId 用户id
* @param request 请求参数
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public CreateOrderDTO createWxOrder(Long userId, CreateOrderRequest request) {
//商品id
Long goodsId = request.getGoodsId();
//根据用户id,商品id和订单状态查询历史订单
List<OrderInfo> list = orderMapper.query(new QueryOrderRequest()
.setUserId(userId)
.setGoodsId(goodsId)
.setStatus(OrderStatus.CODE_UNPAID));
try {
// 当不存在历史订单时新创建订单,否则获取历史订单继续支付
OrderInfo order;
if (CollectionUtils.isEmpty(list)) {
order = generateOrder(userId, goodsId);
} else {
order = list.get(0);
}
//预支付标识
String prepayId = order.getPrepayId();
//随机字符串
String nonce = RandomUtil.randomString(32);
long now = order.getGmtCreate().getTime();
//签名
String signature = wxPayService.signature(now, prepayId, nonce);
CreateOrderDTO dto = new CreateOrderDTO();
dto.setPrepayId(prepayId);
dto.setTimestamp(now / 1000);
dto.setNoncestr(nonce);
dto.setSign(signature);
dto.setOrderId(order.getOrderId());
return dto;
} catch (Exception e) {
throw new ServerException("创建订单失败");
}
}
/**
* 生成订单信息
*
* @param userId 用户id
* @param goodsId 商品id
* @return
*/
private OrderInfo generateOrder(Long userId, Long goodsId) throws Exception {
GoodsInfoDO goods = goodsMapper.getGoodsById(goodsId);
if (null == goods) {
throw new NotFoundException("商品不存在");
}
OrderInfo order = new OrderInfo();
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setOrderId(new IdWorker().nextId());
order.setGmtCreate(new Date());
order.setDescription(goods.getDescription());
order.setPayment(goods.getPrice());
// 微信预支付接口
String prepayId = wxPayService.prepay(order);
order.setPrepayId(prepayId);
orderMapper.addOrder(order);
return order;
}
/**
* 关闭订单
*
* @param orderId 订单id
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean closeOrder(Long orderId) throws Exception {
OrderInfo info = orderMapper.getOrderById(orderId);
if (info == null) {
throw new NotFoundException("订单不存在");
}
if(info.getStatus() != OrderStatus.CODE_UNPAID) {
throw new ServerException("状态不正确,不允许关闭");
}
info.setStatus(OrderStatus.CODE_CLOSED);
boolean flag = orderMapper.updateByOrderId(info) > 0;
log.info("更新订单[{}]状态结果:{}", info.getOrderId(), flag);
if (flag) {
// 调用微信接口关闭订单
try {
wxPayService.close(orderId);
} catch (Exception e) {
e.printStackTrace();
}
}
return flag;
}
/**
* 发起退款申请
*
* @param userId
* @param orderId 订单号
* @param reason 退款原因
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> refund(Long userId, Long orderId, String reason) {
OrderInfo info = new OrderInfo();
info.setOrderId(orderId);
info.setUserId(userId);
OrderInfo order = orderMapper.getOrderByIdAndUserId(info);
if (order == null || order.getId() == 0) {
throw new ServerException("订单未找到");
}
//申请退款
info.setStatus(OrderStatus.CODE_REFUNDING);
info.setMark(reason);
boolean flag = orderMapper.updateByOrderId(info) > 0;
if (flag) {
log.info("发起退款,开始调用wxpay的接口...");
flag = wxPayService.refund(info, reason);
log.info("调用wxpay退款接口结果:{}", flag);
}
return flag;
}
private static final String PAY_SUCCESS = "TRANSACTION.SUCCESS";
private static final String REFUND_SUCCESS = "REFUND.SUCCESS";
/**
* 回调接口
* @param request
**/
@Override
public Result<Boolean> callback(NotifyRequest request) {
NotifyResource resource = request.getResource();
String summary = request.getSummary();
String eventType = request.getEvent_type();
JSONObject notify;
try {
String msg = wxPayService.decrypt(resource.getAssociated_data(), resource.getNonce(), resource.getCiphertext());
notify = JSONObject.parseObject(msg);
} catch (Exception e) {
return Result.fail(e.getMessage());
}
TransactionSuccessNotify successNotify = notify.toJavaObject(TransactionSuccessNotify.class);
log.warn("notify = {}", successNotify);
// 标记订单状态
Long orderId = successNotify.getOut_trade_no();
OrderInfo order = orderMapper.getOrderById(orderId);
if (order == null) {
throw new NotFoundException("订单未找到");
}
switch (eventType) {
case PAY_SUCCESS: // 支付回调
order.setStatus(OrderStatus.CODE_PAID);
orderMapper.updateByOrderId(order);
return Result.success(Boolean.TRUE);
case REFUND_SUCCESS: // 退款回调
order.setStatus(OrderStatus.CODE_REFUNDED);
orderMapper.updateByOrderId(order);
return Result.success(Boolean.TRUE);
default:
log.error("不支持的通知: {}", eventType);
throw new NotFoundException("不支持的通知");
}
}
第二种:使用第三方库
引入必要的jar包pom.xml
<!-- 微信支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.4.0</version>
</dependency>
调用示例
/**
* 关闭订单
* @param orderId
**/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean closeOrder(Long orderId) throws WxPayException {
OrderInfo order = wxOrderInfoMapper.selectByOrderId(orderId);
if (order.getStatus() != OrderStatus.CODE_UNPAID) {
throw new ServerException("当前状态不允许关闭");
}
order.setStatus(OrderStatus.CODE_CLOSED);
Boolean res = wxOrderInfoMapper.updateStatusByOrderId(order);
if (res) {
// 调用微信的关闭订单接口
wxPayService.closeOrderV3(orderId.toString());
}
return res;
}
/**
* 预支付
*
* @param orderId
* @param openId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<OrderPrepayInfoDTO> toPay(String orderId, String openId) {
OrderInfo order = wxOrderInfoMapper.selectByOrderId(orderId);
JSONObject object = prePay(orderId, openId, order);
OrderPrepayInfoDTO prepay = new OrderPrepayInfoDTO();
prepay.setOrderId(orderId);
prepay.setAppId(object.getString("appId"));
prepay.setTimestamp(object.getString("timeStamp"));
prepay.setPackageValue(object.getString("packageValue"));
prepay.setSignType(object.getString("signType"));
prepay.setPaySign(object.getString("paySign"));
prepay.setNonceStr(object.getString("nonceStr"));
return Result.success(prepay);
}
/**
* 预支付订单
*
* @param orderId
* @param openId
* @param order
* @return
* @throws WxPayException
*/
private JSONObject prePay(String orderId, String openId, OrderInfo order) throws WxPayException {
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
payer.setOpenid(openId);
request.setPayer(payer);
WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
amount.setTotal(order.getPayment());
amount.setCurrency("CNY");
request.setAmount(amount);
request.setDescription(order.getDescription());
request.setOutTradeNo(orderId);
log.info("预支付订单请求-->{}", request.toString());
WxPayUnifiedOrderV3Result.JsapiResult orderV3 = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
return JSON.parseObject(JSON.toJSONString(orderV3));
}
/**
* 需要与退款回调分开写
* @param notify
**/
@Override
public Result<Boolean> payCallback(OriginNotifyResponse notify, HttpServletRequest request) {
log.info("Wechat pay callback");
try {
WxPayOrderNotifyV3Result v3Result = wxPayService.parseOrderNotifyV3Result(jsonStrSort(notify), getSignatureHeader(request));
WxPayOrderNotifyV3Result.DecryptNotifyResult result = v3Result.getResult();
log.info("DecryptNotifyResult=>{}", result.toString());
Long orderId = Long.valueOf(result.getOutTradeNo());
OrderInfo order = wxOrderInfoMapper.selectByOrderId(orderId);
if (order == null) {
throw new ClientErrorException(ErrorCode.INVALID_ORDER);
}
String tradeState = result.getTradeState();
log.info("tradeState=>{}", tradeState);
if ("SUCCESS".equals(tradeState)) {
// 修改订单状态为已支付
order.setStatus(OrderStatus.CODE_PAID);
// 付款时间
order.setPayTime(new Date());
wxOrderInfoMapper.updateStatusByOrderId(order);
// 将分享置为【1-已购买】状态
userService.updateUserShareStatus(order.getShareId(), 1);
return Result.success(Boolean.TRUE);
}
} catch (WxPayException e) {
log.error("WxPayException:", e);
return Result.fail(ResponseCode.ERROR_SERVER_EXCEPTION, e.getCustomErrorMsg());
}
return Result.fail();
}
/**
* 退款申请
* @param request 请求参数
* @param userId 用户id
**/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> refund(RefundOrderRequest request, Long userId) {
OrderInfo order = wxOrderInfoMapper.getOrderByUserIdAndOrderId(userId, Long.valueOf(request.getOrderId()));
if (order == null) {
throw new ResourceNotFoundException(ErrorCode.ORDER_NOT_FOUND);
}
order.setReason(request.getReason());
// 退款申请中
order.setStatus(WXOrderStatus.CODE_REFUNDING);
Boolean res = wxOrderInfoMapper.updateStatusByOrderId(order);
if (!res) {
throw new ServerErrorException(ErrorCode.INTERNAL_SERVER_ERROR);
}
WxPayRefundV3Result result = toRefund(order);
switch (result.getStatus()) {
case "SUCCESS":
log.info("退款成功");
order.setStatus(WXOrderStatus.CODE_REFUNDED);
wxOrderInfoMapper.updateStatusByOrderId(order);
break;
case "CLOSED":
log.info("退款关闭");
break;
case "PROCESSING":
log.info("退款处理中");
break;
case "ABNORMAL":
log.info("退款异常");
break;
default:
log.info("受理失败");
break;
}
return new Result<>(Boolean.TRUE, messageUtil.getMessage(ErrorCode.REFUND_REQUEST_RECEIVED));
}
}
/**
* 调用微信退款服务
*
* @param order
* @throws WxPayException
*/
private WxPayRefundV3Result toRefund(OrderInfo order) throws WxPayException {
WxPayRefundV3Request refundRequest = new WxPayRefundV3Request();
refundRequest.setOutTradeNo(order.getOrderId().toString());
refundRequest.setOutRefundNo(order.getOrderId().toString());
WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
amount.setTotal(order.getPayment());
amount.setCurrency("CNY");
amount.setRefund(order.getPayment());
refundRequest.setAmount(amount);
// 退款回调
refundRequest.setNotifyUrl(paymentConfig.getMa_refund_notifyUrl());
WxPayRefundV3Result result = wxPayService.refundV3(refundRequest);
log.info("toRefund result=>{}", result);
return result;
}
/**
* 需要与支付回调分开写,否则接收不到
**/
@Override
public Result<Boolean> refundCallback(OriginNotifyResponse notify, HttpServletRequest request) {
try {
WxPayRefundNotifyV3Result v3Result = wxPayService.parseRefundNotifyV3Result(jsonStrSort(notify), getSignatureHeader(request));
WxPayRefundNotifyV3Result.DecryptNotifyResult result = v3Result.getResult();
log.info("DecryptNotifyResult=>{}", result.toString());
OrderInfo order = wxOrderInfoMapper.selectByOrderId(result.getOutTradeNo());
if (order == null) {
throw new ClientErrorException(ErrorCode.INVALID_ORDER);
}
String refundStatus = result.getRefundStatus();
log.info("refundStatus=>{}", refundStatus);
if ("SUCCESS".equals(refundStatus)) {
// 修改订单状态为退款成功
order.setStatus(WXOrderStatus.CODE_REFUNDED);
wxOrderInfoMapper.updateStatusByOrderId(order);
return Result.success();
}
} catch (WxPayException e) {
log.error("WxPayException:", e);
}
return Result.fail();
}