微信支付 步骤简介

微信支付_V3版本

官方文档
需要下载wechatpay-apache-httpclient,很多具体的代码都在这个文档里。

需要简单了解的:

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

这些是英文和概念需要知道,且其中某些是需要去阿里云获取的:

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

本文的目的是 帮助读者梳理核心步骤,以至于在开发中不会显得脑瓜子很乱。
没有完完整整的开发步骤,没有完完整整的代码。

微信支付总结

官方文档_开发指引

总共有三个大步骤,每个大步骤中都有一个梳理,将大步骤分解为几个小步骤。
为帮助理解,梳理部分是按着代码倒叙方式写的。
可以先看梳理部分。

步骤1:

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

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

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

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

  1. execute():
    发起订单的关键语句是 CloseableHttpResponse response = httpClient.execute(httpPost)
    请求是不是要带一堆参数过去?参数在哪儿呢?参数都存储在了httpPost里。
  2. 参数设置和变换:
    参数都用rootNode.put()来进行设置了,设置了之后rootNode转成bos,bos放进httpPost中。
    rootNode —> bos —>htpPost。
    这就能执行1.execute()了。
  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格式的数据,我们要将结果取出来

    将JSON内容放入builder中,在代码中有提到,请结合查看

  2. 验签:为确保是微信官方发来的数据,需要对其数据进行验签

    1. 获取应答签名:通过request.getHeader(Wechatpay-Signatrue)得到微信发来数据中的签名
    2. 验证签名:构造一个签名和1中签名比对,比对成功则ok,不然可能是别人的攻击。过程在“验证签名”标题下的代码中
  3. 解密密文:有些数据是加密处理的,解密密文的作用是为了步骤4,代码见“解密密文标题”

  4. 验证其他参数:比如我这边的价格和微信发来数据中的价格是否一致等,这一块自己写

  5. 发回支付结果成功的消息:这一步是必须的,写一句result.put("code","SECCESS"),就ok。当然,最后需要将result返回

签名有两个:一个是发送时,需要给微信服务器的。一个是从服务器接收的消息,需要验证是微信服务器,而不是其他黑客分子的。

1. 获取预下单ID

官方文档地址_APP下单API

  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()来进行设置了
    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");
    // rootNode转成bos
    objectMapper.writeValue(bos, rootNode);
    // bos放进httpPost中
    httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
    // execute(),参数都存储在了httpPost中
    CloseableHttpResponse response = httpClient.execute(httpPost); 
    // 执行完之后,要能收到一个prepay_id,你可以认为就是 bodyAsString
    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(预支付)即成功

2. App调起支付

  1. 首先需要构造签名串,计算签名值

    // 在System.out.println(bodyAsString)之后写
    
    // 1. 构造签名串
    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");
    .......	;
    // 2. 计算签名值
    String ciphertext = RsaCryptoUtil.encrytOAEP(builder.toString, verifier.getValidCertificate()); // 加密
    
  2. 将接口参数放入map中返回给前端(这一块可以先不看)

    Map map = new Map();
    map.put("timestamp",timestamp);
    ..........
    
IWXAPI api;
PayReq request = new PayReq();
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
api.sendReq(request); // sendReq()

3. 回调和验签(下面有总的callback)

梳理部分的大框架:

// 获取Json,返回code
@PostMapping("callback")
public Map callback(HttpServletRequest request ){
    //1. 验证签名的需要的参数
    //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);
        }
        // 2. 验证签名
        
        // 3. 解密密文
        
        // 4. 验证订单
        
        // 5. 发回结果
        result.put("code","SUCCESS");
    } catch(IOException e){
        e.printStackTrace();
    }  
    return result;	
}

3.1 验证签名

// 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;
}

3.2 解密密文

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;

}

3.3 总的callback

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

// 获取Json,返回code
@PostMapping("callback")
public Map callback(HttpServletRequest request ){
    // 1. 验证签名的需要的参数
    // 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") ); // 报文主体
        // 2. 验证签名
        if( !signVerify(result.getHeader("Wechatpay-Serial"), signStr.toString, ), 											result.getHeader("Wechatpay-Signature")  ) {
			return result;
        }
        // 3. 解密密文
        decryptOrder(builder.toString());
        // 4. 验证订单
        ...................// 验证回调是否为微信官方发来的
        // 5. 发回结果
        result.put("code","SUCCESS");
    } catch(IOException e){
        e.printStackTrace();
    }  
    return result;	
}

如果看着还是很凌乱,建议看看这个视频操作一下

本文仅供参数

你可能感兴趣的:(微信,java,前端,后端)