java微信app支付服务端v3代码

1 篇文章 0 订阅
1 篇文章 0 订阅

微信的文档写的是真的烂,特别是刚写完支付宝和苹果支付,有了对比就显得微信的文档是真的垃圾,想体验的可以去看下:
微信官方文档
为了下次做微信支付的时候能舒服点,记录下

主要坑点

  1. 统一下单接口调用的时候记得将编码格式转为iso8859-1,否则中文会乱码,微信官方文档里面的demo没有转换也没有提到这点
  2. 统一下单后还需要对参数进行加密签名,v3理论上应该要有支持加密的api但是我在文档里面没有找到,自己网上找代码实现的
  3. v3版本的回调返回不是xml格式的了,而是终于改成了json格式的数据放回
  4. 回调的数据不是直接给你的,需要对数据进行解密

主要代码

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.2.2</version>
</dependency>

import org.apache.http.HttpRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;

@RestController
@RequestMapping("")
public class PayController {
    @Resource
    private WxpayUtil wxpayUtil;


    @PostMapping("/wxpay/getOrder")
    public DataResult wxpayGetOrder(Integer orderType,Integer orderId,Integer orderNum) throws Exception {
        return wxpayUtil.getOrder(orderType,orderId,orderNum);
    }

    @PostMapping("/wxpay/callback")
    public String wxPayCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String resXml = "";
        try {
            InputStream inputStream = request.getInputStream();
            //将InputStream转换成xmlString
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String line = null;
            try {
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            resXml = sb.toString();
            String result =  wxpayUtil.callback(resXml,response);
            return result;
        } catch (Exception e) {
            System.out.println("微信手机支付失败:" + e.getMessage());
            e.printStackTrace();
            String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
            return result;
        }
    }
}

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class WxAPIV3AesUtil {

    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    private final byte[] aesKey;

    public WxAPIV3AesUtil(byte[] key) {
        if (key.length != KEY_LENGTH_BYTE) {
            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
        }
        this.aesKey = key;
    }

    public String decryptToString(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)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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.util.PemUtil;

import com.youyi.heidong.util.ResultUtil;
import com.youyi.heidong.util.StringUtils;
import com.youyi.heidong.web.mapper.*;
import com.youyi.heidong.web.pojo.response.DataResult;
import com.youyi.heidong.web.service.MoneyDictionariesService;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.*;
import java.util.*;

@Service
public class WxpayUtil {
  
    String privateKey = "微信私钥";
    String mchId = "商户号";
    //
    String mchSerialNo = "商户证书序列号";
    // V3密钥
    String apiV3Key = "apiv3密钥";
    String appid = "appid";
    // 回调地址
    String NOTIFY_URL = "回调地址";

    String app_secret = "appSecret";


    /**
     * 获取订单号
     */
    public DataResult getOrder(Integer orderType, Integer orderId, Integer orderNum) throws Exception {
        // 加载商户私钥(privateKey:私钥字符串)
        PrivateKey merchantPrivateKey = PemUtil
                .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
        // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
        // 初始化httpClient
        HttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();
        //请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
        // 请求body参数
        String reqdata = "{"
                + "\"amount\": {"
                + "\"total\":1,"
                + "\"currency\":\"CNY\""
                + "},"
                + "\"mchid\":\""+mchId+"\","
                + "\"description\":\"7878787878会员充值\","
                + "\"notify_url\":\""+NOTIFY_URL+"\","
                + "\"out_trade_no\":\""+new Date().getTime()+"\","
                + "\"appid\":\""+appid+"\","
                + "\"attach\":\"自定义数据说明\""
                + "}";
        StringEntity entity = new StringEntity(new String(reqdata.getBytes("utf-8"), "iso8859-1"));
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                Map<String, String> param = new LinkedHashMap<>();
                param.put("appid", appid);
                param.put("partnerid",mchId);
                String prepay_id = EntityUtils.toString(response.getEntity());
                ObjectMapper objectMapper = new ObjectMapper();
                param.put("prepayid",objectMapper.readValue(prepay_id,Map.class).get("prepay_id").toString());
                param.put("package","Sign=WXPay");
                param.put("noncestr", StringUtils.randomString(32));
                param.put("timestamp",System.currentTimeMillis()/1000+"");
                param.put("sign",createSign(param));
                return ResultUtil.success(param);
                //return ResultUtil.success(EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) { //处理成功,无返回Body
                System.out.println("success");
                return ResultUtil.success();
            } else {
                String flag = EntityUtils.toString(response.getEntity());
                System.out.println("failed,resp code = " + statusCode+ ",return body = " + flag);
                return ResultUtil.fail("failed,resp code = " + statusCode+ ",return body = " + flag);
                //throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }

    /**
     * 微信支付签名算法sign
     * @param parameters
     * @return
     */
    public String createSign(Map<String,String> parameters){
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            Object v = entry.getValue();
            if(null != v && !"".equals(v)
                    && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }

        sb.append("key=" + apiV3Key);
                System.out.println("签名字符串:"+sb.toString());
//        System.out.println("签名MD5未变大写:" + MD5Util.MD5Encode(sb.toString(), characterEncoding));
        String sign = md5Password(sb.toString()).toUpperCase();
        return sign;
    }
    /**
     * 生成32位md5码
     *
     * @param key
     * @return
     */
    public static String md5Password(String key) {
        char hexDigits[] = {
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
        };
        try {
            byte[] btInput = key.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            return null;
        }
    }

    public String callback(String param, HttpServletResponse response) throws IOException, GeneralSecurityException {
        System.out.println(param);
        // 新版本微信回调进行了修改使用json格式回调数据
        Map<String, Object> paramMap = new HashMap<String, Object>();
        ObjectMapper mapper = new ObjectMapper();
        paramMap = mapper.readValue(param,Map.class);
        // 判断是否为支付成功回调
        if("TRANSACTION.SUCCESS".equals(paramMap.get("event_type"))){
            Map<String,String> resourceMap = (Map)paramMap.get("resource");
            // 使用商户私钥解密
            byte[] key = apiV3Key.getBytes("UTF-8");
            WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key);
            String decryptToString = aesUtil.decryptToString(resourceMap.get("associated_data").getBytes("UTF-8"),
                    resourceMap.get("nonce").getBytes("UTF-8"),
                    resourceMap.get("ciphertext"));
            System.out.println(decryptToString);
            // 获取回调数据进行校验比对
            Map map = mapper.readValue(decryptToString, Map.class);
            if(mchId.equals(map.get("mchid"))&&appid.equals(map.get("appid"))&&"SUCCESS".equals(map.get("trade_state"))){
                // todo 基础信息校验通过开始处理业务逻辑
                String out_trade_no = map.get("out_trade_no").toString();
            }
        }
        // 回应微信调用
        return "{ \"code\": \"SUCCESS\",    \"message\": \"成功\"\n }";
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值