微信支付学习笔记

微信支付系列 V3

官方文档

B站视频

wechatpay-apache-httpclient

微信支付没有沙盒测试,在申请时,需要填写用户执照等信息。开发过程中需要对接 预支付接口和下单成功 两个接口。
微信支付平台证书是会发生变化的(5天)。
所有参数在官方文档中有介绍。

mchild(商户id)
API key:主要用于平台证书解密、回调信息解密
商户API证书:是指由商户申请的,包含商户的商户号、公司名称、公钥信息的证书。
私钥:申请商户API证书时,会生成商户私钥
签名:私钥对API URL、消息体等关键数据的组合进行SHA-256 with RSA签名

注意:建议先看下面的"微信支付补充"标题,这是我学完之后做出的步骤总结。

获取预下单ID

  1. 配置maven依赖,可能需要下载阿里云镜像,pom中加入junit依赖

  2. 下载wechatpay-apache-httpclient,将README.md中的下单代码( createOrder()函数 )复制,修改下单API的URL,修改其他参数。文档地址

    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
    httpPost.addHeader("Accept", "application/json");
    httpPost.addHeader("Content-type","application/json; charset=utf-8");
    
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectMapper objectMapper = new ObjectMapper();
    
    ObjectNode rootNode = objectMapper.createObjectNode();
    rootNode.put("mchid","1900009191")
            .put("appid", "wxd678efh567hg6787")
            .put("description", "Image形象店-深圳腾大-QQ公仔")
            .put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php")
            .put("out_trade_no", "1217752501201407033233368018");
    rootNode.putObject("amount")
            .put("total", 1);
    rootNode.putObject("payer")
            .put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
    
    objectMapper.writeValue(bos, rootNode);
    
    httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
    CloseableHttpResponse response = httpClient.execute(httpPost);
    
    String bodyAsString = EntityUtils.toString(response.getEntity());
    System.out.println(bodyAsString);
    
  3. 将httpClient和verifier的代码粘贴过来。修改privateKey、mchId,mchId等

      private CloseableHttpClient httpClient;
      private AutoUpdateCertificatesVerifier verifier;
    
      @Before
      public void setup() throws IOException {
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
            new ByteArrayInputStream(privateKey.getBytes("utf-8")));
    
        //使用自动更新的签名验证器,不需要传入证书
        verifier = new AutoUpdateCertificatesVerifier(
            new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
            apiV3Key.getBytes("utf-8"));
    
        httpClient = WechatPayHttpClientBuilder.create()
            .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
            .withValidator(new WechatPay2Validator(verifier))
            .build();
      }
    
  4. try-catch

  5. 运行获得prepay_id(预支付)即成功

    扫描二维码关注公众号,回复: 14463724 查看本文章

App调起支付

  1. 首先需要构造签名串

    // 在System.out.println(bodyAsString)之后写 
    String timestamp = System.currentTimeMillis()+""; // 时间戳
    String nonce = RandomUtil.randomString(32); // 随机数,使用hutool的工具类
    // 预支付交易会话ID
    Json node = objectMapper.readTree(bodyAsString); 
    String presessionid = node.get("prepay_id");
    // 应用id
    // 时间戳
    // 随机字符串
    // 预支付交易会话ID
    StringBuilder builder = new StringBuilder();
    // 拼接
    builder.append(APP_id).addpend("\n");
    .......	;
    String ciphertext = RsaCryptoUtil.encrytOAEP(builder.toString, verifier.getValidCertificate()); // 加密
    
  2. 接口参数放入map中返回给前端

    Map map = new Map();
    map.put("timestamp",timestamp);
    ..........
    

回调和验签

notify_url = “回调地址”

控制层:结果通知获取 Json,验证签名

// 获取Json,返回code
@PostMapping("callback")
public Map callback(HttpServletRequest request ){
    
    
    // 验证签名的需要的参数
    //result.getHeader("Wechatpay-Timestamp");
    //result.getHeader("Wechatpay-Nonce");
    //result.getHeader("Wechatpay-Signature");
    //result.getHeader("Wechatpay-Serial");
    
    // 将Json内容放入builder中
    Map result = new Map();
    resuit.put("code","FAILD");// 默认code失败
	try{
    
    
        BufferedReader br = request.getReader();
        String str = null;
        StringBuilder builder = new StringBuilder();
        while( (str = br.readLine())!=null ){
    
    
            builder.append(str);
        }
        // 验证签名
        
        // 解密密文
        
        // 验证订单
        
        result.put("code","SUCCESS");
    } catch(IOException e){
    
    
        e.printStackTrace();
    }  
    return result;	
}

获取后的resource需要进行解密,

验证签名

// serial:请求头中携带的序列号,  报文,  签名	
public static boolean signVerify(String serial, String message, String signature){
    
    
    // 获取 verifier
    PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
    //使用自动更新的签名验证器,不需要传入证书
    verifier = new AutoUpdateCertificatesVerifier(
        new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
        apiV3Key.getBytes(StandardCharsets.UTF_8));
    // 验证签名
    try{
    
    
    	return verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature);
    }
    catch(UnSupportedEncodingException e) {
    
    
        e.printStackTrace();
    }
    return false;
}

解密密文

node中的数据

public static String decryptOrder(String body){
    
    
    try{
    
    
	    AesUtil util = new AesUtil( PayConstants.API_V3KEY.getBytes("utf-8") );        
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode node = objectMapper.readTree(body);
        // 从node中拿到resource
        JsonNode resource = node.get("resource");
        // 拿数据密文
        String ciphertext = resouce.get("ciphertext").textValue();// 还是Json类型,需要textValue()转String
        String associatedData = resoure.get("ciphertext").textValue();
        String nonce = resoure.get("nonce").textValue();
        return util.decryToString( associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext );
    } catch(UnsupportEncodingException e){
    
    
        e.printStackTrace();
    }
    return null;

}

callback

中间步骤不是分开的,写一起吧

// 获取Json,返回code
@PostMapping("callback")
public Map callback(HttpServletRequest request ){
    
    
    // 验证签名的需要的参数
    // result.getHeader("Wechatpay-Timestamp");
    // result.getHeader("Wechatpay-Nonce");
    // result.getHeader("Wechatpay-Signature");
    // result.getHeader("Wechatpay-Serial");
    
    // 将Json内容放入builder中
    Map result = new Map();
    resuit.put("code","FAILD");// 默认code失败
	try{
    
    
        // 签名构造
		StringBuilder signStr = new StringBuiler();
       	signStr.append( result.getHeader("Wechatpay-Timestamp").append("\n") ); // 时间戳
        signStr.append( result.getHeader("Wechatpay-Nonce").append("\n") ); // 随机数
        BufferedReader br = request.getReader();
        String str = null;
        StringBuilder builder = new StringBuilder();
        while( (str = br.readLine())!=null ){
    
    
            builder.append(str);
        }
		signStr.append( builder.toString().append("\n") ); // 报文主体
        // 验证签名
        if( !signVerify(result.getHeader("Wechatpay-Serial"), signStr.toString, ), 											result.getHeader("Wechatpay-Signature")  ) {
    
    
			return result;
        }
        // 解密密文
        decryptOrder(builder.toString());
        // 验证订单
        ...................// 验证回调是否为微信官方发来的
        result.put("code","SUCCESS");
    } catch(IOException e){
    
    
        e.printStackTrace();
    }  
    return result;	
}

微信支付补充

重点步骤说明

步骤1:用户下单发起支付,商户可通过微信支付APP下单API创建支付订单。

商户调用APP下单API后,分正常返回和异常返回情况:

  • 正常返回:返回prepay_id,商户可根据返回的prepay_id来生成调用OpenSDK的签名以执行下一步。
  • 异常返回:返回http code或错误码,商户可根据http code列表错误码说明来排查原因并执行下一步操作

梳理:请结合代码"获取预下单id"一起看。

  1. 发起订单的关键语句是 CloseableHttpResponse response = httpClient.execute(httpPost),请求是不是要带一堆参数过去,参数在哪儿呢?参数都存储在了httpPost里。
  2. 参数设置都用rootNode.put()来进行设置了,设置了之后rootNode转成bos,bos放进httpPost中。这就能执行第1步了。
  3. 执行完之后,要能收到一个prepay_id。这个id通过String bodyAsString = EntityUtils.toString(response.getEntity())拿到手。

其他函数,类等都是起辅助作用。


步骤2: 商户通过APP调起支付OpenSDK调起微信支付,发起支付请求,有关OpenSDK调起支付的详细说明,请参考2.2.2部分的说明

梳理:请结合代码"App调起支付"一起看。

  1. App调起的关键代码是 api.sendReq(request)。哎,没错,要调起需要很多参数。参数在那儿呢,参数在 request中。
  2. request中有很多参数,最难的是签名。以下是签名的计算方法:
    1. 构造签名串
    2. 计算签名值:对签名串进行 SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。 签名的函为RsaCrytoUtil.encryOAEP();

步骤3: 当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答 。

拿到结果后的步骤梳理:结合“回调和验签”查看

  1. 取参数:微信会传给我们一个Json格式的数据,我们要将结果取出来
    1. 将JSON内容放入builder中,在代码中有提到,请结合查看
  2. 验签:为确保是微信官方发来的数据,需要对其数据进行验签
    1. 获取应答签名:通过request.getHeader(Wechatpay-Signatrue)得到微信发来数据中的签名
    2. 验证签名:构造一个签名和1中签名比对,比对成功则ok,不然可能是别人的攻击。过程在“验证签名”标题下的代码中
  3. 解密密文:有些数据是加密处理的,解密密文的作用是为了步骤4,代码见“解密密文标题”
  4. 验证其他参数:比如我这边的价格和微信发来数据中的价格是否一致等,这一块自己写
  5. 发回支付结果成功的消息:这一步是必须的,写一句result.put("code","SECCESS"),就ok。当然,最后需要将result返回

本文仅供参数

猜你喜欢

转载自blog.csdn.net/qq_43693424/article/details/125872305