支付
1.小程序支付
1.1 前置条件
申请相关权限
1.2 接口规则
1.3 接口参数
1.3.1 商品号保存
- sql
CREATE TABLE "shop_mch_info" (
"id" int8 NOT NULL DEFAULT nextval('shop_payment_info_id_seq'::regclass),
"shop_id" int8 NOT NULL,
"wx_mch_id" varchar(255) COLLATE "pg_catalog"."default",
"wx_mch_serial_no" varchar(255) COLLATE "pg_catalog"."default",
"wx_api_key" varchar(255) COLLATE "pg_catalog"."default",
"wx_private_key" varchar(5000) COLLATE "pg_catalog"."default",
"wx_pay_notify_url" varchar(255) COLLATE "pg_catalog"."default",
"wx_refund_notify_url" varchar(255) COLLATE "pg_catalog"."default",
"wx_key_path" varchar COLLATE "pg_catalog"."default",
"device_id" varchar COLLATE "pg_catalog"."default",
"orgunit_id" varchar COLLATE "pg_catalog"."default"
);
COMMENT ON COLUMN "shop_mch_info"."shop_id" IS '业务门店ID';
COMMENT ON COLUMN "shop_mch_info"."wx_mch_id" IS '微信商户号ID';
COMMENT ON COLUMN "shop_mch_info"."wx_mch_serial_no" IS '微信证书序列号';
COMMENT ON COLUMN "shop_mch_info"."wx_api_key" IS '微信api秘钥';
COMMENT ON COLUMN "shop_mch_info"."wx_private_key" IS '微信证书私钥';
COMMENT ON COLUMN "shop_mch_info"."wx_pay_notify_url" IS '微信支付回调通知地址';
COMMENT ON COLUMN "shop_mch_info"."wx_refund_notify_url" IS '微信退款回调通知地址';
COMMENT ON COLUMN "shop_mch_info"."orgunit_id" IS '业务门店ID';
COMMENT ON TABLE "shop_mch_info" IS '门店商户号相关信息';
1.3.2 配置类
- WxPayConfigHolder
@Slf4j
@Component
public class WxPayConfigHolder {
private static final Map<Long, RSAAutoCertificateConfig> configMap = new ConcurrentHashMap<>();
@Autowired
private CafeShopMchInfoService cafeShopMchInfoService;
@Autowired
private WxPayV3Bean wxPayV3Bean;
@PostConstruct
public void init() {
try {
// 为每个商户号创建一个RSAAutoCertificateConfig
List<CafeShopMchInfoEntity> list = cafeShopMchInfoService.list();
boolean failed = false;
try {
for (CafeShopMchInfoEntity mchInfoEntity : list) {
String wxMchId = mchInfoEntity.getWxMchId();
if (wxMchId == null) {
continue;
}
String privateKey = mchInfoEntity.getWxPrivateKey().replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
String wxMchSerialNo = mchInfoEntity.getWxMchSerialNo();
String apiV3Key = mchInfoEntity.getWxApiKey();
RSAAutoCertificateConfig config =
new RSAAutoCertificateConfig.Builder()
.merchantId(wxMchId)
.privateKey(privateKey)
.merchantSerialNumber(wxMchSerialNo)
.apiV3Key(apiV3Key)
.build();
configMap.put(mchInfoEntity.getShopId(), config);
log.info("不使用代理初始化微信支付配置成功,shopId: {}", mchInfoEntity.getShopId());
}
} catch (HttpException e) {
log.error("不使用代理初始化微信支付配置出错:{}", e.getMessage());
}
}
public RSAAutoCertificateConfig get(Long shopId) {
return configMap.get(shopId);
}
}
- WxPayV3Bean
@Component
@ConfigurationProperties(prefix = "wechat")
@Data
@ToString
public class WxPayV3Bean {
//小程序appid
private String appId;
//商户号
private String mchId;
//证书序列号
private String mchSerialNo;
//小程序秘钥
private String appSecret;
//api秘钥
private String apiKey;
//回调接口地址
private String notifyUrl;
//证书地址
private String keyPath;
// 代理地址
private String proxyUrl;
}
1.4 流程
- 调用提交订单接口获取订单ID (自己业务)
- 调用微信预支付接口获取prepay_id等参数
- 小程序拉起微信支付(小程序能力)等待用户付款
- 轮询订单支付查询接口查询付款结果
1.5 代码
1.5.1 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.2.12</version>
</dependency>
1.5.2 预支付
WXPayOrderReqVO req = new WXPayOrderReqVO();
req.setMchId(mchInfo.getWxMchId());
req.setMchSerialNo(mchInfo.getWxMchSerialNo());
req.setApiKey(mchInfo.getWxApiKey());
req.setKeyPath(mchInfo.getWxPrivateKey());
req.setNotifyUrl(mchInfo.getWxPayNotifyUrl());
req.setOpenId(user.getOpenid());
req.setTradeNo(orderCode);
req.setDescription(sj);
req.setAmount(orderEntity.getTotalAmount().multiply(new BigDecimal(100)).intValue());
req.setOrderType(OrderType.MINI_PROGRAM.getDesc());
req.setShopId(shopId);
req.setAttach(shopEntity.getName());
@Override
public WXPayOrderResVO createOrder(WXPayOrderReqVO req) throws NoSuchAlgorithmException, SignatureException, IOException, URISyntaxException, InvalidKeyException {
String appId = wxPayV3Bean.getAppId();
// 使用自动更新平台证书的RSA配置,配置微信支付的自动证书管理功能
RSAAutoCertificateConfig config = wxPayConfigHolder.get(req.getShopId());
// 构建service,用于处理JSAPI支付相关的操作
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
// 创建预支付订单的请求对象
PrepayRequest prepayRequest = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(req.getAmount());
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appId);
prepayRequest.setMchid(req.getMchId());
prepayRequest.setNotifyUrl(req.getNotifyUrl());
prepayRequest.setDescription(req.getDescription());
prepayRequest.setOutTradeNo(req.getTradeNo());
prepayRequest.setAttach(req.getAttach());
Date dateFromNow = DateUtil.getDateFromNow(Calendar.MINUTE, 5); // 5分钟后关闭订单
prepayRequest.setTimeExpire(DateUtil.formatWithTimezone(dateFromNow));
Payer payer = new Payer();
payer.setOpenid(req.getOpenId());
prepayRequest.setPayer(payer);
// 调用下单方法,得到应答
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(prepayRequest);
log.info("微信下单方法请求返回:" + response);
// 自定义返回类
WXPayOrderResVO vo = new WXPayOrderResVO();
return vo;
}
1.5.3 查询
- PaymentStatusEnum
SUCCESS(1, "支付成功"),
REFUND(2, "已退款"),
NOTPAY(3, "未支付"),
CLOSED(4, "已关闭"),
REVOKED(5, "已撤销"),
USERPAYING(6, "用户支付中"),
PAYERROR(7, "支付失败"),
FINISHED(8, "交易结束,不可退款(支付宝在超过退款期限之后会推送这个状态)"),
REFUNDING(9, "退款中"), // 直营专用的退款状态
REFUNDAPPLY(10,"退款申请(待审批)"),
UNKNOWN(99, "未知状态");
- 查询逻辑
// 业务逻辑
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(mchInfo.getWxMchId());
queryRequest.setOutTradeNo(orderCode);
RSAAutoCertificateConfig config = wxPayConfigHolder.get(shopId);
// 构建service,用于处理JSAPI支付相关的操作
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
com.wechat.pay.java.service.payments.model.Transaction result = service.queryOrderByOutTradeNo(queryRequest);
log.info("微信查询订单返回:{}", JSONObject.toJSONString(result));
PaymentStatus paymentStatus = PaymentStatus.valueOf(result.getTradeState().toString());
// 业务逻辑
1.5.4 退款
@Override
public PaymentRefundVO refund(OrderRefundVO vo) {
Long shopId = vo.getShopId();
BigDecimal totalAmount = vo.getTotalAmount();
String orderCode = vo.getOrderCode();
RSAAutoCertificateConfig config = wxPayConfigHolder.get(shopId);
// 构建退款service
RefundService service = new RefundService.Builder().config(config).build();
//构建退款请求
CreateRequest request = new CreateRequest();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
//构建订单金额信息
AmountReq amountReq = new AmountReq();
//退款金额
long refundAmount = totalAmount.multiply(new BigDecimal(100)).longValue();
amountReq.setRefund(refundAmount);
//原订单金额
amountReq.setTotal(refundAmount);
//货币类型(默认人民币)
amountReq.setCurrency("CNY");
request.setAmount(amountReq);
request.setOutTradeNo(orderCode);
request.setReason(vo.getReason());
//商户退款单号
request.setOutRefundNo(orderCode);
//退款通知回调地址
request.setNotifyUrl(vo.getRefundNotifyUrl());
// 调用微信sdk退款接口
Refund refund = service.create(request);
log.info("微信请求退款返回:" + refund);
PaymentRefundVO refundVO = new PaymentRefundVO();
refundVO.setOrderCode(orderCode);
refundVO.setPaymentCode(refund.getTransactionId());
refundVO.setRefundCode(refund.getRefundId());
refundVO.setRefundAmount(new BigDecimal(refund.getAmount().getPayerRefund()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
Status status = refund.getStatus();
if (status.equals(Status.SUCCESS)) {
refundVO.setRefundStatus(RefundStatus.SUCCESS.getCode());
} else if (status.equals(Status.ABNORMAL)) {
refundVO.setRefundStatus(RefundStatus.ABNORMAL.getCode());
} else if (status.equals(Status.CLOSED)) {
refundVO.setRefundStatus(RefundStatus.CLOSED.getCode());
} else if (status.equals(Status.PROCESSING)) {
refundVO.setRefundStatus(RefundStatus.PROCESSING.getCode());
}
return refundVO;
}