官网:https://github.com/wechatpay-apiv3/wechatpay-java
一:引入依赖
我用的是微信官方推荐的java版本的SDK,maven坐标如下
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
二:准备微信支付所必要的商户参数,具体如下:
xxx:
pay:
wechat-pay:
#商户号
merchantId: xxxxxxx
#商户API私钥(很长)
privateKey: xxxxxxx...
#商户API公钥 (也很长)
publicKey: xxxxxx....
#微信支付的商户平台证书序列号
merchantSerialNo: xxxxxxxxxxx
#微信公众号ID
appid: xxxxxxxxx
#支付回调地址,必须是外网可访问的,文中会提供对应工具
notify_url: https://xxxxx
#支付回调地址,同上,我写成了两个接口分开处理业务
refunds_notify_url: https://xxxxxxx
#秘钥, 申请微信支付商户平台证书的时候能看见
apiV3Key: xxxxxxx
#域名
domain: https://api.mch.weixin.qq.com
商户api证书:
申请完成之后商户api证书会下载成压缩包,解压之后就是上图的文件了,其中apiclient_key.pem就是商户私钥, 另外一个就是公钥了,具体都是做什么的可以去看官网或者其他文章
私钥的加载方式有两种
1.一种是将私钥中的字符串直接复制到代码中(或者.yaml配置文件中),切记:不能有换行.
2.第二种是用加载文件的方式, 将api证书文件存到你的电脑或者服务器上,在配置文件中分别记录对应的位置即可,这种方式的具体加载方式看官网即可.
三:编写配置类WechatPayConfig.java
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.billdownload.BillDownloadServiceExtension;
import com.wechat.pay.java.service.payments.app.AppServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.refund.RefundService;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.List;
/**
* @author: wxj
* @Desc:
* @create: 2023-11-06 13:15:34
**/
@Data
@Configuration
@NoArgsConstructor
public class WechatPayConfig {
@Value("${}")
private String merchantId;
@Value("${}")
private String privateKey;
@Value("${}")
private String publicKey;
@Value("${}")
private String serialNo;
@Value("${}")
private String appid;
@Value("${}")
private String notifyUrl;
@Value("${}")
private String apiV3Key;
@Value("${}")
private String domain;
@Value("${}")
private String refundNotifyUrl;
/**
* 初始化最基础的商户配置
*
* @return Config
*/
@Bean
@Order(0)
public Config initConfig() {
return new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKey(privateKey)
.merchantSerialNumber(serialNo)
.apiV3Key(apiV3Key)
.build();
}
/**
* 初始化小程序需要的SDK支持服务
*
* @return JsapiService
*/
@Bean
@Order(1)
public JsapiServiceExtension initJsapiService() {
Config config = SpringUtils.getBean(Config.class);
return new JsapiServiceExtension.Builder().config(config).build();
}
/**
* 初始化安卓app需要的SDK支持服务
*
* @return AppService
*/
@Bean
@Order(2)
public AppServiceExtension initAppService() {
Config config = SpringUtils.getBean(Config.class);
return new AppServiceExtension.Builder().config(config).build();
}
/**
* 初始化退款服务需要的SDK支持服务
*
* @return RefundService
*/
@Bean
@Order(3)
public RefundService initRefundService() {
Config config = SpringUtils.getBean(Config.class);
return new RefundService.Builder().config(config).build();
}
@Bean
@Order(4)
public BillDownloadServiceExtension initBillDownloadServiceExtension() {
Config config = SpringUtils.getBean(Config.class);
return new BillDownloadServiceExtension.Builder().config(config).build();
}
/**
* 加载私钥
*/
public PrivateKey getPrivateKey() {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
} catch (Exception e) {
throw new RuntimeException("加载私钥异常,请检查私钥文件", e);
}
}
}
业务代码就使用小程序支付代码为例
四:下单相关接口(4个)
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONObject;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Slf4j
@Component
public class WechatAppletPayService {
@Resource
private Config config;
@Resource
private JsapiServiceExtension jsapiService;
@Resource
private RefundService refundService;
@Resource
private WechatPayConfig payConfig;
private final ReentrantLock lock = new ReentrantLock();
/**
* 创建订单
*
* @param orderBo 订单参数对象
* @return
*/
public R<PrepayWithRequestPaymentResponse> createOrder(CourseOrderBo orderBo) {
// 创建未支付订单对象并插入数据库, 具体根据需求实现
// Order 创建的订单对象 cusWechat用户在咱们小程序对应的对象, 主要需要用户的openId,openId是某个微信用户在第一次进小程序的时候就分配的唯一ID,不会变,还是不知道openId是啥的去百度搜吧
return this.pushOrder(order, cusWechat);
}
/**
* 拉起支付
*
* @param orderBo
* @param cusWechat
* @return
*/
public R<PrepayWithRequestPaymentResponse> pushOrder(CourseOrderBo orderBo, CusWechat cusWechat) {
String appid = cusWechat.getAppid();
String openid = cusWechat.getOpenid();
PrepayRequest request = new PrepayRequest();
request.setMchid(payConfig.getMerchantId());
request.setAppid(appid);
request.setDescription(orderBo.getGoodsName());
request.setOutTradeNo(orderBo.getOrderId());
// 2023-11-30T15:45:48+08:00
String timeExpire = DateUtil.offsetMinute(DateUtil.date(), 10).toString().replace(" ", "T") + "+08:00";
request.setTimeExpire(timeExpire);
JSONObject attach = new JSONObject(1);
attach.put("couponCode", orderBo.getCouponCode());
request.setAttach(attach.toString());
request.setNotifyUrl(String.format(payConfig.getNotifyUrl(), WechatConstants.SourcePathEnum.PAY_FROM_APPLET));
Amount amount = new Amount();
amount.setTotal(orderBo.getTotalAmount().intValue());
amount.setCurrency("CNY");
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(openid);
request.setPayer(payer);
PrepayWithRequestPaymentResponse prepay = jsapiService.prepayWithRequestPayment(request);
return R.ok(prepay);
}
/**
* 通过微信交易ID查询订单
*
* @param id
* @param transactionId
* @return
*/
public R getOrderByTransactionId(Long id, String transactionId) {
QueryOrderByIdRequest request = new QueryOrderByIdRequest();
request.setMchid(payConfig.getMerchantId());
request.setTransactionId(transactionId);
Transaction transactionResp = jsapiService.queryOrderById(request);
Transaction.TradeStateEnum tradeState = transactionResp.getTradeState();
CourseTransactionVo transactionVo = transactionService.queryById(id);
}
public R getOrderByOrderId(String orderId) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(payConfig.getMerchantId());
request.setOutTradeNo(orderId);
Transaction transactionResp = jsapiService.queryOrderByOutTradeNo(request);
String appid = transactionResp.getAppid();
String openId = transactionResp.getPayer().getOpenid();
Long aLong = transactionResp.getAmount().getTotal().longValue();
Long aLong1 = transactionResp.getAmount().getPayerTotal().longValue();
String transactionId = transactionResp.getTransactionId();
Transaction.TradeStateEnum tradeState = transactionResp.getTradeState();
// 具体业务自行处理
return R.ok();
}
@Transactional(rollbackFor = Exception.class)
public R<String> closeOrder(String orderId) {
try {
CloseOrderRequest request = new CloseOrderRequest();
request.setMchid(payConfig.getMerchantId());
request.setOutTradeNo(orderId);
jsapiService.closeOrder(request);
// 具体业务自行处理
return R.ok();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
这里包含了下单,根据商户订单号查询微信支付订单,根据微信支付订单号查询微信支付订单以及关闭订单四个接口,具体业务代码需要兄弟你自己根据你们项目需求写.
五:退款相关接口(2个)
import cn.hutool.core.util.EnumUtil;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author: wxj
* @Desc: 微信支付退款服务类
* @create: 2023-12-06 17:17:33
**/
@Service
public class WechatRefundService {
@Resource
private WechatPayConfig payConfig;
@Resource
private RefundService refundService;
/**
* 后台向微信支付申请退款
*
* @param bo 参数对象
* orderId 退款的订单ID
* refundAmount 实际退款金额(由相关人员与客户商谈)
* refundReason 退款理由
* @return 处理结果
*/
public R<String> refundsOrder(CourseOrderBo bo) {
try {
// 具体业务自行实现
CreateRequest request = new CreateRequest();
request.setReason(reasonEnum.getReason());
Integer platform = order.getPlatform();
WechatConstants.PlatformEnum platformEnum = EnumUtil.likeValueOf(WechatConstants.PlatformEnum.class, platform);
AmountReq amount = new AmountReq();
amount.setRefund(refundAmount);
amount.setTotal(order.getTotalAmount());
amount.setCurrency("CNY");
request.setAmount(amount);
request.setOutRefundNo(refundTransaction.getId().toString());
request.setOutTradeNo(orderId);
Refund refund = refundService.create(request);
Status status_wx = refund.getStatus();
String refundId_wx = refund.getRefundId();
String transactionId_wx = refund.getTransactionId();
String orderId_wx = refund.getOutTradeNo();
com.wechat.pay.java.service.refund.model.Amount amount_wx = refund.getAmount();
long total_wx = amount_wx.getTotal();
long refundAmount_wx = amount_wx.getRefund();
Long playTotal_wx = amount_wx.getPayerTotal();
refundTransaction.setTransactionId(transactionId_wx);
refundTransaction.setPayAmount(playTotal_wx);
refundTransaction.setTotalAmount(total_wx);
refundTransaction.setOrderId(orderId_wx);
refundTransaction.setRefundId(refundId_wx);
transactionService.updateByBo(BeanCopyUtils.copy(refundTransaction, CourseTransactionBo.class));
return R.ok("申请成功, 请注意查收退款记录");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public R getRefundsOrder(Long id) {
try {
// 具体业务自行处理 此处ID为商户系统流水ID
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setSubMchid(payConfig.getMerchantId());
request.setOutRefundNo(transaction.getId().toString());
Refund refundResp = refundService.queryByOutRefundNo(request);
return R.fail();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
六:下载账单类接口(2个)
import cn.hutool.core.date.DateUtil;
import com.wechat.pay.java.service.billdownload.BillDownloadServiceExtension;
import com.wechat.pay.java.service.billdownload.DigestBillEntity;
import com.wechat.pay.java.service.billdownload.model.AccountType;
import com.wechat.pay.java.service.billdownload.model.BillType;
import com.wechat.pay.java.service.billdownload.model.GetFundFlowBillRequest;
import com.wechat.pay.java.service.billdownload.model.GetTradeBillRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
/**
* @author: wxj
* @Desc: 微信支付下载服务类
* @create: 2023-12-06 17:37:49
**/
@Slf4j
@Service
public class WechatBillDownloadService {
@Resource
private BillDownloadServiceExtension downloadService;
/**
* @param billDate 日期 不填默认昨天
* @param billTypeStr 导出内容类型
* ALL: 返回当日所有订单信息(不含充值退款订单)
* SUCCESS: 返回当日成功支付的订单(不含充值退款订单)
* REFUND: 返回当日退款订单(不含充值退款订单)
* RECHARGE_REFUND: 返回当日充值退款订单
* ALL_SPECIAL: 返回个性化账单当日所有订单信息
* SUC_SPECIAL: 返回个性化账单当日成功支付的订单
* REF_SPECIAL: 返回个性化账单当日退款订单
* @param tarType 压缩类型 不填默认ZGIP
* @return 流
*/
public R<InputStream> exportTransactions(String billDate, String tarType, String billTypeStr, HttpServletResponse resp) {
if (StringUtils.isEmpty(billDate)) {
billDate = DateUtil.format(DateUtil.yesterday(), "yyyy-MM-DD");
}
BillType billType = BillType.ALL;
try {
billType = BillType.valueOf(billTypeStr);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
GetTradeBillRequest request = new GetTradeBillRequest();
request.setBillDate(billDate);
request.setBillType(billType);
request.setTarType(null);
DigestBillEntity tradeBill = downloadService.getTradeBill(request);
return R.ok(tradeBill.getInputStream());
}
public R<InputStream> exportFundflow(String billDate, String accountTypeStr, HttpServletResponse resp) {
if (StringUtils.isEmpty(billDate)) {
billDate = DateUtil.format(DateUtil.yesterday(), "yyyy-MM-DD");
}
AccountType accountType = AccountType.ALL;
try {
accountType = AccountType.valueOf(accountTypeStr);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
GetFundFlowBillRequest request = new GetFundFlowBillRequest();
request.setBillDate(billDate);
request.setTarType(null);
request.setAccountType(accountType);
DigestBillEntity fundFlowBill = downloadService.getFundFlowBill(request);
return R.ok(fundFlowBill.getInputStream());
}
}
安卓app的接口大差不差也都这样,不知道方法或者参数接收的兄弟可以看SDK源码,实在找不到idea不能搜索jar中的代码,建议反编译之后再用idea打开搜索,毕竟我就是这么做的...
这些接口暂时还没完整测试, 过段时间测通过上线了再把支付宝的也分享出来...