在java项目中对接微信支付功能

自己工作中对接完成之后的记录.
先是拿到微信对应的参数,放在application.yml中.
在这里插入图片描述
appid和secret是微信小程序或者公众号的参数
mchId是用于收款的商户号的id
mchSerialNo是商户证书序列号(可在微信支付平台商户信息中查看)
notifyUrl是支付成功后的回调地址,微信要求必须为https并且绑定域名,无法使用ip端口
apiV2Key和apiV3Key都在商户的API中心设置,分别对应两套版本,目前基本都使用apiV3,少量接口会使用到apiV2.
个人觉得在对接微信过程中最麻烦的就是签名的校验和解析,但是在apiV3的SDK中带的client已经封装好了签名相关功能,因此在对接文档的时候可以忽略相关的签名参数,这点很不错.如果是apiV2的接口则需要手动签名.
设置好配置文件之后需要配置一个微信client的config文件.

@Data
@Configuration
@ConfigurationProperties(prefix = "wx")
public class WechatPayConfig {
    private String appid;
    private String secret;
    private String privateKey;
    private String mchId;
    private String mchSerialNo;
    private String apiV2Key;
    private String apiV3Key;
    private String notifyUrl;
   // private String version;
   // private String subMchid;
  //  private String subApiV2Key;
  //  private static final String DEV = "dev";
   // private static final String PROD = "prod";

    public String getPrivateKey() {
            return "-----BEGIN PRIVATE KEY-----\n" +
                    "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDu4AhYeoJ2vCw/\n" +
                    "ElrzSyTnxlFw96g7WBIFHy64f/4CQXKkz5Wj9Gi9UZGwTaPj85c73SVvBU+rgPnH\n" +
                    "frhsB/cQ7xSCrBdcW+1KKiiM\n" +
                    "-----END PRIVATE KEY-----";
    }

}

这里如果要分dev和prod两个版本,则可以使用version的切换在getPrivateKey方法中返回不同的两个key.同时就要在配置文件中设置

wx:
   version: ${spring.profiles.active}

才可以在配置文件中获取到version为dev还是prod
其中suvMchid和subApiV2Key这些sub相关参数是服务商用的,如果是服务商对接的微信支付,那么sub就是服务商下的子商户相关参数,对应的接口api也不一样,这里记录的是非服务商的商户直接对接

配置好该配置类以后就可以在代码中注入来获取到微信client从而避免签名校验

    @Autowired
    private CloseableHttpClient wxHttpClient;

微信支付分为很多种支付方式,但其实对接完一种以后其他种大同小异,只是一些参数区别,这里以native支付举例,native支付的流程是:创建内部订单,拿到订单号和相关价格名称参数去调用微信的api,微信api返回一个二维码数据,把二维码返回给前端,进行扫码支付,支付完成之后微信回调我们项目的接口,修改订单状态,完成.也可以手动去查询微信接口获取结果,这里不记录退款等其他操作.
微信文档//https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html
controller层:

    @PostMapping("/pay")
    @ApiOperation(value = "微信支付")
    public R generateSignature(@RequestBody @Valid WxPayOrderRequest request) {
        return R.ok(payService.prePay(request));
    }

其中入参为:

@Data
public class WxPayOrderRequest {
    @NotBlank(message = "金额必填")
    @ApiModelProperty(value = "支付金额,单位元")
    private String realPrice;
    @NotBlank(message = "订单号必填")
    @ApiModelProperty(value = "系统内部订单号")
    private String orderNo;
    @NotBlank(message = "商品名称必填")
    @ApiModelProperty("商品名称")
    private String name;
}

service层,注意返回值给的是Map

    /**
     * 生成预支付交易单
     */
    Map<String, String> prePay(WxPayOrderRequest request);

impl层,这里构建参数用的是jackson,可以替换为fastjson等都可以.

    @Override
    public Map<String, String> prePay(WxPayOrderRequest request) {
        try {
            //文档https://pay.weixin.qq.com/docs/merchant/apis/native-payment/direct-jsons/native-prepay.html
            String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
            HttpPost httpPost = new HttpPost(url);
            //固定值
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectMapper objectMapper = new ObjectMapper();
            //这里对照文档,使用jackson构建参数,也可以使用fastjson之类的
            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("appid", payConfig.getAppid())
                    .put("mchid", payConfig.getMchId())
                    .put("description", request.getName())
                    .put("out_trade_no", request.getOrderNo())
                    .put("notify_url", payConfig.getNotifyUrl());
                    //子对象里的子字段
            rootNode.putObject("amount")
                    .put("total", Integer.parseInt(yuanToFen(request.getRealPrice())));
            System.out.println("rootNode=" + rootNode);
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            //返回结果
            CloseableHttpResponse response = wxHttpClient.execute(httpPost);
            String bodyAsString = EntityUtils.toString(response.getEntity());
            log.info("bodyAsString为{}",bodyAsString);
            ObjectMapper obj = new ObjectMapper();
            //解析结果
            Map<String, String> map = obj.readValue(bodyAsString, Map.class);
            log.info("创建订单map解析出来为{}",map);
            //这里注意微信如果成功只返回code_url,失败会返回code,所以需要判断
            if (map.get("code")!=null){
                throw new RuntimeException("该订单号已支付!");
            }
            Map<String, String> result = new HashMap<>();
            String codeUrl = map.get("code_url");
            if (StrUtil.isBlank(codeUrl)) throw new RuntimeException("创建订单失败,请稍后重试");
            //把url放到
            String qrCode = QrCodeUtil.generateAsBase64(codeUrl, QrConfig.create(), "jpg");
            result.put("code_url", qrCode);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("创建订单失败!");
        }
    }

然后是回调,回调地址在配置文件中配置,微信支付成功之后会调用这个接口

    @Anonymous
    @ApiOperation("微信支付回调")
    @PostMapping("/callback")
    public void callback(HttpServletRequest request, HttpServletResponse response) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> map = JsonUtils.JsonToEntity(request.getInputStream(), Map.class);
            String message = mapper.writeValueAsString(map);
            log.info("微信支付通知:{}", message);
            //这是返回给微信的结果,需要这两个参数
            Map<String, String> res = new HashMap<>();
            res.put("code", "SUCCESS");
            res.put("message", "成功");
            //支付成功回调
            if ("TRANSACTION.SUCCESS".equals("" + map.get("event_type"))) {
                //拿到回调内容中的source进行解密 解密后获取订单号,根据订单号去修改状态
                Map<String, String> resource = (Map) map.get("resource");
                log.info("回调中的resource为{}", resource);
                //这里的AESUtils是微信的解密工具类,里面还会用到其他的一些依赖
                String decrypt = AESUtils.decryptToString(payConfig.getApiV3Key().getBytes(),
                        resource.get("associated_data").getBytes(),
                        resource.get("nonce").getBytes(),
                        resource.get("ciphertext"));
                log.info("decrypt为{}", decrypt);
                //解密之后的参数重新构建为一个java对象,这个对象里的参数只能多不能少,如果少了会转换失败.所以我的对象里包含了商户订单的和服务商订单的一些参数.有很多参数是用不到的 常用就一个订单号
                TbWxOrder params = mapper.readValue(decrypt, TbWxOrder.class);
                log.info("解密消息:{}", mapper.writeValueAsString(params));
                if ("SUCCESS".equals(params.getTradeState())) {
                    String orderNo = params.getOutTradeNo();
                    log.info("解密之后订单号为{}", orderNo);
                    //拿到订单号就可以查订单改状态 或者其他逻辑,这里其他逻辑的代码我就删除了
                    //修改订单状态
                    order.setStatus(1);
                    messageOrderService.updateById(order);
                }
            }
            //回调完成,写入response结束回调方法
            response.getWriter().write(mapper.writeValueAsString(res));
        } catch (Exception e) {
            log.info("解密过程出错了");
            e.printStackTrace();
        }
    }

代码当中使用到的解析对象,里面有很多参数,这里的参数可以多不可以少,少了就会报json解析失败.所以我这个对象当中其实是多个接口的参数放在一起的,并不是一个接口的.

@Data
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class TbWxOrder {
    private String mchid;
    private String appid;
    @JsonProperty("sp_mchid")
    private String spMchid;
    @JsonProperty("sub_mchid")
    private String subMchid;
    @JsonProperty("sp_appid")
    private String spAppid;
    @JsonProperty("sub_appid")
    private String subAppid;
    @JsonProperty("out_trade_no")
    private String outTradeNo;
    @JsonProperty("transaction_id")
    private String transactionId;
    @JsonProperty("trade_type")
    private String tradeType;
    @JsonProperty("trade_state")
    private String tradeState;
    @JsonProperty("trade_state_desc")
    private String tradeStateDesc;
    @JsonProperty("bank_type")
    private String bankType;
    private String attach;
    @JsonProperty("success_time")
    private String successTime;
    private Payer payer;
    private Amount amount;
    @JsonProperty("refund_id")
    private String refundId;
    @JsonProperty("out_refund_no")
    private String outRefundNo;
    private String status;
    @JsonProperty("refund_status")
    private String refundStatus;
    private String channel;
    @JsonProperty("user_received_account")
    private String userReceivedAccount;
    @JsonProperty("create_time")
    private String createTime;
    private String openid;
    private Integer total;
    private Integer payerTotal;
    private String currency;
    private String payerCurrency;
    private Integer refund;
    private Integer payerRefund;
}

最后是一个解密用的工具类,微信的回调内容是加密的,需要解密才能解析出内容

/**
 * 解密微信callback回调的工具类
 */
@Slf4j
public class AESUtils {

    // 加密模式
    private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
    private static final String CHARSET_NAME = "UTF-8";
    private static final String AES_NAME = "AES";

    //解决java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/CBC/PKCS7Padding
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 解密
     *
     * @param content 目标密文
     * @param key     秘钥
     * @param iv      偏移量
     * @return
     */
    public static String decrypt(@NotNull String content, @NotNull String key, @NotNull String iv) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            byte[] sessionKey = Base64.getDecoder().decode(key);
            SecretKeySpec keySpec = new SecretKeySpec(sessionKey, AES_NAME);
            byte[] ivByte = Base64.getDecoder().decode(iv);
            AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivByte);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
            return new String(cipher.doFinal(Base64.getDecoder().decode(content)), CHARSET_NAME);
        } catch (Exception e) {
          //  log.error("解密失败:{}", e);
        }
        return "";
    }

    static final int TAG_LENGTH_BIT = 128;
    public static String decryptToString(byte[] aesKey, byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);

            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), CHARSET_NAME);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

over

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java小程序可以使用微信支付接口实现支付功能对接微信支付接口的步骤如下: 1. 获取微信支付接口的开发者账号,并进行账号绑定和认证。 2. 在Java小程序的后端代码,引入微信支付的SDK,可以使用第三方的开源SDK,如微信官方提供的java-sdk或者其他优秀的支付SDK。 3. 在小程序,创建一个支付请求页面,用户选择商品并点击支付按钮。将用户购买的商品信息传递到后台。 4. 后台接收到支付请求后,调用微信支付接口,传递必要的支付参数,包括商户号、商户订单号、支付金额、支付方式等。 5. 微信支付接口会返回一个预支付交易会话标识prepay_id,后台将该值返回给前端,前端将该值存储到小程序的支付参数。 6. 前端根据prepay_id、商户号、商户订单号等参数,调用微信支付的API,通过微信支付页面生成支付订单。 7. 用户在小程序看到生成的支付订单,选择支付方式(如微信支付或其他支付方式),输入支付密码等信息完成支付。 8. 支付成功后,微信支付接口会向后台发送支付结果通知,后台需要对接收到的结果进行验证,包括验证订单是否支付成功、验证订单金额是否一致等。 9. 后台验证通过后,向前端返回支付成功的信息,前端展示支付成功页面,并修改相关订单状态。 10. 后台同时需要记录支付相关的信息,如支付时间、支付方式等,供后续的订单查询和统计使用。 以上就是Java小程序对接微信支付接口的一般步骤,具体实现过程还需要根据具体的业务需求进行调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值