支付宝支付架构

# 支付宝支付架构 ## 相关知识点 ### 对称加密 #### 对称加密是什么? > 首先, 服务器会生成一个密钥, 然后将该密钥的副本传输给用户, 数据传输之前, 会用该密钥对数据进行加密, 数据到达后, 通过该密钥进行解密 #### 对称加密为什么不安全? > **因为传输密钥的时候可能会被别人截取, 一旦被别人截取, 数据就很可能被恶意网关解密, 数据泄漏的同时, 也会造成传输过程中数据被恶意篡改** ![image.png](https://cos.easydoc.net/13568421/files/lmlpdy8z.png) ### 非对称加密 #### 非对称加密是什么? > 首先, 服务器会生成一个密钥对, 一个是公钥, 一个是私钥, 公钥用于传输, 私钥是需要做到绝对保密的, 将公钥传输给用户, 下次数据传输的时候(用户), 会使用公钥加密, 最后由服务器端的私钥进行解密, 公钥加密, 私钥解密, 私钥加密, 公钥解密, 公钥加密, 公钥不能解密, 私钥同理 #### 非对称加密为什么不安全? > **因为有可能在公钥传输的时候, 恶意网关拦截真公钥, 将一个假公钥给了用户, 用户用假公钥加密, 恶意网关用假密钥解密, 然后用真公钥加密, 服务器用真密钥解密, 这样就被第三方恶意篡改数据了, 因此非对称加密也不安全** ![image.png](https://cos.easydoc.net/13568421/files/lmlpkh24.png) > **公钥是给别人的, 私钥是给自己的** ### 支付宝的架构 ![image.png](https://cos.easydoc.net/13568421/files/lmlpqal2.png) > 简单来说, 用户创建一个密钥对, 将用户公钥传输给支付宝服务端, 而支付宝服务端给我们一个支付宝公钥, 即我们获取了支付宝公钥和用户私钥, 支付宝获取了支付宝私钥(这个几乎不可能被偷取)和用户公钥 > **数据传输的时候, 均用公钥加密, 私钥解密的形式, 但是, 单纯加密和解密一定会有问题, 如果用户的密钥对泄漏了, 支付宝服务端响应的数据可能会被恶意篡改** > 最简单那的场景, 第三方通过使用泄漏的密钥对修改响应结果, 让用户一直支付, 造成用户财产损失 ### 加签和验签 > 为了解决上述情况, 无论是哪一方, 发送数据的时候都要对原数据进行特殊的数据摘要(加签), 数据到达后, 通过密钥解密, 在进行数据摘要判断与之前的数据摘要结果比较, 如果没有改变则验签成功, 这样解决了数据篡改问题 ### 为什么需要内网穿透? > **因为当我们支付成功后, 无论是同步回调(支付成功, 需要跳转到某个页面), 还是异步回调(支付成功, 进行异步通知), 都是支付宝端发的请求, 即, 需要我的的应用能被外网访问, 因此, 我们需要内网穿透** ### 内网穿透原理 ![image.png](https://cos.easydoc.net/13568421/files/lmlqaaj2.png) ![image.png](https://cos.easydoc.net/13568421/files/lmlqc39h.png) ## 支付宝沙箱申请流程 > [密钥对文档](https://opendocs.alipay.com/common/02kipk?pathHash=0d20b438) > **根据文档, 完成对服务器的密钥设置, 获取服务器的公钥, 最终, 我们需要获取到支付宝公钥和应用私钥** ![image.png](https://cos.easydoc.net/13568421/files/lmlfp7a2.png) > [下载Demo场景](https://opendocs.alipay.com/open/270/106291) > 这个项目是一个eclipse项目, 我们需要把他转换成Idea项目, [参考博客](https://blog.csdn.net/qq_41799219/article/details/103931450#:~:text=%E6%95%99%E4%BD%A0%E4%BB%A5%E6%9C%80%E5%B9%B2%E5%87%80%E7%9A%84%E6%96%B9%E5%BC%8F%E7%94%A8IDEA%E6%89%93%E5%BC%80eclipse%E9%A1%B9%E7%9B%AE%201%E3%80%81%E5%8E%BB%E9%99%A4%E4%B8%8D%E5%BF%85%E8%A6%81%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6,%E5%85%B6%E5%AE%9E%E5%88%B0%E8%BF%99%E9%87%8C%EF%BC%8C%E5%B0%B1%E6%98%AF%E6%8A%8Aeclipse%E7%9A%84%E6%89%80%E6%9C%89%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%8F%8A%E7%94%9F%E6%88%90%E7%9A%84%E6%96%87%E4%BB%B6%E9%83%BD%E5%88%A0%E9%99%A4%EF%BC%8C%E5%8F%AA%E7%95%99%E4%B8%8B%E9%A1%B9%E7%9B%AE%E7%9A%84%E4%BB%A3%E7%A0%81%E3%80%82%202%E3%80%81%E7%94%A8IDEA%E5%AF%BC%E5%85%A5%E9%A1%B9%E7%9B%AE) > **配置成web项目** ![image.png](https://cos.easydoc.net/13568421/files/lmlhrux6.png) > **配置项目Tomcat** ![image.png](https://cos.easydoc.net/13568421/files/lmlgs5gk.png) > **注意: 引入的是web** > **修改配置并测试** ### Bug修复 #### 错误页面 ![image.png](https://cos.easydoc.net/13568421/files/lmlhvgni.png) >**这是因为支付宝网关出现了错误, 需要把两个网关都设置成沙箱环境** #### 访问不了页面 >**这是因为没有配置web环境** #### 启动问题 > 如果启动发现报无法解析编码等相关问题, 可以尝试更改页面编码,然后该回去, 再重启即可 ## 内网穿透流程 1. [下载花生壳](https://hsk.oray.com/download) 2. **设置Demo环境的内网穿透** ![image.png](https://cos.easydoc.net/13568421/files/lmn5qksj.png) 3. 测试(成功) ![image.png](https://cos.easydoc.net/13568421/files/lmlqyqhy.png) ## 导入项目流程 > **确保项目的编码是UTF-8(所有)** ```xml <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.34.0.ALL</version> <exclusions> <exclusion> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </exclusion> </exclusions> </dependency> <dependency> ``` ```yaml alipay: app_id: 沙箱应用ID merchant_private_key: 应用私钥 alipay_public_key: 阿里云公钥 notify_url: http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp return_url: http://工程公网访问地址/alipay.trade.page.pay-JAVA-UTF-8/notify_url.jsp sign_type: RSA2 charset: utf-8 gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do log_path: https://openapi-sandbox.dl.alipaydev.com/gateway.do ``` ```java @Configuration @EnableConfigurationProperties(AlipayProperties.class) public class AlipayTemplate { @Autowired private AlipayProperties alipayProperties; public String pay(PayVO payVO) throws AlipayApiException { //获得初始化的AlipayClient AlipayClient alipayClient = new DefaultAlipayClient(alipayProperties.getGatewayUrl(), alipayProperties.getApp_id(), alipayProperties.getMerchant_private_key(), "json", alipayProperties.getCharset(), alipayProperties.getAlipay_public_key(), alipayProperties.getSign_type()); //设置请求参数 AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); alipayRequest.setReturnUrl(alipayProperties.getReturn_url()); alipayRequest.setNotifyUrl(alipayProperties.getNotify_url()); //商户订单号,商户网站订单系统中唯一订单号,必填 String out_trade_no = payVO.getOut_trade_no(); //付款金额,必填 String total_amount = payVO.getTotal_amount().toString(); //订单名称,必填 String subject = payVO.getSubject(); //商品描述,可空 String body = payVO.getBody(); alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\"," + "\"total_amount\":\""+ total_amount +"\"," + "\"subject\":\""+ subject +"\"," + "\"body\":\""+ body +"\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}"); //若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明 //alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\"," // + "\"total_amount\":\""+ total_amount +"\"," // + "\"subject\":\""+ subject +"\"," // + "\"body\":\""+ body +"\"," // + "\"timeout_express\":\"10m\"," // + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}"); //请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节 //请求 return alipayClient.pageExecute(alipayRequest).getBody(); } } ``` ```java @ConfigurationProperties(prefix = "spring.alipay") @Data public class AlipayProperties { // 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号 private String app_id; // 商户私钥,您的PKCS8格式RSA2私钥 private String merchant_private_key; // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。 private String alipay_public_key; // 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 private String notify_url; // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 private String return_url; // 签名方式 private String sign_type; // 字符编码格式 private String charset; // 支付宝网关 private String gatewayUrl; // 支付宝网关 private String log_path; } ``` ```java @Data public class PayVO { /** * 订单号 */ private String out_trade_no; /** * 应付总额: 必须保留两位小数, 否则会参数异常 */ private BigDecimal total_amount; /** * 标题 */ private String subject; /** * 备注 */ private String body; } ``` ```java @RestController @RequestMapping("/alipay") public class AlipayController { @Autowired private AlipayTemplate alipayTemplate; /** * 跳转支付页面 * @return */ @PostMapping("/toPay") public String toPay(PayVO payVO) throws AlipayApiException { payVO.setTotal_amount(payVO.getTotal_amount().setScale(2)); return alipayTemplate.pay(payVO); } } ``` > **最终引入依赖** # Bug修复 ## 工程里面的模块突然间和工程同级 > 解决方案很简单, 右键打开maven, 重新加载工程, 直到全部消失即可 ## 又出现无法反序列化的问题 > **这个原因本质上是因为引入的支付依赖中存在fastJson的依赖, 这个依赖导致我们之前的底层逻辑改变了, 所以又出现了反序列化问题, 因此, 我们需要排除依赖** ```xml <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.34.0.ALL</version> <exclusions> <exclusion> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </exclusion> </exclusions> </dependency> ``` ## 参数问题 ![image.png](https://cos.easydoc.net/13568421/files/lmlzae0p.png) > 这是因为应付总额的小数大于2, 所以, 参数异常, 把应付总额的小数变成2即可 # 其他知识 ## 为什么跳转支付页面调用POST请求? >** 因为如果按照老师发送GET请求, 仅仅会有订单号, 我们还需要调用其他微服务查询数据, 效率极低, 如果发送POST请求, 不仅对请求参数做出了保护, 而且数据可在请求域中获取, 效率极大的提高**