小程序微信支付

This commit is contained in:
wangxiaopeng
2024-03-14 15:44:18 +08:00
parent a24a4dc4bd
commit 7642752bad
12 changed files with 602 additions and 3 deletions

View File

@@ -10,6 +10,12 @@
</server>
</servers>
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>nexus</id>
<name>collcollege</name>

View File

@@ -0,0 +1,40 @@
package com.cool.store.dto.wx;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 动态下单参数
*/
@Data
@ApiModel
public class PreOrderDTO {
/**
* 订单号(业务)
*/
@ApiModelProperty("订单号")
private String outTradeNo;
/**
* 用户openId
*/
@ApiModelProperty("用户openId")
private String wxOpenId;
/**
* 订单描述
*/
@ApiModelProperty("订单描述")
private String description;
/**
* 订单总金额,单位为分
*/
@ApiModelProperty("订单总金额")
private int total;
public PreOrderDTO(String outTradeNo, String wxOpenId, String description, int total) {
this.outTradeNo = outTradeNo;
this.wxOpenId = wxOpenId;
this.description = description;
this.total = total;
}
}

View File

@@ -109,6 +109,10 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,34 @@
package com.cool.store.config.weixin;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* @description 微信支付证书自动更新配置
* @date 2024-03-13 14:28
*/
@Configuration
public class WxPayAutoCertificateConfig {
@Resource
private WxPayConfig wxPayConfig;
/**
* 初始化商户配置
* 使用自动更新平台证书的RSA配置
* 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
* @return
*/
@Bean
public RSAAutoCertificateConfig rsaAutoCertificateConfig() {
RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
return config;
}
}

View File

@@ -0,0 +1,29 @@
package com.cool.store.config.weixin;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @description 微信支付配置类
* @date 2024-03-13 14:28
*/
@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig {
//mchid
private String merchantId;
/** 商户API私钥路径 */
private String privateKeyPath;
//商户证书序列号
private String merchantSerialNumber;
//商户APIv3密钥
private String apiV3Key;
//支付通知地址
private String payNotifyUrl;
//退款回调地址
private String backNotifyUrl;
}

View File

@@ -0,0 +1,70 @@
package com.cool.store.service;
import com.cool.store.dto.wx.PreOrderDTO;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
/**
* 微信支付V3
*/
public interface WxPayService {
/**
* JSAPI下单
*
* @return
*/
PrepayWithRequestPaymentResponse jsApiOrder(PreOrderDTO preOrderDTO);
/**
* 支付回调
*
* @param request
* @return
* @throws IOException
*/
ResponseEntity payNotify(HttpServletRequest request) throws IOException;
/**
* 根据商户订单号查询订单
*
* @param outTradeNo
* @return
*/
Transaction queryOrderByOutTradeNo(String outTradeNo);
/**
* 根据支付订单号查询订单
*
* @param paymentNo
* @return
*/
Transaction queryOrderByPaymentNo(String paymentNo);
/**
* 申请退款
* @param orderID
* @param outRefundNo
* @param backAmount
*/
void applyRefund(String orderID, String outRefundNo, BigDecimal backAmount);
ResponseEntity refundNotify(HttpServletRequest request) throws IOException;
/**
* 获取订单退款结果信息
* @param outRefundNo
* @return
*/
Refund getRefundOrderInfo(String outRefundNo);
/**
* 关闭订单
*
* @return
*/
void closePay(String outTradeNo);
}

View File

@@ -0,0 +1,273 @@
package com.cool.store.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.config.weixin.WxPayConfig;
import com.cool.store.dto.wx.PreOrderDTO;
import com.cool.store.service.WxPayService;
import com.cool.store.utils.HttpServletUtils;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
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.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
/**
* 微信支付相关
* @author
* @date 2024-03-13 14:28
*/
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {
@Resource
private RSAAutoCertificateConfig rsaAutoCertificateConfig;
@Resource
private WxPayConfig wxPayConfig;
@Value("${weixin.appId}")
private String wxAppId;
/**
* 微信支付
*/
@Override
@Transactional
public PrepayWithRequestPaymentResponse jsApiOrder(PreOrderDTO order) {
log.info("微信预支付下单:{}", JSONObject.toJSONString(order));
//请求微信支付相关配置
PrepayWithRequestPaymentResponse response = new PrepayWithRequestPaymentResponse();
try {
PrepayRequest request = new PrepayRequest();
request.setAppid(wxAppId);
request.setMchid(wxPayConfig.getMerchantId());
request.setDescription(order.getDescription());
request.setOutTradeNo(order.getOutTradeNo());
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
Amount amount = new Amount();
amount.setTotal(order.getTotal());
amount.setCurrency("CNY");
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(order.getWxOpenId());
request.setPayer(payer);
log.info("请求预支付下单,请求参数:{}", JSONObject.toJSONString(request));
// 调用预下单接口
response = getJsapiService().prepayWithRequestPayment(request);
log.info("订单【{}】发起预支付成功,返回信息:{}", order.getOutTradeNo(), response);
} catch (HttpException e) {
// 发送HTTP请求失败
log.error("微信下单发送HTTP请求失败错误信息{}", e.getHttpRequest());
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300例如500
log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
}
return response;
}
/**
* 微信回调
*
* @param request
* @return
* @throws IOException
*/
@Transactional
@Override
public ResponseEntity payNotify(HttpServletRequest request) throws IOException {
log.info("------收到支付通知------");
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(request.getHeader("Wechatpay-Serial"))
.nonce(request.getHeader("Wechatpay-Nonce"))
.signature(request.getHeader("Wechatpay-Signature"))
.timestamp(request.getHeader("Wechatpay-Timestamp"))
.signType(request.getHeader("Wechatpay-Signature-Type"))
.body(HttpServletUtils.getRequestBody(request))
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
// 以支付通知回调为例,验签、解密并转换成 Transaction
log.info("验签参数:{}", requestParam);
try {
// 验签、解密并转换成 Transaction返回参数对象
Transaction transaction = parser.parse(requestParam, Transaction.class);
log.info("验签成功!-支付回调结果:{}", JSON.toJSONString(transaction));
// TODO 处理你的业务逻辑
//修改订单前建议主动请求微信查询订单是否支付成功防止恶意post
//修改订单信息
// 处理成功,返回 200 OK 状态码
return ResponseEntity.status(HttpStatus.OK).build();
} catch (ValidationException e) {
log.error("sign verification failed", e);
log.error("微信支付回调v3java失败=" + e.getMessage(), e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
@Override
public Transaction queryOrderByOutTradeNo(String outTradeNo) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(wxPayConfig.getMerchantId());
request.setOutTradeNo(outTradeNo);
Transaction result = getJsapiService().queryOrderByOutTradeNo(request);
if (Transaction.TradeStateEnum.SUCCESS != result.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", result.getOutTradeNo(), result.getTransactionId());
}
// TODO 处理业务逻辑
return result;
}
@Override
public Transaction queryOrderByPaymentNo(String paymentNo) {
QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
queryRequest.setMchid(wxPayConfig.getMerchantId());
queryRequest.setTransactionId(paymentNo);
Transaction result = getJsapiService().queryOrderById(queryRequest);
if (Transaction.TradeStateEnum.SUCCESS != result.getTradeState()) {
log.info("内部订单号【{}】,微信支付订单号【{}】支付未成功", result.getOutTradeNo(), result.getTransactionId());
}
// TODO 处理业务逻辑
return result;
}
/**
* 申请退款
* @param orderID
* @param outRefundNo
* @param backAmount
*/
@Override
public void applyRefund(String orderID, String outRefundNo, BigDecimal backAmount) {
Transaction transaction = queryOrderByOutTradeNo(orderID);
CreateRequest request = new CreateRequest();
request.setTransactionId(transaction.getTransactionId());
request.setNotifyUrl(wxPayConfig.getBackNotifyUrl());
request.setOutTradeNo(transaction.getOutTradeNo());
request.setOutRefundNo(outRefundNo);
request.setReason("测试退款");
AmountReq amountReq = new AmountReq();
amountReq.setCurrency(transaction.getAmount().getCurrency());
amountReq.setTotal(Long.parseLong((transaction.getAmount().getTotal().toString())));
amountReq.setRefund((backAmount.multiply(new BigDecimal(100))).longValue());
request.setAmount(amountReq);
getRefundService().create(request);
}
@Override
public ResponseEntity refundNotify(HttpServletRequest request) throws IOException {
log.info("------收到退款回调通知------");
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(request.getHeader("Wechatpay-Serial"))
.nonce(request.getHeader("Wechatpay-Nonce"))
.signature(request.getHeader("Wechatpay-Signature"))
.timestamp(request.getHeader("Wechatpay-Timestamp"))
.signType(request.getHeader("Wechatpay-Signature-Type"))
.body(HttpServletUtils.getRequestBody(request))
.build();
// 如果已经初始化了 RSAAutoCertificateConfig可以直接使用 config
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
log.info("验签参数:{}", requestParam);
try {
// 验签、解密并转换成 Refund
Refund refund = parser.parse(requestParam, Refund.class);
log.info("验签成功!-退款回调结果:{}", JSON.toJSONString(refund));
//记录日志信息
Status state = refund.getStatus();
String orderID = refund.getOutTradeNo();
String backID = refund.getOutRefundNo();
System.out.println("订单ID" + orderID);
System.out.println("退款ID" + backID);
if (state == Status.PROCESSING) {
//TODO------
//根据自己的需求处理相应的业务逻辑,异步
//通知微信回调成功
return ResponseEntity.status(HttpStatus.OK).build();
} else if (state == Status.SUCCESS) {
//TODO------
//根据自己的需求处理相应的业务逻辑,异步
//通知微信回调成功
return ResponseEntity.status(HttpStatus.OK).build();
} else {
System.out.println("微信回调失败,JsapiPayController.Refund" + state.toString());
//通知微信回调失败
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
} catch (ValidationException e) {
log.error("sign verification failed", e);
log.error("微信支付回调v3java失败=" + e.getMessage(), e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
/**
* 获取订单退款结果信息
* @param outRefundNo
* @return
*/
@Override
public Refund getRefundOrderInfo(String outRefundNo) {
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(outRefundNo);
// 调用request.setXxx(val)设置所需参数具体参数可见Request定义
// 调用接口
return getRefundService().queryByOutRefundNo(request);
}
/**
* 关闭微信支付
*/
@Override
public void closePay(String outTradeNo) {
CloseOrderRequest closeRequest = new CloseOrderRequest();
closeRequest.setMchid(wxPayConfig.getMerchantId());
closeRequest.setOutTradeNo(outTradeNo);
getJsapiService().closeOrder(closeRequest);
}
/**
* 创建小程序支付服务
* @return
*/
protected JsapiServiceExtension getJsapiService() {
JsapiServiceExtension jsapiServiceExtension =
new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA") // 不填默认为RSA
.build();
return jsapiServiceExtension;
}
protected RefundService getRefundService() {
RefundService refundService = new RefundService.Builder()
.config(rsaAutoCertificateConfig)
.build();
return refundService;
}
}

View File

@@ -0,0 +1,40 @@
package com.cool.store.utils;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author
* @date 2023/07/26 上午9:17
**/
public class HttpServletUtils {
/**
* 获取请求体
*
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
}

View File

@@ -14,7 +14,6 @@ import javax.annotation.Resource;
import javax.validation.Valid;
/**
* @author zhangchenbiao
* @FileName: MiniProgramAppController
* @Description:
* @date 2023-05-29 14:28
@@ -22,7 +21,7 @@ import javax.validation.Valid;
@Api(tags = "微信小程序app接口")
@RestController
@RequestMapping({"/v1/partnerManage/miniProgram" })
@RequestMapping({"/mini/miniProgram" })
public class MiniProgramAppController {
@Resource

View File

@@ -0,0 +1,90 @@
package com.cool.store.controller.webc;
import com.cool.store.dto.wx.PreOrderDTO;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.WxPayService;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Map;
/**
* @FileName: WechatPayController
* @Description:
* @date 2024-03-13 14:28
*/
@Slf4j
@Api(tags = "微信小程序支付接口")
@RestController
@RequestMapping({"/mini/wechatPay"})
public class WechatPayController {
@Resource
private WxPayService wxPayService;
@ApiOperation(value = "预支付订单", notes = "预支付订单")
@PostMapping("/create")
public ResponseResult<PrepayWithRequestPaymentResponse> createOrder(@Validated @RequestBody PreOrderDTO preOrderDTO) {
//创建初始化订单
PrepayWithRequestPaymentResponse response = wxPayService.jsApiOrder(preOrderDTO);
//更新订单状态
return ResponseResult.success(response);
}
@ApiOperation("根据商户订单号查询订单")
@GetMapping("/queryOrderByOutTradeNo")
public ResponseResult<Transaction> queryOrderByOutTradeNo(@RequestParam(value = "outTradeNo",required = true)String outTradeNo) {
return ResponseResult.success(wxPayService.queryOrderByOutTradeNo(outTradeNo));
}
@ApiOperation("根据支付订单号查询订单")
@GetMapping("/queryOrderByOutTradeNo")
public ResponseResult<Transaction> queryOrderByPaymentNo(@RequestParam(value = "paymentNo",required = true)String paymentNo) {
return ResponseResult.success(wxPayService.queryOrderByPaymentNo(paymentNo));
}
@ApiOperation(value = "预支付-回调")
@PostMapping("/payNotify")
public ResponseEntity payNotify(HttpServletRequest request) throws IOException {
ResponseEntity responseEntity = wxPayService.payNotify(request);
return responseEntity;
}
@ApiOperation(value = "退款申请")
@PostMapping("/applyRefund")
public ResponseResult applyRefund(@RequestBody Map<String, Object> params){
String orderID = String.valueOf(params.get("orderID"));
String backID = String.valueOf(params.get("backID"));
BigDecimal backAmount = new BigDecimal(String.valueOf(params.get("backAmount")));
wxPayService.applyRefund(orderID, backID, backAmount);
return ResponseResult.success(true);
}
@ApiOperation(value = "退款回调")
@PostMapping("/refundNotify")
public ResponseEntity refundNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
ResponseEntity responseEntity = wxPayService.refundNotify(request);
return responseEntity;
}
@GetMapping("/getRefundOrderInfo")
@ApiOperation("退款查询")
public ResponseResult<Refund> getRefundOrderInfo(@RequestParam(value = "outRefundNo",required = true)String outRefundNo){
Refund refund = wxPayService.getRefundOrderInfo(outRefundNo);
return ResponseResult.success(refund);
}
}

View File

@@ -50,4 +50,11 @@ server.connection-timeout=18000000
server.tomcat.basedir=/tmp/tomcat/partner-b
log4j2.formatMsgNoLookups=true
mybatis.configuration.variables.enterpriseId=e17cd2dc350541df8a8b0af9bd27f77d
mybatis.configuration.variables.enterpriseId=e17cd2dc350541df8a8b0af9bd27f77d
wx.pay.merchantId=1670560201
wx.pay.privateKeyPath=/home/admin/apiclient/apiclient_key.pem
wx.pay.merchantSerialNumber=66B8E966AFE796BA06006664FCBFBC3F0E2F5A1B
wx.pay.apiV3Key=wxpayzhenghu123JKJHkjafWXCertUt1
wx.pay.payNotifyUrl=https://abstore-api.coolstore.cn/xfsg/mini/wechatPay/payNotify
wx.pay.backNotifyUrl=https://abstore-api.coolstore.cn/xfsg/mini/wechatPay/backNotify

View File

@@ -221,6 +221,13 @@
<artifactId>logback-kafka-appender</artifactId>
<version>0.2.0-RC2</version>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.11</version>
</dependency>
</dependencies>
</dependencyManagement>