文章目录

  • 简介
  • v2 与 v3 的区别
  • API 密钥设置
  • 获取 API 证书
  • 请求签名
    • 示例代码
      • 构造签名串
      • 构造 HTTP 头中的 Authorization
  • 获取证书序列号
    • 通过工具获取
    • 通过代码获取
  • SHA256 with RSA 签名
    • 获取商户私钥
    • 私钥签名
  • 执行请求
    • GET 请求
    • POST 请求
    • DELETE 请求
    • 上传文件
    • 构建 Http 请求头
    • 构建 Http 请求返回值
  • 验证签名
    • 构建签名参数
  • 证书和回调报文解密
    • 验证签名

简介

为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付API v3。

其实还要一个主要因素是「为了符合监管的要求」。

主要是为了符合监管的要求,保证更高的安全级别。《中华人民共和国电子签名法》、《金融电子认证规范》及《非银行支付机构网络支付业务管理办法》中规定 “电子签名需要第三方认证的,由依法设立的电子认证服务提供者提供认证服务。”,所以需使用第三方 CA 来确保数字证书的唯一性、完整性及交易的不可抵赖性。

支付宝支付也是如此,从之前的「普通公钥方式」新增了 「公钥证书方式」。今天的主角是微信支付 Api v3 这里就不展开讲支付宝支付了。

微信支付 Api v3 接口规则 官方文档

v2 与 v3 的区别

V3 规则差异 V2
JSON 参数格式 XML
POST、GET 或 DELETE 提交方式 POST
AES-256-GCM加密 回调加密 无需加密
RSA 加密 敏感加密 无需加密
UTF-8 编码方式 UTF-8
非对称密钥SHA256-RSA 签名方式 MD5 或 HMAC-SHA256

微信支付 Api-v2 版本详细介绍请参数之前博客 微信支付,你想知道的一切都在这里

干货多,屁话少,下面直接进入主题,读完全文你将 Get 到以下知识点

  • 如何获取证书序列号
  • 非对称密钥 SHA256-RSA 加密与验证签名
  • AES-256-GCM 如何解密

API 密钥设置

请登录商户平台进入【账户中心】->【账户设置】->【API安全】->【APIv3密钥】中设置 API 密钥。

具体操作步骤请参见:什么是APIv3密钥?如何设置?

获取 API 证书

请登录商户平台进入【账户中心】->【账户设置】->【API安全】根据提示指引下载证书。

具体操作步骤请参见:什么是API证书?如何获取API证书?

按照以上步骤操作后你将获取如下内容:

  • apiKey API 密钥
  • apiKey3 APIv3 密钥
  • mchId 商户号
  • apiclient_key.pem X.509 标准证书的密钥
  • apiclient_cert.p12 X.509 标准的证书+密钥
  • apiclient_cert.pem X.509 标准的证书

请求签名

如何生成签名参数?官方文档 描述得非常清楚这里就不啰嗦了。

示例代码

构造签名串

    /*** 构造签名串** @param method    {@link RequestMethod} GET,POST,PUT等* @param url       请求接口 /v3/certificates* @param timestamp 获取发起请求时的系统当前时间戳* @param nonceStr  随机字符串* @param body      请求报文主体* @return 待签名字符串*/public static String buildSignMessage(RequestMethod method, String url, long timestamp, String nonceStr, String body) {return new StringBuilder().append(method.toString()).append("\n").append(url).append("\n").append(timestamp).append("\n").append(nonceStr).append("\n").append(body).append("\n").toString();}

构造 HTTP 头中的 Authorization

/*** 构建 v3 接口所需的 Authorization** @param method    {@link RequestMethod} 请求方法* @param urlSuffix 可通过 WxApiType 来获取,URL挂载参数需要自行拼接* @param mchId     商户Id* @param serialNo  商户 API 证书序列号* @param keyPath   key.pem 证书路径* @param body      接口请求参数* @param nonceStr  随机字符库* @param timestamp 时间戳* @param authType  认证类型* @return {@link String} 返回 v3 所需的 Authorization* @throws Exception 异常信息*/
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId,String serialNo, String keyPath, String body, String nonceStr,long timestamp, String authType) throws Exception {// 构建签名参数String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);// 获取商户私钥String key = PayKit.getPrivateKey(keyPath);// 生成签名String signature = RsaKit.encryptByPrivateKey(buildSignMessage, key);// 根据平台规则生成请求头 authorizationreturn PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}/*** 获取授权认证信息** @param mchId     商户号* @param serialNo  商户API证书序列号* @param nonceStr  请求随机串* @param timestamp 时间戳* @param signature 签名值* @param authType  认证类型,目前为WECHATPAY2-SHA256-RSA2048* @return 请求头 Authorization*/
public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {Map<String, String> params = new HashMap<>(5);params.put("mchid", mchId);params.put("serial_no", serialNo);params.put("nonce_str", nonceStr);params.put("timestamp", timestamp);params.put("signature", signature);return authType.concat(" ").concat(createLinkString(params, ",", false, true));
}

拼接参数

    public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {List<String> keys = new ArrayList<String>(params.keySet());Collections.sort(keys);StringBuilder content = new StringBuilder();for (int i = 0; i < keys.size(); i++) {String key = keys.get(i);String value = params.get(key);// 拼接时,不包括最后一个&字符if (i == keys.size() - 1) {if (quotes) {content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');} else {content.append(key).append("=").append(encode ? urlEncode(value) : value);}} else {if (quotes) {content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);} else {content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);}}}return content.toString();}

从上面示例来看我们还差两个参数

  • serial_no 证书序列号
  • signature 使用商户私钥对待签名串进行 SHA256 with RSA 签名

如何获取呢?不要着急,容我喝杯 「89年的咖啡」提提神。

获取证书序列号

通过工具获取

  • openssl x509 -in apiclient_cert.pem -noout -serial
  • 使用证书解析工具 https://myssl.com/cert_decode.html

通过代码获取

// 获取证书序列号
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream("apiclient_cert.pem 证书路径"));System.out.println("输出证书信息:\n" + certificate.toString());
System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
System.out.println("版本号:" + certificate.getVersion());
System.out.println("签发者:" + certificate.getIssuerDN());
System.out.println("有效起始日期:" + certificate.getNotBefore());
System.out.println("有效终止日期:" + certificate.getNotAfter());
System.out.println("主体名:" + certificate.getSubjectDN());
System.out.println("签名算法:" + certificate.getSigAlgName());
System.out.println("签名:" + certificate.getSignature().toString());/*** 获取证书** @param inputStream 证书文件* @return {@link X509Certificate} 获取证书*/
public static X509Certificate getCertificate(InputStream inputStream) {try {CertificateFactory cf = CertificateFactory.getInstance("X509");X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);cert.checkValidity();return cert;} catch (CertificateExpiredException e) {throw new RuntimeException("证书已过期", e);} catch (CertificateNotYetValidException e) {throw new RuntimeException("证书尚未生效", e);} catch (CertificateException e) {throw new RuntimeException("无效的证书", e);}
}

SHA256 with RSA 签名

获取商户私钥

 /*** 获取商户私钥** @param keyPath 商户私钥证书路径* @return 商户私钥* @throws Exception 解析 key 异常*/public static String getPrivateKey(String keyPath) throws Exception {String originalKey = FileUtil.readUtf8String(keyPath);String privateKey = originalKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");return RsaKit.getPrivateKeyStr(RsaKit.loadPrivateKey(privateKey));}public static String getPrivateKeyStr(PrivateKey privateKey) {return Base64.encode(privateKey.getEncoded());
}/*** 从字符串中加载私钥** @param privateKeyStr 私钥* @return {@link PrivateKey}* @throws Exception 异常信息*/public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {try {byte[] buffer = Base64.decode(privateKeyStr);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);return keyFactory.generatePrivate(keySpec);} catch (NoSuchAlgorithmException e) {throw new Exception("无此算法");} catch (InvalidKeySpecException e) {throw new Exception("私钥非法");} catch (NullPointerException e) {throw new Exception("私钥数据为空");}}

私钥签名

/*** 私钥签名** @param data       需要加密的数据* @param privateKey 私钥* @return 加密后的数据* @throws Exception 异常信息*/public static String encryptByPrivateKey(String data, String privateKey) throws Exception {PKCS8EncodedKeySpec priPkcs8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);PrivateKey priKey = keyFactory.generatePrivate(priPkcs8);java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");signature.initSign(priKey);signature.update(data.getBytes(StandardCharsets.UTF_8));byte[] signed = signature.sign();return StrUtil.str(Base64.encode(signed));}

至此微信支付 Api-v3 接口请求参数已封装完成。

执行请求

/*** V3 接口统一执行入口** @param method    {@link RequestMethod} 请求方法* @param urlPrefix 可通过 {@link WxDomain}来获取* @param urlSuffix 可通过 {@link WxApiType} 来获取,URL挂载参数需要自行拼接* @param mchId     商户Id* @param serialNo  商户 API 证书序列号* @param keyPath   apiclient_key.pem 证书路径* @param body      接口请求参数* @param nonceStr  随机字符库* @param timestamp 时间戳* @param authType  认证类型* @param file      文件* @return {@link String} 请求返回的结果* @throws Exception 接口执行异常*/
public static Map<String, Object> v3Execution(RequestMethod method, String urlPrefix, String urlSuffix,String mchId, String serialNo, String keyPath, String body,String nonceStr, long timestamp, String authType,File file) throws Exception {// 构建 AuthorizationString authorization = WxPayKit.buildAuthorization(method, urlSuffix, mchId, serialNo,keyPath, body, nonceStr, timestamp, authType);if (method == RequestMethod.GET) {return doGet(urlPrefix.concat(urlSuffix), authorization, serialNo, null);} else if (method == RequestMethod.POST) {return doPost(urlPrefix.concat(urlSuffix), authorization, serialNo, body);} else if (method == RequestMethod.DELETE) {return doDelete(urlPrefix.concat(urlSuffix), authorization, serialNo, body);} else if (method == RequestMethod.UPLOAD) {return doUpload(urlPrefix.concat(urlSuffix), authorization, serialNo, body, file);}return null;
}

网络请求库默认是使用的 Hutool 封装的一套 Java 工具集合来实现

GET 请求

/*** @param url           请求url* @param authorization 授权信息* @param serialNumber  公钥证书序列号* @param jsonData      请求参数* @return {@link HttpResponse} 请求返回的结果*/
private HttpResponse doGet(String url, String authorization, String serialNumber, String jsonData) {return HttpRequest.post(url).addHeaders(getHeaders(authorization, serialNumber)).body(jsonData).execute();
}

POST 请求

 /*** @param url           请求url* @param authorization 授权信息* @param serialNumber  公钥证书序列号* @param jsonData      请求参数* @return {@link HttpResponse} 请求返回的结果*/private HttpResponse doPost(String url, String authorization, String serialNumber, String jsonData) {return HttpRequest.post(url).addHeaders(getHeaders(authorization, serialNumber)).body(jsonData).execute();}

DELETE 请求

/*** delete 请求** @param url           请求url* @param authorization 授权信息* @param serialNumber  公钥证书序列号* @param jsonData      请求参数* @return {@link HttpResponse} 请求返回的结果*/
private HttpResponse doDelete(String url, String authorization, String serialNumber, String jsonData) {return HttpRequest.delete(url).addHeaders(getHeaders(authorization, serialNumber)).body(jsonData).execute();
}

上传文件

 /*** @param url           请求url* @param authorization 授权信息* @param serialNumber  公钥证书序列号* @param jsonData      请求参数* @param file          上传的文件* @return {@link HttpResponse} 请求返回的结果*/private HttpResponse doUpload(String url, String authorization, String serialNumber, String jsonData, File file) {return HttpRequest.post(url).addHeaders(getUploadHeaders(authorization, serialNumber)).form("file", file).form("meta", jsonData).execute();}

构建 Http 请求头

private Map<String, String> getBaseHeaders(String authorization) {String userAgent = String.format("WeChatPay-IJPay-HttpClient/%s (%s) Java/%s",getClass().getPackage().getImplementationVersion(),OS,VERSION == null ? "Unknown" : VERSION);Map<String, String> headers = new HashMap<>(3);headers.put("Accept", ContentType.JSON.toString());headers.put("Authorization", authorization);headers.put("User-Agent", userAgent);return headers;
}private Map<String, String> getHeaders(String authorization, String serialNumber) {Map<String, String> headers = getBaseHeaders(authorization);headers.put("Content-Type", ContentType.JSON.toString());if (StrUtil.isNotEmpty(serialNumber)) {headers.put("Wechatpay-Serial", serialNumber);}return headers;
}private Map<String, String> getUploadHeaders(String authorization, String serialNumber) {Map<String, String> headers = getBaseHeaders(authorization);headers.put("Content-Type", "multipart/form-data;boundary=\"boundary\"");if (StrUtil.isNotEmpty(serialNumber)) {headers.put("Wechatpay-Serial", serialNumber);}return headers;
}

构建 Http 请求返回值

从响应的 HttpResponse 中获取微信响应头信息、状态码以及 body

/*** 构建返回参数** @param httpResponse {@link HttpResponse}* @return {@link Map}*/
private Map<String, Object> buildResMap(HttpResponse httpResponse) {Map<String, Object> map = new HashMap<>();String timestamp = httpResponse.header("Wechatpay-Timestamp");String nonceStr = httpResponse.header("Wechatpay-Nonce");String serialNo = httpResponse.header("Wechatpay-Serial");String signature = httpResponse.header("Wechatpay-Signature");String body = httpResponse.body();int status = httpResponse.getStatus();map.put("timestamp", timestamp);map.put("nonceStr", nonceStr);map.put("serialNumber", serialNo);map.put("signature", signature);map.put("body", body);map.put("status", status);return map;
}

至此已完成构建请求参数,执行请求。接下来我们就要实现响应数据的解密以及响应结果的验证签名

对应的官方文档

  • 证书和回调报文解密
  • 签名验证

验证签名

构建签名参数

/*** 构造签名串** @param timestamp 应答时间戳* @param nonceStr  应答随机串* @param body      应答报文主体* @return 应答待签名字符串*/
public static String buildSignMessage(String timestamp, String nonceStr, String body) {return new StringBuilder().append(timestamp).append("\n").append(nonceStr).append("\n").append(body).append("\n").toString();
}

证书和回调报文解密

官方文档文末有完整的源码这里就不贴了。贴一个示例大家参数一下

try {String associatedData = "certificate";String nonce = "80d28946a64a";String cipherText = "DwAqW4+4TeUaOEylfKEXhw+XqGh/YTRhUmLw/tBfQ5nM9DZ9d+9aGEghycwV1jwo52vXb/t6ueBvBRHRIW5JgDRcXmTHw9IMTrIK6HxTt2qiaGTWJU9whsF+GGeQdA7gBCHZm3AJUwrzerAGW1mclXBTvXqaCl6haE7AOHJ2g4RtQThi3nxOI63/yc3WaiAlSR22GuCpy6wJBfljBq5Bx2xXDZXlF2TNbDIeodiEnJEG2m9eBWKuvKPyUPyClRXG1fdOkKnCZZ6u+ipb4IJx28n3MmhEtuc2heqqlFUbeONaRpXv6KOZmH/IdEL6nqNDP2D7cXutNVCi0TtSfC7ojnO/+PKRu3MGO2Z9q3zyZXmkWHCSms/C3ACatPUKHIK+92MxjSQDc1E/8faghTc9bDgn8cqWpVKcL3GHK+RfuYKiMcdSkUDJyMJOwEXMYNUdseQMJ3gL4pfxuQu6QrVvJ17q3ZjzkexkPNU4PNSlIBJg+KX61cyBTBumaHy/EbHiP9V2GeM729a0h5UYYJVedSo1guIGjMZ4tA3WgwQrlpp3VAMKEBLRJMcnHd4pH5YQ/4hiUlHGEHttWtnxKFwnJ6jHr3OmFLV1FiUUOZEDAqR0U1KhtGjOffnmB9tymWF8FwRNiH2Tee/cCDBaHhNtfPI5129SrlSR7bZc+h7uzz9z+1OOkNrWHzAoWEe3XVGKAywpn5HGbcL+9nsEVZRJLvV7aOxAZBkxhg8H5Fjt1ioTJL+qXgRzse1BX1iiwfCR0fzEWT9ldDTDW0Y1b3tb419MhdmTQB5FsMXYOzqp5h+Tz1FwEGsa6TJsmdjJQSNz+7qPSg5D6C2gc9/6PkysSu/6XfsWXD7cQkuZ+TJ/Xb6Q1Uu7ZB90SauA8uPQUIchW5zQ6UfK5dwMkOuEcE/141/Aw2rlDqjtsE17u1dQ6TCax/ZQTDQ2MDUaBPEaDIMPcgL7fCeijoRgovkBY92m86leZvQ+HVbxlFx5CoPhz4a81kt9XJuEYOztSIKlm7QNfW0BvSUhLmxDNCjcxqwyydtKbLzA+EBb2gG4ORiH8IOTbV0+G4S6BqetU7RrO+/nKt21nXVqXUmdkhkBakLN8FUcHygyWnVxbA7OI2RGnJJUnxqHd3kTbzD5Wxco4JIQsTOV6KtO5c960oVYUARZIP1SdQhqwELm27AktEN7kzg/ew/blnTys/eauGyw78XCROb9F1wbZBToUZ7L+8/m/2tyyyqNid+sC9fYqJoIOGfFOe6COWzTI/XPytCHwgHeUxmgk7NYfU0ukR223RPUOym6kLzSMMBKCivnNg68tbLRJHEOpQTXFBaFFHt2qpceJpJgw5sKFqx3eQnIFuyvA1i8s2zKLhULZio9hpsDJQREOcNeHVjEZazdCGnbe3Vjg7uqOoVHdE/YbNzJNQEsB3/erYJB+eGzyFwFmdAHenG5RE6FhCutjszwRiSvW9F7wvRK36gm7NnVJZkvlbGwh0UHr0pbcrOmxT81xtNSvMzT0VZNLTUX2ur3AGLwi2ej8BIC0H41nw4ToxTnwtFR1Xy55+pUiwpB7JzraA08dCXdFdtZ72Tw/dNBy5h1P7EtQYiKzXp6rndfOEWgNOsan7e1XRpCnX7xoAkdPvy40OuQ5gNbDKry5gVDEZhmEk/WRuGGaX06CG9m7NfErUsnQYrDJVjXWKYuARd9R7W0aa5nUXqz/Pjul/LAatJgWhZgFBGXhNr9iAoade/0FPpBj0QWa8SWqKYKiOqXqhfhppUq35FIa0a1Vvxcn3E38XYpVZVTDEXcEcD0RLCu/ezdOa6vRcB7hjgXFIRZQAka0aXnQxwOZwE2Rt3yWXqc+Q1ah2oOrg8Lg3ETc644X9QP4FxOtDwz/A==";AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));// 平台证书密文解密// encrypt_certificate 中的  associated_data nonce  ciphertextString publicKey = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),cipherText);// 保存证书FileWriter writer = new FileWriter(wxPayV3Bean.getPlatformCertPath());writer.write(publicKey);// 获取平台证书序列号X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));return certificate.getSerialNumber().toString(16).toUpperCase();} catch (Exception e) {e.printStackTrace();}

验证签名

/*** 验证签名** @param signature       待验证的签名* @param body            应答主体* @param nonce           随机串* @param timestamp       时间戳* @param certInputStream 微信支付平台证书输入流* @return 签名结果* @throws Exception 异常信息*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);// 获取证书X509Certificate certificate = PayKit.getCertificate(certInputStream);PublicKey publicKey = certificate.getPublicKey();return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}/*** 公钥验证签名** @param data      需要加密的数据* @param sign      签名* @param publicKey 公钥* @return 验证结果* @throws Exception 异常信息*/
public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");signature.initVerify(publicKey);signature.update(data.getBytes(StandardCharsets.UTF_8));return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
}

至此微信支付 Api-v3 接口已介绍完,如有疑问欢迎留言一起探讨。

完整示例 SpringBoot

参考资料

  • 你真的了解 HTTPS 吗?
  • WechatPay-API-v3

一文搞懂「微信支付 Api-v3」接口规则所有知识点相关推荐

  1. 一文搞懂如何使用ArcGIS API for Python训练深度学习模型

    一文搞懂如何使用ArcGIS API for Python训练深度学习模型 文章目录 一文搞懂如何使用ArcGIS API for Python训练深度学习模型 写在前面 一.ArcGIS API f ...

  2. 微信支付API v3接口使用应用篇

    目录 前言 版本 应用 基础配置 1.申请商户API证书 2.设置接口密钥 3.下载平台证书 接口实测 微信支付API官方客户端 1.客户端 2.支付调起参数签名 3.回调通知 参考资料 前言 最近新 ...

  3. 实用教程 | 一文读懂「微信分账」功能

    在现有的微信支付互联网生态环境中,存在平台方角色,如电商平台.加盟代理等,用户支付订单金额后,先由平台方统一收款,再由平台方与商家或其他参与方进行结算,若无引入具有支付牌照的第三方,则为「二清」行为, ...

  4. 微信支付API V3版本JAVA开发指南

    微信支付版本V3的Demo,在官方上下载下来,压根就是不能直接用的东西,你要想学会用,你就得一层一层的看源码,看文档,要求你事无巨细的做一个接入者. 如果接入API需要让人看源码来理解,我觉得是一件让 ...

  5. 一文搞懂 Service Mesh 和 API Gateway 关系和区别

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 关于Service Mesh和API Gateway之间的关系,这个问题过去两年间经常被问起,社区也有不少文章和资 ...

  6. 一文看懂「生成对抗网络 - GAN」基本原理+10种典型算法+13种应用

    生成对抗网络 – Generative Adversarial Networks | GAN 文章目录 GAN的设计初衷 生成对抗网络 GAN 的基本原理 GAN的优缺点 10大典型的GAN算法 GA ...

  7. 微信支付API v3签名与验签-APP支付问题

    目录 使用API v3微信支付遇到的问题: 1.微信请求客户端配置 2.生成预付款订单 3.拼接字符串使用API v3签名 4.微信支付成功后通知 使用API v3微信支付遇到的问题: 1.jdk版本 ...

  8. 微信支付API v3 Native支付

    废话不多说直接上代码  不熟悉的直接私信我 依赖 <dependency><groupId>com.github.wechatpay-apiv3</groupId> ...

  9. 支付: 一文搞懂62个支付名词 (3)

    支付路由设计详解-最美算法(4) 支付名词 支付体系很庞大,有很多名词总是模棱两可,支付产品,支付方式,支付类型,支付通道,等等:这篇文章尽可能详尽,可能会有遗漏,后面针对每一块的详解里还会继续介绍 ...

最新文章

  1. Nature:学术造假者瑟瑟发抖,论文图像查重AI技术重拳出击!
  2. 1.1 Spring的整体架构--Spring源码深度解析
  3. python时间处理模块有哪些_Python模块之时间处理
  4. Javascript在页面加载时的执行顺序(转载)
  5. C++中string.find()的误用
  6. postgress无法远程连接问题解决方案
  7. Win10 Anaconda Prompt 快捷方式恢复
  8. 深入浅出4G标准:LTE FDD和LTE TDD
  9. 论文笔记A Liver Segmentation Method Based on the Fusion of VNet and WGAN
  10. 【Java】Deprecated 注解
  11. localstorage ie11不支持
  12. 学习---微分_中值定理及洛必达法则
  13. 友情的目的和作为目的的友情
  14. 如何在iPad,iPad mini,iPad Air和iPad Pro之间进行选择?
  15. [计算机视觉] AprilTag: A robust and flexible visual fiducial system(2011)论文理解
  16. html圆形图片切换,jQuery和CSS3炫酷圆形图片切换特效
  17. SpringBoot整合MybatisPlus遇到的大坑!
  18. 给女朋友做个聊天机器人,这样就能安心写代码了
  19. 编写优雅的JavaScript——前言
  20. Dubbo剖析-粘包与半包问题(一)

热门文章

  1. 黑产工具情报的分析方式浅析
  2. es查询简单场景问题小记
  3. MSDC 4.3 接口规范(3)
  4. 纸飞机 --2013-08-08博客搬家
  5. 用python写家族树的基本事实
  6. 降噪效果好的蓝牙耳机该怎么选?盘点四款高品质降噪蓝牙耳机
  7. 启用mysql系统找不到指定的文件类型_net start mysql 发生系统错误2 系统找不到指定的文件...
  8. 江苏大学正版Windows和Office全家桶--UJS微软正版化服务平台
  9. 浅析专题中的构图之美
  10. 决策树与随机森林(从入门到精通)