赞
踩
这是整体的一个微信支付+退款的项目结构。
如果大家需要的话,我可以等确认回调没有问题以后可以把我这个项目中完整的jsapi支付及退款做一个demo。或者按照图片的顺序把代码贴进来。
1.获取商户号
微信商户平台 申请成为商户 => 提交资料 => 签署协议 => 获取商户号
2.获取AppID
微信公众平台 注册服务号 => 服务号认证 => 获取APPID => 绑定商户号
3.申请商户证书
登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 申请API证书 包括商户证书和商户私钥
4.获取微信的证书
获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 设置APIv3密钥
1.引入pom.xml
- <!-- 微信支付 -->
- <dependency>
- <groupId>com.github.wechatpay-apiv3</groupId>
- <artifactId>wechatpay-apache-httpclient</artifactId>
- <version>0.4.2</version>
- </dependency>
- <dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <version>2.8.9</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.12.0</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.78</version>
- </dependency>
2.配置application.yml
- # 微信相关配置
- vx:
- # appid
- appId: appid
- # 小程序密钥
- secret: 小程序密钥
- # 商户号
- mchId: 商户号
- # 证书序列号
- mchSerialNo: 证书序列号
- # api密钥
- apiKey: api密钥
- # 证书地址
- keyPath: D:/wxPayPem/apiclient_key.pem
- certPath: D:/wxPayPem/apiclient_cert.pem
- certP12Path: D:/wxPayPem/apiclient_cert.p12
说明:这个证书地址是在微信商户平台内生成下载的API证书文件
具体教程:什么是商户API证书?如何获取商户API证书?
3.都配置完了之后创建对应的配置实体。
- import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
- import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
- import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
- import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
- import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
- import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
- import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
- import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
- import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
- import lombok.Data;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.security.GeneralSecurityException;
- import java.security.PrivateKey;
-
- /**
- * <p>
- * 配置信息实体
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/26 15:31
- */
- @Configuration
- @Data
- public class WxPayConfig {
- /**
- * appid
- */
- @Value("${vx.appId}")
- private String appId;
- /**
- * 小程序密钥
- */
- @Value("${vx.secret}")
- private String secret;
- /**
- * 商户号
- */
- @Value("${vx.mchId}")
- private String mchId;
- /**
- * 证书序列号
- */
- @Value("${vx.mchSerialNo}")
- private String mchSerialNo;
- /**
- * api密钥
- */
- @Value("${vx.apiKey}")
- private String apiKey;
-
- /**
- * 证书地址
- */
- @Value("${vx.keyPath}")
- private String keyPath;
-
-
- /**
- * 获取商户的私钥文件
- *
- * @param filename 证书地址
- * @return 私钥文件
- */
- public PrivateKey getPrivateKey(String filename) {
- try {
- return PemUtil.loadPrivateKey(new FileInputStream(filename));
- } catch (FileNotFoundException e) {
- throw new RuntimeException("私钥文件不存在");
- }
- }
-
-
- /**
- * 获取签名验证器
- */
- @Bean
- public Verifier getVerifier() {
- // 获取商户私钥
- final PrivateKey privateKey = getPrivateKey(keyPath);
-
- // 私钥签名对象
- PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
-
- // 身份认证对象
- WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
-
- // 获取证书管理器实例
- CertificatesManager certificatesManager = CertificatesManager.getInstance();
-
- try {
- // 向证书管理器增加需要自动更新平台证书的商户信息
- certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiKey.getBytes(StandardCharsets.UTF_8));
- } catch (IOException | GeneralSecurityException | HttpCodeException e) {
- e.printStackTrace();
- }
-
- try {
- return certificatesManager.getVerifier(mchId);
- } catch (NotFoundException e) {
- e.printStackTrace();
- throw new RuntimeException("获取签名验证器失败");
- }
- }
-
- /**
- * 获取微信支付的远程请求对象
- * @return Http请求对象
- */
- @Bean
- public CloseableHttpClient getWxPayClient() {
-
- // 获取签名验证器
- Verifier verifier = getVerifier();
-
- // 获取商户私钥
- final PrivateKey privateKey = getPrivateKey(keyPath);
-
- WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey)
- .withValidator(new WechatPay2Validator(verifier));
-
- return builder.build();
- }
-
- }
4.创建一个枚举类 type用到了什么就写什么,或者不创建枚举类都可以。
- import lombok.AllArgsConstructor;
- import lombok.Getter;
-
- /**
- * <p>
- * 请求地址枚举类
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/26 15:41
- */
- @AllArgsConstructor
- @Getter
- public enum WxApiConstants {
-
- /**
- * jsapi下单
- */
- JSAPI_PAY("/v3/pay/transactions/jsapi"),
-
-
- /**
- * 申请退款
- */
- DOMESTIC_REFUNDS("/v3/refund/domestic/refunds");
-
- /**
- * 类型
- */
- private final String type;
-
-
- public String getType() {
- return type;
- }
- }
5.微信支付求数据的对象
- import lombok.Data;
- import lombok.experimental.Accessors;
-
- import javax.validation.constraints.NotBlank;
-
- /**
- * <p>
- * 预支付参数
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/26 13:30
- */
- @Data
- @Accessors(chain = true)
- public class WxPayReqParam {
-
- /**
- * 总金额
- */
- @NotBlank(message = "总金额不能为空!")
- private String totalPrice;
-
- /**
- * 商品名称
- */
- @NotBlank(message = "商品名称不能为空!")
- private String goodsName;
-
- /**
- * openId
- */
- @NotBlank(message = "openId不能为空!")
- private String openId;
-
- /**
- * 订单号
- */
- @NotBlank(message = "商品订单号不能为空!")
- private String orderNumber;
-
-
- }
6.将请求参数封装成Map集合+创建微信支付订单的三种方式(Native,Jsapi,App),
我使用的是Jsapi,别的支付方式可以在刚才的枚举类中“WxApiConstants”,添加别的下单路径,或者就是写死了也可以,如果你也是Jsapi支付,应该就不需要改动什么。
开发指引-JSAPI支付 | 微信支付商户平台文档中心
- import com.cx.sasmc.vxpay.reqparam.WxPayReqParam;
- import com.cx.sasmc.vxpay.vxapienum.WxApiConstants;
- import com.google.gson.Gson;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.entity.StringEntity;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.apache.http.util.EntityUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * <p>
- * 将请求参数封装成Map集合
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/26 15:54
- */
- public class WxPayCommon {
-
- private final static Logger logger = LoggerFactory.getLogger(WxPayCommon.class);
-
-
- /**
- * 封装基础通用请求数据
- * @param wxPayConfig 微信的配置文件
- * @param basePayData 微信支付基础请求数据
- * @return 封装后的map对象
- */
- public static Map<String, Object> getBasePayParams(WxPayConfig wxPayConfig, WxPayReqParam basePayData) {
- Map<String, Object> paramsMap = new HashMap<>();
- paramsMap.put("appid", wxPayConfig.getAppId());
- paramsMap.put("mchid", wxPayConfig.getMchId());
- // 如果商品名称过长则截取
- String title = basePayData.getGoodsName().length() > 62 ? basePayData.getGoodsName().substring(0, 62) : basePayData.getGoodsName();
- paramsMap.put("description",title);
- paramsMap.put("out_trade_no", basePayData.getOrderNumber());
- paramsMap.put("notify_url", "https://你自己的回调域名.cn/vxPay/payNotify");
- Map<String, Integer> amountMap = new HashMap<>();
- amountMap.put("total", Integer.valueOf(basePayData.getTotalPrice()));
- paramsMap.put("amount", amountMap);
- return paramsMap;
- }
-
- /**
- * 获取请求对象(Post请求)
- * @param paramsMap 请求参数
- * @return Post请求对象
- */
- public static HttpPost getHttpPost(String type, Map<String, Object> paramsMap) {
-
- // 1.设置请求地址
- HttpPost httpPost = new HttpPost(type);
-
- // 2.设置请求数据
- Gson gson = new Gson();
- String jsonParams = gson.toJson(paramsMap);
-
- // 3.设置请求信息
- StringEntity entity = new StringEntity(jsonParams, "utf-8");
- entity.setContentType("application/json");
- httpPost.setEntity(entity);
- httpPost.setHeader("Accept", "application/json");
- return httpPost;
- }
-
- /**
- * 解析响应数据
- * @param response 发送请求成功后,返回的数据
- * @return 微信返回的参数
- */
- public static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
- try {
- // 1.获取请求码
- int statusCode = response.getStatusLine().getStatusCode();
- // 2.获取返回值 String 格式
- final String bodyAsString = EntityUtils.toString(response.getEntity());
-
- Gson gson = new Gson();
- if (statusCode == 200) {
- // 3.如果请求成功则解析成Map对象返回
- HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
- return resultMap;
- } else {
- if (StringUtils.isNoneBlank(bodyAsString)) {
- logger.error("微信支付请求失败,提示信息:{}", bodyAsString);
- // 4.请求码显示失败,则尝试获取提示信息
- HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
- throw new RuntimeException(resultMap.get("message"));
- }
- logger.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
- // 其他异常,微信也没有返回数据,这就需要具体排查了
- throw new IOException("request failed");
- }
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(e.getMessage());
- } finally {
- try {
- response.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
-
- /**
- * 创建微信支付订单-jsapi方式
- * @param wxPayConfig 微信配置信息
- * @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
- * @param openId 通过微信小程序或者公众号获取到用户的openId
- * @param wxPayClient 微信请求客户端()
- * @return 微信支付二维码地址
- */
- public static String wxJsApiPay(WxPayConfig wxPayConfig, WxPayReqParam basePayData, String openId, CloseableHttpClient wxPayClient) {
-
- // 1.获取请求参数的Map格式
- Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);
-
- // 1.1 添加支付者信息
- Map<String,String> payerMap = new HashMap<>();
- payerMap.put("openid",openId);
- paramsMap.put("payer",payerMap);
-
- // 2.获取请求对象
- HttpPost httpPost = getHttpPost("https://api.mch.weixin.qq.com"+WxApiConstants.JSAPI_PAY.getType(),paramsMap);
-
- // 3.完成签名并执行请求
- CloseableHttpResponse response = null;
- try {
- response = wxPayClient.execute(httpPost);
- } catch (IOException e) {
- e.printStackTrace();
- throw new RuntimeException("微信支付请求失败");
- }
-
- // 4.解析response对象
- HashMap<String, String> resultMap = resolverResponse(response);
- if (resultMap != null) {
- // native请求返回的是二维码链接,前端将链接转换成二维码即可
- return resultMap.get("prepay_id");
- }
- return null;
- }
-
- }
7.创建实体存储前端微信支付所需参数(WxChatPayDto)
小程序内需要多个参数才可以唤起微信支付,就是输入密码支付的那个支付页面。
- import lombok.Data;
-
- /**
- * <p>
- * 前端微信支付所需参数
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/26 16:26
- */
- @Data
- public class WxChatPayDto {
-
- /**
- * 需要支付的小程序id
- */
- private String appid;
-
- /**
- * 时间戳(当前的时间)
- */
- private String timeStamp;
-
- /**
- * 随机字符串,不长于32位。
- */
- private String nonceStr;
-
- /**
- * 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
- */
- private String prepayId;
-
- /**
- * 签名类型,默认为RSA,仅支持RSA。
- */
- private String signType;
-
- /**
- * 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
- */
- private String paySign;
- }
8. 微信支付
- /**
- * 微信用户调用微信支付
- */
- @Override
- public WxChatPayDto wxPay(WxPayReqParam param) {
- String prepayId = WxPayCommon.wxJsApiPay(wxPayConfig, param, param.getOpenId(), wxPayClient);
- WxChatPayDto wxChatPayDto = new WxChatPayDto();
- wxChatPayDto.setAppid(wxPayConfig.getAppId());
- wxChatPayDto.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000));
- wxChatPayDto.setNonceStr(UUID.randomUUID().toString().replaceAll("-", ""));
- wxChatPayDto.setPrepayId("prepay_id=" + prepayId);
- wxChatPayDto.setSignType("RSA");
- wxChatPayDto.setPaySign(getSign(wxChatPayDto.getNonceStr(),wxChatPayDto.getAppid(),wxChatPayDto.getPrepayId(),Long.parseLong(wxChatPayDto.getTimeStamp())));
- return wxChatPayDto;
- }
返回给小程序的wxChatPayDto就可以唤起支付页面了。
9.成功回调
- import com.alibaba.fastjson.JSONObject;
- import com.cx.sasmc.vxpay.config.WxPayConfig;
- import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
- import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.Resource;
- import javax.servlet.ServletInputStream;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.UnsupportedEncodingException;
- import java.security.GeneralSecurityException;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
-
- /**
- * <p>
- * 微信支付回调工具类
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 8:50
- */
- @Component
- public class WxPayCallbackUtil {
- @Resource
- private Verifier verifier;
- @Resource
- private WxPayConfig wxPayConfig;
- /**
- * 获取回调数据
- * @param request
- * @param response
- * @return
- */
- public Map<String, String> wxChatPayCallback(HttpServletRequest request, HttpServletResponse response) {
- //获取报文
- String body = getRequestBody(request);
-
- //随机串
- String nonceStr = request.getHeader("Wechatpay-Nonce");
-
- //微信传递过来的签名
- String signature = request.getHeader("Wechatpay-Signature");
-
- //证书序列号(微信平台)
- String serialNo = request.getHeader("Wechatpay-Serial");
-
- //时间戳
- String timestamp = request.getHeader("Wechatpay-Timestamp");
-
- //构造签名串 应答时间戳\n,应答随机串\n,应答报文主体\n
- String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
-
- Map<String, String> map = new HashMap<>(2);
- try {
- //验证签名是否通过
- boolean result = verifiedSign(serialNo, signStr, signature);
- if(result){
- //解密数据
- String plainBody = decryptBody(body);
- return convertWechatPayMsgToMap(plainBody);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return map;
- }
-
- /**
- * 转换body为map
- * @param plainBody
- * @return
- */
- public Map<String,String> convertWechatPayMsgToMap(String plainBody){
-
- Map<String,String> paramsMap = new HashMap<>(2);
-
- JSONObject jsonObject = JSONObject.parseObject(plainBody);
-
- //商户订单号
- paramsMap.put("out_trade_no",jsonObject.getString("out_trade_no"));
-
- //交易状态
- paramsMap.put("trade_state",jsonObject.getString("trade_state"));
-
- //附加数据
- paramsMap.put("attach",jsonObject.getString("attach"));
- if (jsonObject.getJSONObject("attach") != null && !jsonObject.getJSONObject("attach").equals("")){
- paramsMap.put("account_no",jsonObject.getJSONObject("attach").getString("accountNo"));
- }
- return paramsMap;
-
- }
-
- /**
- * 解密body的密文
- *
- * "resource": {
- * "original_type": "transaction",
- * "algorithm": "AEAD_AES_256_GCM",
- * "ciphertext": "",
- * "associated_data": "",
- * "nonce": ""
- * }
- *
- * @param body body
- * @return
- */
- public String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {
- AesUtil aesUtil = new AesUtil(wxPayConfig.getApiKey().getBytes("utf-8"));
- JSONObject object = JSONObject.parseObject(body);
- JSONObject resource = object.getJSONObject("resource");
- String ciphertext = resource.getString("ciphertext");
- String associatedData = resource.getString("associated_data");
- String nonce = resource.getString("nonce");
- return aesUtil.decryptToString(associatedData.getBytes("utf-8"),nonce.getBytes("utf-8"),ciphertext);
- }
-
-
- /**
- * 验证签名
- *
- * @param serialNo 微信平台-证书序列号
- * @param signStr 自己组装的签名串
- * @param signature 微信返回的签名
- * @return
- * @throws UnsupportedEncodingException
- */
- public boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
- return verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
- }
-
- /**
- * 读取请求数据流
- *
- * @param request
- * @return
- */
- public String getRequestBody(HttpServletRequest request) {
- StringBuffer sb = new StringBuffer();
- try (ServletInputStream inputStream = request.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- ) {
- String line;
- while ((line = reader.readLine()) != null) {
- sb.append(line);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return sb.toString();
-
- }
- }
成功回调方法
- /**
- * 微信支付成功回调
- * @param request request
- * @param response response
- * @return map
- */
- @Override
- public Map<String, String> wxOrderCallBack(HttpServletRequest request, HttpServletResponse response) {
- Map<String, String> map = new HashMap<>(2);
- try {
- Map<String, String> stringMap = wxChatPayCallback.wxChatPayCallback(request, response);
- //支付成功
- if (stringMap.get("trade_state").equals("SUCCESS")){
- // 获取咱们自己生成的订单号
- String out_trade_no = stringMap.get("out_trade_no");
- if (!stringRedisTemplate.hasKey("ORDER_NO:"+out_trade_no)){
- QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
- queryWrapper.eq("orderNumber",OrderStatusConstants.UNPAID);
- Order order = orderMapper.selectOne(queryWrapper);
- if (ObjectUtil.isNotEmpty(order)){
- //编写支付成功后逻辑 修改订单为已付款。
- Order upOrder = new Order();
- upOrder.setId(order.getId());
- upOrder.setOrderStatus(OrderStatusConstants.PAID);
- orderMapper.updateById(upOrder);
- // (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)
- // 数据有效期设置25小时
- stringRedisTemplate.opsForValue().set("ORDER_NO:"+out_trade_no,out_trade_no,25L, TimeUnit.HOURS);
- }
-
- }
- }
- //响应微信
- map.put("code", "SUCCESS");
- map.put("message", "成功");
- } catch (Exception e) {
- e.printStackTrace();
- }
- return map;
- }
至此支付就完成了。
10.退款 说明(项目中用到了Sa-token,@SaIgnore注解标识这个方法无需鉴权,可以匿名访问)
- /**
- * 申请退款
- */
- @SaIgnore
- @PostMapping("/vxPayRefund")
- public Result payRefund(@RequestBody @Validated WxPayRefundReqParam param){
- String s = vxPayService.payRefund(param);
- if (ObjectUtil.isNotEmpty(s)){
- return Result.ok(s);
- } else {
- return Result.fail("退款失败");
- }
- }
- /**
- * 申请退款
- * @param param param
- * @return string
- */
- @Override
- public String payRefund(WxPayRefundReqParam param) {
- Map<String, String> map = WxPayRefundUtil.refundPay(param, wxPayClient);
- Order upOrder = new Order();
- upOrder.setRefundNumber(map.get("refundNumber"));
- upOrder.setOrderStatus(OrderStatusConstants.REFUND_PROCESSING);
- UpdateWrapper<Order> updateWrapper = new UpdateWrapper<>();
- updateWrapper.eq("orderNumber",param.getTransactionId());
- orderMapper.update(upOrder,updateWrapper);
- return map.get("refund_id");
- }
- /**
- * 发起微信退款申请
- * @param param 微信支付申请退款请求参数
- * @return 微信支付二维码地址
- */
- public static Map<String,String> refundPay(WxPayRefundReqParam param,CloseableHttpClient wxPayClient) {
- Map<String,String> returnMap = new HashMap<>();
-
- // 1.获取请求参数的Map格式
- Map<String, Object> paramsMap = getRefundParams(param);
-
- // 2.获取请求对象
- HttpPost httpPost = WxPayCommon.getHttpPost("https://api.mch.weixin.qq.com"+ WxApiConstants.DOMESTIC_REFUNDS.getType(), paramsMap);
-
- // 3.完成签名并执行请求
- CloseableHttpResponse response = null;
- try {
- response = wxPayClient.execute(httpPost);
- } catch (IOException e) {
- e.printStackTrace();
- throw new RuntimeException("微信支付请求失败");
- }
-
- // 4.解析response对象
- HashMap<String, String> resultMap = WxPayCommon.resolverResponse(response);
- if (resultMap != null) {
- // 返回微信支付退款单号
- returnMap.put("refund_id",resultMap.get("refund_id"));
- returnMap.put("refundNumber",paramsMap.get("out_refund_no").toString());
- return returnMap;
-
- }
- return null;
- }
退款请求参数:
- package com.cx.sasmc.vxpay.vxpayutil;
-
- import com.cx.sasmc.utils.OrderNumberGenerate;
- import com.cx.sasmc.vxpay.config.WxPayCommon;
- import com.cx.sasmc.vxpay.reqparam.WxPayRefundReqParam;
- import com.cx.sasmc.vxpay.vxapienum.WxApiConstants;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.impl.client.CloseableHttpClient;
-
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * <p>
- * 微信支付退款
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 9:49
- */
- public class WxPayRefundUtil {
- /**
- * 封装微信支付申请退款请求参数
- * @param param 微信支付申请退款请求参数
- * @return 封装后的map微信支付申请退款请求参数对象
- */
- private static Map<String, Object> getRefundParams(WxPayRefundReqParam param) {
- String out_refund_no = OrderNumberGenerate.orderNo("100001");
- Map<String, Object> paramsMap = new HashMap<>();
- if (StringUtils.isNoneBlank(param.getTransactionId())) {
- paramsMap.put("out_trade_no", param.getTransactionId());
- }
- paramsMap.put("out_refund_no", out_refund_no);
- paramsMap.put("notify_url", "https://你自己的回调域名.cn/vxPay/refundWechatCallback");
- Map<String, Object> amountMap = new HashMap<>();
- amountMap.put("refund", Long.valueOf(param.getRefundMoney()));
- amountMap.put("total", Long.valueOf(param.getTotalMoney()));
- amountMap.put("currency", "CNY");
- paramsMap.put("amount", amountMap);
- return paramsMap;
- }
-
- /**
- * 发起微信退款申请
- * @param param 微信支付申请退款请求参数
- * @return 微信支付二维码地址
- */
- public static Map<String,String> refundPay(WxPayRefundReqParam param,CloseableHttpClient wxPayClient) {
- Map<String,String> returnMap = new HashMap<>();
-
- // 1.获取请求参数的Map格式
- Map<String, Object> paramsMap = getRefundParams(param);
-
- // 2.获取请求对象
- HttpPost httpPost = WxPayCommon.getHttpPost("https://api.mch.weixin.qq.com"+ WxApiConstants.DOMESTIC_REFUNDS.getType(), paramsMap);
-
- // 3.完成签名并执行请求
- CloseableHttpResponse response = null;
- try {
- response = wxPayClient.execute(httpPost);
- } catch (IOException e) {
- e.printStackTrace();
- throw new RuntimeException("微信支付请求失败");
- }
-
- // 4.解析response对象
- HashMap<String, String> resultMap = WxPayCommon.resolverResponse(response);
- if (resultMap != null) {
- // 返回微信支付退款单号
- returnMap.put("refund_id",resultMap.get("refund_id"));
- returnMap.put("refundNumber",paramsMap.get("out_refund_no").toString());
- return returnMap;
-
- }
- return null;
- }
-
- }
退款回调的请求参数
- import cn.hutool.core.date.DateUtil;
- import lombok.Data;
-
- import java.math.BigDecimal;
- import java.util.Date;
-
- /**
- * <p>
- * 微信退款回调参数
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 13:40
- */
- @Data
- public class WxChatCallbackRefundReqParam {
- /**
- * 商户订单号
- */
- private String orderId;
-
- /**
- * 商户退款单号,out_refund_no
- */
- private String refundId;
-
- /**
- * 微信支付系统生成的订单号
- */
- private String transactionId;
-
- /**
- * 微信支付系统生成的退款订单号
- */
- private String transactionRefundId;
-
- /**
- * 退款渠道 1.ORIGINAL:原路退款 2.BALANCE:退回到余额
- * 3.OTHER_BALANCE:原账户异常退到其他余额账户
- * 4.OTHER_BANKCARD:原银行卡异常退到其他银行卡
- */
- private String channel;
-
- /**
- * 退款成功时间 当前退款成功时才有此返回值
- */
- private Date successTime;
-
- /**
- * 退款状态 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
- * 1.SUCCESS:退款成功 2.CLOSED:退款关闭 3.PROCESSING:退款处理中 4.ABNORMAL:退款异常
- */
- private String status;
-
- /**
- * 退款金额
- */
- private BigDecimal refundMoney;
-
- public Date getSuccessTime() {
- return successTime;
- }
-
- public void setSuccessTime(String successTime) {
- // Hutool工具包的方法,自动识别一些常用格式的日期字符串
- this.successTime = DateUtil.parse(successTime);
- }
-
- }
退款业务处理接口
- import com.cx.sasmc.vxpay.reqparam.WxChatCallbackRefundReqParam;
-
- /**
- * <p>
- * 退款回调
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 13:54
- */
- public interface WechatRefundCallback {
- /**
- * 退款成功处理情况
- */
- void success(WxChatCallbackRefundReqParam refundData);
-
- /**
- * 退款失败处理情况
- */
- void fail(WxChatCallbackRefundReqParam refundData);
- }
微信退款回调方法
- import javax.servlet.http.HttpServletRequest;
- import java.io.BufferedReader;
- import java.io.IOException;
-
- /**
- * <p>
- * 将通知参数转化为字符串
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 13:43
- */
- public class HttpUtils {
- /**
- * 将通知参数转化为字符串
- * @param request
- * @return
- */
- public static String readData(HttpServletRequest request) {
- BufferedReader br = null;
- try {
- StringBuilder result = new StringBuilder();
- br = request.getReader();
- for (String line; (line = br.readLine()) != null; ) {
- if (result.length() > 0) {
- result.append("\n");
- }
- result.append(line);
- }
- return result.toString();
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- if (br != null) {
- try {
- br.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.time.DateTimeException;
- import java.time.Duration;
- import java.time.Instant;
-
- import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
-
- import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;
-
- /**
- * <p>
- * 微信支付 退款回调请求验证
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 13:47
- */
- public class WechatPayValidatorForRequest {
-
- private final Logger logger = LoggerFactory.getLogger(WechatPayValidatorForRequest.class);
- /**
- * 应答超时时间,单位为分钟
- */
- protected static final long RESPONSE_EXPIRED_MINUTES = 5;
- protected final Verifier verifier;
- protected final String body;
- protected final String requestId;
-
- public WechatPayValidatorForRequest(Verifier verifier, String body, String requestId) {
- this.verifier = verifier;
- this.body = body;
- this.requestId = requestId;
- }
-
- protected static IllegalArgumentException parameterError(String message, Object... args) {
- message = String.format(message, args);
- return new IllegalArgumentException("parameter error: " + message);
- }
-
- protected static IllegalArgumentException verifyFail(String message, Object... args) {
- message = String.format(message, args);
- return new IllegalArgumentException("signature verify fail: " + message);
- }
-
- public final boolean validate(HttpServletRequest request) throws IOException {
- try {
- validateParameters(request);
-
- String message = buildMessage(request);
- String serial = request.getHeader(WECHAT_PAY_SERIAL);
- String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
-
- if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
- throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
- serial, message, signature, request.getHeader(REQUEST_ID));
- }
- } catch (IllegalArgumentException e) {
- logger.warn(e.getMessage());
- return false;
- }
-
- return true;
- }
-
- protected final void validateParameters(HttpServletRequest request) {
-
- // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
- String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
-
- String header = null;
- for (String headerName : headers) {
- header = request.getHeader(headerName);
- if (header == null) {
- throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
- }
- }
-
- String timestampStr = header;
- try {
- Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
- // 拒绝过期应答
- if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
- throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
- }
- } catch (DateTimeException | NumberFormatException e) {
- throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
- }
- }
-
- protected final String buildMessage(HttpServletRequest request) throws IOException {
- String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
- String nonce = request.getHeader(WECHAT_PAY_NONCE);
- return timestamp + "\n"
- + nonce + "\n"
- + body + "\n";
- }
-
- }
- import com.cx.sasmc.vxpay.config.WxPayConfig;
- import com.cx.sasmc.vxpay.reqparam.WxChatCallbackRefundReqParam;
- import com.cx.sasmc.vxpay.service.WechatRefundCallback;
- import com.google.gson.Gson;
- import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
- import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
- import org.apache.commons.lang3.StringUtils;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.math.BigDecimal;
- import java.nio.charset.StandardCharsets;
- import java.security.GeneralSecurityException;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * <p>
- * 微信支付申请退款回调
- * </p>
- *
- * @author Lch
- * @dateTime 2024/2/27 13:51
- */
- public class WxPayRefundCallbackUtil {
- /**
- * 微信支付申请退款回调方法
- *
- * @param verifier 证书
- * @param wxPayConfig 微信配置
- * @param refundCallback 回调方法,用于处理业务逻辑,包含退款成功处理于退款失败处理
- * @return json格式的string数据,直接返回给微信
- */
- public static String wxPayRefundCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, WechatRefundCallback refundCallback) {
- Gson gson = new Gson();
-
- // 1.处理通知参数
- final String body = HttpUtils.readData(request);
- HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
-
- // 2.签名验证
- WechatPayValidatorForRequest wechatForRequest = new WechatPayValidatorForRequest(verifier, body, (String) bodyMap.get("id"));
- try {
-
- if (!wechatForRequest.validate(request)) {
- // 通知验签失败
- response.setStatus(500);
- final HashMap<String, Object> map = new HashMap<>();
- map.put("code", "ERROR");
- map.put("message", "通知验签失败");
- return gson.toJson(map);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- // 3.获取明文数据
- String plainText = decryptFromResource(bodyMap, wxPayConfig);
- HashMap<String, Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
- // log.info("退款plainTextMap:{}", plainTextMap);
- // 4.封装微信返回的数据
- WxChatCallbackRefundReqParam refundData = getRefundCallbackData(plainTextMap);
-
- if ("SUCCESS".equals(refundData.getStatus())) {
- // 执行业务逻辑
- refundCallback.success(refundData);
- } else {
- // 特殊情况退款失败业务处理,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款
- refundCallback.fail(refundData);
- }
-
- // 5.成功应答
- response.setStatus(200);
- final HashMap<String, Object> resultMap = new HashMap<>();
- resultMap.put("code", "SUCCESS");
- resultMap.put("message", "成功");
- return gson.toJson(resultMap);
- }
-
- private static WxChatCallbackRefundReqParam getRefundCallbackData(HashMap<String, Object> plainTextMap) {
- Gson gson = new Gson();
- WxChatCallbackRefundReqParam refundData = new WxChatCallbackRefundReqParam();
- String successTime = String.valueOf(plainTextMap.get("success_time"));
- if (StringUtils.isNoneBlank(successTime)) {
- refundData.setSuccessTime(successTime);
- }
- refundData.setOrderId(String.valueOf(plainTextMap.get("out_trade_no")));
- refundData.setRefundId(String.valueOf(plainTextMap.get("out_refund_no")));
- refundData.setTransactionId(String.valueOf(plainTextMap.get("transaction_id")));
- refundData.setTransactionRefundId(String.valueOf(plainTextMap.get("refund_id")));
- refundData.setChannel(String.valueOf(plainTextMap.get("channel")));
- final String status = String.valueOf(plainTextMap.get("refund_status"));
- refundData.setStatus(status);
- String amount = String.valueOf(plainTextMap.get("amount"));
- HashMap<String, Object> amountMap = gson.fromJson(amount, HashMap.class);
- String refundMoney = String.valueOf(amountMap.get("refund"));
- refundData.setRefundMoney(new BigDecimal(refundMoney).movePointLeft(2));
- // log.info("refundData:{}", refundData);
- return refundData;
- }
-
- /**
- * 对称解密
- */
- private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig) {
- // 通知数据
- Map<String, String> resourceMap = (Map) bodyMap.get("resource");
- // 数据密文
- String ciphertext = resourceMap.get("ciphertext");
- // 随机串
- String nonce = resourceMap.get("nonce");
- // 附加数据
- String associateData = resourceMap.get("associated_data");
- AesUtil aesUtil = new AesUtil(wxPayConfig.getApiKey().getBytes(StandardCharsets.UTF_8));
- try {
- return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
- } catch (GeneralSecurityException e) {
- e.printStackTrace();
- throw new RuntimeException("解密失败");
- }
- }
-
- }
- /**
- * 退款回调
- * @param request r
- * @param response rp
- * @return s
- */
- @Override
- public String refundWechatCallback(HttpServletRequest request, HttpServletResponse response) {
- return WxPayRefundCallbackUtil.wxPayRefundCallback(request, response, verifier, wxPayConfiga, new WechatRefundCallback() {
-
- /**
- * 退款成功
- */
- @Override
- public void success(WxChatCallbackRefundReqParam refundData) {
- Order upOrder = new Order();
- upOrder.setId(Long.valueOf(refundData.getOrderId()));
- upOrder.setOrderStatus(OrderStatusConstants.CANCELED);
- orderMapper.updateById(upOrder);
- }
-
- /**
- * 退款失败
- */
- @Override
- public void fail(WxChatCallbackRefundReqParam refundData) {
- Order order = orderMapper.selectById(Long.valueOf(refundData.getOrderId()));
- if (ObjectUtil.isNotEmpty(order.getOperatorName()) && ObjectUtil.isNotEmpty(order.getOperatorId())){
- Order upOrder = new Order();
- upOrder.setId(Long.valueOf(refundData.getOrderId()));
- upOrder.setOrderStatus(OrderStatusConstants.OPERATOR_ACCEPTED);
- orderMapper.updateById(upOrder);
- } else {
- Order upOrder = new Order();
- upOrder.setId(Long.valueOf(refundData.getOrderId()));
- upOrder.setOrderStatus(OrderStatusConstants.PAID);
- orderMapper.updateById(upOrder);
- }
- }
- });
- }
也是真乱啊 如果大家需要demo就留个言或者私信我一下我就弄一个,或者在在博文里按照图片的顺序把所有代码全部贴进来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。