接入支付宝/微信支付、调用接口生成二维码图片的链接、Hutool生成二维码图片文件

1.1、万维易源/Hutool工具

(1)官网注册开发者账号:https://www.showapi.com/

image-20210719111859369

(2)点击验证邮箱

image-20210719111938507

(3)进入控制台,点击 接口使用者 —> 我的应用,获取showapi_appid 和 secret

image-20210719114741793

当前使用的是生成二维码的功能接口,官网中会有很多业务统计或其他功能,大家可以自行了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.itheima.restkeeper;

import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;

/**
* @Description:
* @Version: V1.0
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class QrCodeTest {

@Autowired
RestTemplate restTemplate;


@Test
public void testQRcode(){
//生成二维码
String url = "http://route.showapi.com/887-1" +
"?showapi_appid=710810" +
"&showapi_sign=a35b3ebc52d04d159a9a60fa0335709b"+
"&content=http://www.itcast.cn"+
"&size=8" +
"&imgExtName=png";
String showApiSring = restTemplate.getForObject(url, String.class);
JSONObject jsonObject = JSONObject.parseObject(showApiSring);
String qrcodeUrl = jsonObject.getJSONObject("showapi_res_body").getString("imgUrl");
System.out.println(qrcodeUrl);
}
}

或者是使用 Hutool,Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类。

同时提供以下组件:

模块介绍
hutool-aopJDK动态代理封装,提供非IOC下的切面支持
hutool-bloomFilter布隆过滤,提供一些Hash算法的布隆过滤
hutool-cache简单缓存实现
hutool-core核心,包括Bean操作、日期、各种Util等
hutool-cron定时任务模块,提供类Crontab表达式的定时任务
hutool-crypto加密解密模块,提供对称、非对称和摘要算法封装
hutool-dbJDBC封装后的数据操作,基于ActiveRecord思想
hutool-dfa基于DFA模型的多关键字查找
hutool-extra扩展模块,对第三方封装
(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等)
hutool-http基于HttpUrlConnection的Http客户端封装
hutool-log自动识别日志实现的日志门面
hutool-script脚本执行封装,例如Javascript
hutool-setting功能更强大的Setting配置文件和Properties封装
hutool-system系统参数调用封装(JVM信息等)
hutool-jsonJSON实现
hutool-captcha图片验证码实现
hutool-poi针对POI中Excel和Word的封装
hutool-socket基于Java的NIO和AIO的Socket封装
hutool-jwtJSON Web Token (JWT)封装实现

可以根据需求对每个模块单独引入,也可以通过引入hutool-all方式引入所有模块。

1
2
3
4
5
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.5</version>
</dependency>

二维码快速生成见文档:https://www.hutool.cn/docs/#/extra/%E4%BA%8C%E7%BB%B4%E7%A0%81%E5%B7%A5%E5%85%B7-QrCodeUtil

依赖:

1
2
3
4
5
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>

核心代码:

1
2
3
4
5
6
7
// 生成指定url对应的二维码到文件,宽和高都是300像素
QrCodeUtil.generate("https://hutool.cn/", 300, 300, FileUtil.file("d:/qrcode.jpg"));

//生成Base64格式
String base64 = QrCodeUtil.
generateAsBase64("http://www.itheima.com", new QrConfig(300, 300), "png");
System.out.println(base64);

很多时候,二维码无法识别,这时就要调整纠错级别。纠错级别使用zxing的ErrorCorrectionLevel枚举封装,包括:L、M、Q、H几个参数,由低到高。低级别的像素块更大,可以远距离识别,但是遮挡就会造成无法识别。高级别则相反,像素块小,允许遮挡一定范围,但是像素块更密集。

1.2、支付宝支付

1.2.1、准备环境

详细参考:讲义/支付宝支付接入指南.pdf

总结步骤:

  1. 注册开放平台账号

    注册地址:https://developers.alipay.com/developmentAccess/developmentAccess.htm

  2. 支付宝沙箱环境配置

    沙箱地址:https://openhome.alipay.com/platform/appDaily.htm

  3. 使用支付沙箱需要配置密钥

    在线生成秘钥:https://miniu.alipay.com/keytool/create

    秘钥相关:https://opendocs.alipay.com/mini/miniu/keytool/create

    在沙箱中配置对应的公钥保存即可

  4. 目前沙箱钱包仅提供Android版本,可点击下载

img

注意:使用沙箱账号登录

备注:

  • 应用公钥(public key)需提供给支付宝账号管理者上传到支付宝开放平台。
  • 应用私钥(private key)由开发者自己保存,需填写到代码中供签名时使用。
  • 生成的私钥需妥善保管,避免遗失,不要泄露。
  • 密钥和应用(APPID)一一对应,即开发者需要为名下的每个应用分别设置密钥,且不同应用的密钥不能混用。

餐掌柜主要使用 面对面 支付接入方法,详细参见:https://docs.open.alipay.com/203/

1.2.2、公钥私钥概述

大家可以登录支付宝开发者中心进行账号及应用的创建,其入住方式配置这里就不做赘述,大家可以访问网址:https://opendocs.alipay.com/open/0128wr,进行学习:

image-20210606151012174

再接入支付宝中我们需要理解公钥及私钥的使用

对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。

对于对称加密,封装了JDK的,具体介绍见:https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyGenerator:

  • AES (默认AES/ECB/PKCS5Padding)
  • ARCFOUR
  • Blowfish
  • DES (默认DES/ECB/PKCS5Padding)
  • DESede
  • RC2
  • PBEWithMD5AndDES
  • PBEWithSHA1AndDESede
  • PBEWithSHA1AndRC2_40

57327523975392075

非对称加密,最常用的就是RSA和DSA,在Hutool中使用AsymmetricCrypto对象来负责加密解密。

非对称加密有公钥和私钥两个概念,私钥自己拥有,不能给别人,公钥公开。根据应用的不同,我们可以选择使用不同的密钥加密:

  1. 签名:使用私钥加密,公钥解密。用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。
  2. 加密:用公钥加密,私钥解密。用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得。

Hutool封装了JDK的,详细见https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator:

  • RSA
  • RSA_ECB_PKCS1(RSA/ECB/PKCS1Padding)
  • RSA_None(RSA/None/NoPadding)
  • ECIES(需要Bouncy Castle库)

应用请求三方中私钥公钥的使用:

image-20210819150017557

三方返回信息给应用中私钥公钥的使用:

image-20210819150227601

小结:

  • 公钥加密,私钥解密

  • 应用服务器【餐掌柜】需要保存秘钥:

    • 应用服务私钥(工具生成)
    • 支付宝公钥(三方提供)
  • 三方服务器【支付宝/微信】需要保存秘钥:

    • 应用服务公钥(上传)
    • 支付宝私钥(三方保存,不对外提供)

使用支付宝开发助手创建密钥,选择RSA2的加密方式,如图所示

image-20210606151123604

生成后点击上传公钥,则会跳转对应的支付密钥上传页面

image-20210606163132138

image-20210606162551024

image-20210606162855473

1.2.3、快速入门(沙箱版本)

接口定义:外部商户请求支付宝创建订单并支付

参数参考文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay

官方SDK与DEMO代码:https://opendocs.alipay.com/open/203/105285/

代码实现:

(1)pom依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.9</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

(2)必要的参数

如下参数是支付宝接口交互中的必要参数:appId、应用私钥、支付宝公钥,以下参数请学习者自行申请。

AlipayConfig配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.itheima.alipay.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "restkeeper.alipay")
@Data
public class AlipayConfig {
// 商户appid
public String APPID = "";
// 私钥 pkcs8格式的
public String RSA_PRIVATE_KEY = "";
// 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public String notify_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址
public String return_url = "http://商户网关地址/alipay.trade.wap.pay-JAVA-UTF-8/return_url.jsp";
// 请求网关地址
public String URL = "https://openapi.alipay.com/gateway.do";
// 编码
public String CHARSET = "UTF-8";
// 返回格式
public String FORMAT = "json";
// 支付宝公钥
public String ALIPAY_PUBLIC_KEY = "";
// 日志记录目录
public String log_path = "/log";
// RSA2
public String SIGNTYPE = "RSA2";
}

application.yml配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
restkeeper:
alipay:
APPID: 2016100200645088 #商户appid
URL: https://openapi.alipaydev.com/gateway.do #请求网关地址 沙箱环境
CHARSET: UTF-8 #编码
notify_url: #服务器异步通知页面路径
return_url: #页面跳转同步通知页面路径
FORMAT: json #返回格式
ALIPAY_PUBLIC_KEY: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl1NG6w+hLEUwQvXViCSZJFoFY6vdkpK9/PRo7bBSo2JV1TOqHeAGBF0cGlH/GA16sPpXgE+hYIRv0fQLMmQxE54GHGhHCp2qXvqNdcOeUuazGkV70FBIXl0CUdgVkpuVhNCTa4llooCJsFGftD/BtcyW65TcY4GAU5Km+ZoLkgRQIV+cEBWgo4yHdQpJPYDZmMmG5rbY7YsL/gSPsAL1NwjdOH4lBv+IpTWGUJJ5KtLcN7d9F5a2KdEZTuN1JqeROgJp32q2hbpUeat4X/1S1xgpmZ1BJ7LgSALcn9bLmbXJLGc6wZ1sB4kp/TCgrkANJpzOPp7XMGjBXiZ4v6Yl9QIDAQAB #支付宝公钥
RSA_PRIVATE_KEY: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCnYRYD+omT9FbR+lfHXVYkGK4MI66NFLaMqm7lexPec0reVbx5bRTgs+l/u7J3Jf5I4d/3v27a51voPqQE2PDM8/r16RRmF946na9l9hYcbRfZHdTFRmoJ+Epd3SahuDRvBm84c3sUdcbQXFFytyYgDdszYukOank8glKHzpwgMVznNEej/q6+aw9Fut2EJONpw6gBtpA5WowsxFcg+cCgLPG1gesruD30/WnOk2zXA4K8OknFh21g7Fd4bU0fNSBWjBD4p4iDlsEQ7krpGrK9/CI/xezREvk5745Y9pDMFRQRYpEAHiFKx+pYgvgfPWWi4me3Na4Y64mt6vbkdWxbAgMBAAECggEAZslyMaNLlXZ5Up2ABkhFPAmD6KSI7s6HhD6tt3MrsnHuyjawdYkNRyh0/iIP6KeGTs+XMJd4xilKAYdmRivLRLGXrigihMeniyuGqQDEd1RvTr/JCBTDzbeSQ64pqSpr2LqE1o/kR55EJ3Rp+B6M5SZdNGNLZ7TvGr+VWx0AN6vXrQcX+W0Fmju6725RiSZqihAVNTpHbWNUZbqZIV/W/8pmwNW/2or59PCIyWeaBK8+4hIH1Hx5viAXYLSK4brOlP7w6uzzy+d49hNVrII8guPQ+9WF/CNg4og7loifVH5Dyd665uyny/0dBZf5IPVl0UF8OlY/wAJ6Y/10ue/yEQKBgQDwGBN260uqKOs5nIIPiILiIbKmF79iSstJwh3LJJGtA62NDCyA95LRddIv+FTkZ6F7kagSuOs4TSFtAIWm8tj0MufdGzsSmYabXE1DDNsUuNbXDyd1Ii6KtYN7oMHxzm0PPi6jGefgqS5yq8bG+28APVHQNYnwyuvS0w27J/azrwKBgQCyd8nUjCQvJJxWLnyxy352Xpr+CcXxKYHnKBwDm94elG+TnA/IxoE4iqdKT9yhRCI0pqQDbwyaRm0iXNviVS+oMLwyRotesQ+/ViFBQw26j4Fmaq2LQFti6mSHQjw+BLtxdIy1pVVxDxPBXg6zYRvq5Q3T/+fmbrfB6DxZcgsBFQKBgQC0N848gEfudQKD3xe9YyGjbdn0VHUC6dOIDN5iQpPag50893tcXvlkooTgHw5R1/vdjirTytw9CaBienbYJwd03dUvIaaIwpbIfVM9ViQIfOo+yZA7mynGUpNcNAIAaItyWqGVKffkqflEd+4gJFFgo6aKm/VrulWjjWqMJmZG3wKBgGaVko85SudKTQ8Aw65TQUr7EG5r4brA2CmuFYRBiQjc29HmR/BpogeFM6n0g+ayylKnYumSYJUhXEP/Smkr/Cvab6Mah6wTbPDXql/gEjklmgTr1vuPL7iI8OYKvaQMhk4t51/WPGmzd/CThzG25Rw9M5ijpYIALGIqgt4LPqYNAoGAFk8hYTT5PU1tNmCi/VVcsNNc4GlDytpgitP0nbBrp/XhjIBEjF1kxW/BrshIuQLzZ74jnM3P0Q6i9vtuqMHv5/Lcx9PSprCxCNSAn7W1VNmey2reqBtTFRo485AtsSntjGiaoORzpBKRZHBIMLSS8zEY+BSBwecaktF6IkmVuuI= #私钥 pkcs8格式的
log_path: #日志路径
SIGNTYPE: RSA2 #签名

(3)编写Controller实现

注意:此处使用的是通用版,开发时使用简易版本,具体看官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.itheima.alipay.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.itheima.alipay.config.AlipayConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Description:
* @Version: V1.0
*/
@RestController
public class PayController {

@Autowired
AlipayConfig alipayConfig;

@GetMapping("/alipaytest")
public void alipaytest(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.getURL(),
alipayConfig.getAPPID(), alipayConfig.getRSA_PRIVATE_KEY(),
alipayConfig.getFORMAT(), alipayConfig.getCHARSET(),
alipayConfig.getALIPAY_PUBLIC_KEY(), alipayConfig.getSIGNTYPE());
//获得初始化的AlipayClient
AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
//创建API对应的 request
// 注意订单的唯一性
alipayRequest.setBizContent("{" +
"\"out_trade_no\":\"20150321210101123\","
+ " \"total_amount\":\"0.01\","
+ " \"subject\":\"Iphone6 16G\","
+ " \"product_code\":\"QUICK_WAP_PAY\""
+ " }");
//填充业务参数
String form = "";
try {
form = alipayClient.pageExecute(alipayRequest).getBody();
//调用SDK生成表单
} catch (AlipayApiException e) {
e.printStackTrace();
}
httpResponse.setContentType("text/html;charset=" + alipayConfig.getCHARSET());
httpResponse.getWriter().write(form);
//直接将完整的表单html输出到页面
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}

}

(4)启动项目测试,使用 ==支付宝沙箱版== 付款

需要和支付宝建立连接,所以我们需要内网穿透工具 将当前请求的地址变成公网可以访问的地址测试。

  1. 准备内网穿透工具 小米球Natapp,注册配置

  2. 启动内网穿透服务

  3. 将 请求地址修改为: http://itheima.ngrok2.xiaomiqiu.cn/alipaytest

  4. 将上述地址 生成对应的二维码

  5. 支付宝扫描二维码完成支付

    image-20210721103052743

在沙箱环境完成功能调试后,必须将支付宝网关、APPID、应用私钥、支付宝公钥修改成正式环境的配置,并在蚂蚁正式环境进行完整的功能验收测试。

1.2.4、快速入门(上线版本)

当前使用SDK(easy-sdk

(1)引入依赖

1
2
3
4
5
6
<!--方式二 https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.0</version>
</dependency>

(2)配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
restkeeper:
easyalipay:
protocol: https
gatewayHost: openapi.alipay.com
signType: RSA2
appId: 2021002173680104
merchantPrivateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCYTgKj2xEIJi7lqjGRuQ/XWNDRuhoMtWvQg1DzF3fDWodh6WMed5V7mi3s4zXLnm4TvHVazgLnVXJlONxG4EsVJRGp3hY1fSGAAcByRC2E8E+NdhIvaOAWUuEid6AeshfNqmePnJyHxpxf9ZYn0Bij0kM8yMwv1izkBmsuydXroQegYfias6HT+CQ9mgU0awb6ZPeGqC6sw9Tv991jOohGv4xgEVjRh69pvB8Eo1vubYJ8mhOEf5xwWkx8/n7tDa4uA7ioUlgLRxhtOkCkpTQXzLAzm8gxoJlEYL/sFR3plaPBRAafWoKdfF55W0SBXDhhNqqce+r1H4pw4IyZxqyrAgMBAAECggEAYAcnoPpZlcLFZObXFCMTutpz5xgonoSwsqppGqxcRZ7Jp1FIvof1hxYiCK8FVxnQG7+CWrtzlzoHw4yDTmjSzkUuCuVNKXJ48cWo+iLEdII0FmQweRXt3AVrj5jPKyts2K6tVx4Oj4kJRXOJthZ9wqSq4iNUooCukyL852Y467P/Me+9Vr2vQb117qLaNTwvR2GV6OZUNQPl7qKNsp86e5lREIBHPk8uhQO3KS+QPPvTDkKBH+bNo7Bu4L4J0ITdvjU9FupaQ6xfFCOrgu7P2bw9Mk2JM6jHp1hEjuoLVGA97sL6CSPhQu9s9KJbup2DGOI1qQoEATBypHFueYv4yQKBgQD57xI1iPUl3tVNH5e3yYa08NLS+mV9N3tRTjIWHbMwQKMbraaO+dGQRGiytjejao3y0EVFuqOuhbXAxtvb1pUukASqY3zW01zzVNNw8nO3zeVPo+xWLAvlnyX3MekKMYjo1dAyWBzXuPgo3D413nMiPZ8oOgtbpTyu/dTeN3NJTQKBgQCcAFWSUinmZ3x9Xr779CeX1MsKG+++C4iLP7vNP8Lf8IcPE8NnHYZTQqDuvq1Itai7UbhZX95itYqjp9SlXT4hSrMGI7qwobJ0vXxXrNN6VwZtz1N75vnIrZNHnTFMWTUBKCySLcpsqs7qCSQEv6luOQUSUH/0gaN6txOq5W4R1wKBgH4GdLIV6zc7U2beJUyBC7G1NTk5FW+8SCxJN6w7MZ2FGjncp/20Ll2GgRyMESYPlp/3MNbmM57OwUUBgN8rJnIiIJgiLlLMpTP1c+CiAIOQCK7Nw1/4Oc+BHk21FwMS0yxElASutWx5UniYBa54CqobVGOeURfXC/BZAbtDTpiJAoGAMR3B03HfE1Xd0jM0emti0+EBlEs7bmB/Oyhz3qmGl69JNqwIR7z5/9johoKuWEgpueB+5FTU1ctGvUQoJXB4EU9Nkk9JhjdC0pKeRZR6ePhRY9108XvFhTNxPYj2bo1frN+TOOsF4rTctL7wAja+B6AYQq3pu3fdmtNtc88Mmr0CgYEAuDD/PzVTLMYgEqq0ruZcCyFd0HwMV8KoC07aXXeLZT/IGtoJASFkWIxcoyK8NVZvDqoGkpaLAw3G7CmzoxpqSvyMWwHGdZy7KDV55g5O9r4Lt/dti7xt6gBYm8XB1X7cLcu0x9lYs6KL01To+Ep8DxSO7LUODQ0Utu3Pkf9W1qc=
alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhUnjdAKwZApwZEcfq+5L0pa77Vg3mqcoXv+th8RR0SYotkPsH1f2JkbS48ySaSCM6YNWSMNfqp5qdOla2zUJOBnJ/yaBg7s7fVD6V3M2mEog8kCDYGKt/3P4VII3xYl8lFYMQ3IcFRELkxCBBCA8JDKmf5z2R4F/Z/jFFEuOwxaJvp+7Ke9OzZHYdWGNnU6QP8YYLYUeX7VNZLHEuly34ExAw6A+yJkNDsYEho2Lu31QjT2pLh9g+88MlRfiI92iN25O9NVdeM4f5RcpvBPrBQZQs9tlFmALYSFS3prIf3FAobWM+W7iwxT6J25nFIhst1DdJQfIBpaeRUJVTkn99QIDAQAB
encryptKey: 3GHtw6uVpqjtQGeJc+eEqQ==
notifyUrl:
# 沙箱环境
# easyalipay:
# protocol: https
# gatewayHost: openapi.alipaydev.com
# signType: RSA2
# appId: 2016100200645088
# merchantPrivateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCnYRYD+omT9FbR+lfHXVYkGK4MI66NFLaMqm7lexPec0reVbx5bRTgs+l/u7J3Jf5I4d/3v27a51voPqQE2PDM8/r16RRmF946na9l9hYcbRfZHdTFRmoJ+Epd3SahuDRvBm84c3sUdcbQXFFytyYgDdszYukOank8glKHzpwgMVznNEej/q6+aw9Fut2EJONpw6gBtpA5WowsxFcg+cCgLPG1gesruD30/WnOk2zXA4K8OknFh21g7Fd4bU0fNSBWjBD4p4iDlsEQ7krpGrK9/CI/xezREvk5745Y9pDMFRQRYpEAHiFKx+pYgvgfPWWi4me3Na4Y64mt6vbkdWxbAgMBAAECggEAZslyMaNLlXZ5Up2ABkhFPAmD6KSI7s6HhD6tt3MrsnHuyjawdYkNRyh0/iIP6KeGTs+XMJd4xilKAYdmRivLRLGXrigihMeniyuGqQDEd1RvTr/JCBTDzbeSQ64pqSpr2LqE1o/kR55EJ3Rp+B6M5SZdNGNLZ7TvGr+VWx0AN6vXrQcX+W0Fmju6725RiSZqihAVNTpHbWNUZbqZIV/W/8pmwNW/2or59PCIyWeaBK8+4hIH1Hx5viAXYLSK4brOlP7w6uzzy+d49hNVrII8guPQ+9WF/CNg4og7loifVH5Dyd665uyny/0dBZf5IPVl0UF8OlY/wAJ6Y/10ue/yEQKBgQDwGBN260uqKOs5nIIPiILiIbKmF79iSstJwh3LJJGtA62NDCyA95LRddIv+FTkZ6F7kagSuOs4TSFtAIWm8tj0MufdGzsSmYabXE1DDNsUuNbXDyd1Ii6KtYN7oMHxzm0PPi6jGefgqS5yq8bG+28APVHQNYnwyuvS0w27J/azrwKBgQCyd8nUjCQvJJxWLnyxy352Xpr+CcXxKYHnKBwDm94elG+TnA/IxoE4iqdKT9yhRCI0pqQDbwyaRm0iXNviVS+oMLwyRotesQ+/ViFBQw26j4Fmaq2LQFti6mSHQjw+BLtxdIy1pVVxDxPBXg6zYRvq5Q3T/+fmbrfB6DxZcgsBFQKBgQC0N848gEfudQKD3xe9YyGjbdn0VHUC6dOIDN5iQpPag50893tcXvlkooTgHw5R1/vdjirTytw9CaBienbYJwd03dUvIaaIwpbIfVM9ViQIfOo+yZA7mynGUpNcNAIAaItyWqGVKffkqflEd+4gJFFgo6aKm/VrulWjjWqMJmZG3wKBgGaVko85SudKTQ8Aw65TQUr7EG5r4brA2CmuFYRBiQjc29HmR/BpogeFM6n0g+ayylKnYumSYJUhXEP/Smkr/Cvab6Mah6wTbPDXql/gEjklmgTr1vuPL7iI8OYKvaQMhk4t51/WPGmzd/CThzG25Rw9M5ijpYIALGIqgt4LPqYNAoGAFk8hYTT5PU1tNmCi/VVcsNNc4GlDytpgitP0nbBrp/XhjIBEjF1kxW/BrshIuQLzZ74jnM3P0Q6i9vtuqMHv5/Lcx9PSprCxCNSAn7W1VNmey2reqBtTFRo485AtsSntjGiaoORzpBKRZHBIMLSS8zEY+BSBwecaktF6IkmVuuI= #私钥 pkcs8格式的
# alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl1NG6w+hLEUwQvXViCSZJFoFY6vdkpK9/PRo7bBSo2JV1TOqHeAGBF0cGlH/GA16sPpXgE+hYIRv0fQLMmQxE54GHGhHCp2qXvqNdcOeUuazGkV70FBIXl0CUdgVkpuVhNCTa4llooCJsFGftD/BtcyW65TcY4GAU5Km+ZoLkgRQIV+cEBWgo4yHdQpJPYDZmMmG5rbY7YsL/gSPsAL1NwjdOH4lBv+IpTWGUJJ5KtLcN7d9F5a2KdEZTuN1JqeROgJp32q2hbpUeat4X/1S1xgpmZ1BJ7LgSALcn9bLmbXJLGc6wZ1sB4kp/TCgrkANJpzOPp7XMGjBXiZ4v6Yl9QIDAQAB #支付宝公钥
# encryptKey: GEyK+oXaeYiUy3Y4Qb25JQ==
# notifyUrl:

(3)属性配置类AliPayProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.itheima.alipay.prop;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
* @Description:
* @Version: V1.0
*/
@Configuration
@Data
@ConfigurationProperties(prefix = "restkeeper.easyalipay")
public class AliPayProperties {


//请求协议
private String protocol;
// 请求网关
private String gatewayHost;
// 签名类型 RSA2
private String signType;
// 应用ID
private String appId;
// 应用私钥
private String merchantPrivateKey;
// 支付宝公钥
private String alipayPublicKey;
// 异步通知接收服务地址
private String notifyUrl;
// 设置AES密钥
private String encryptKey;
}

(4)配置类MyAlipayConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.itheima.alipay.config;

import com.alipay.easysdk.kernel.Config;
import com.itheima.alipay.prop.AliPayProperties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @Description:
* @Version: V1.0
*/
@Configuration
@Data
public class MyAlipayConfig {


@Bean
public Config config(AliPayProperties payProperties) {
Config config = new Config();
config.protocol = payProperties.getProtocol();
config.gatewayHost = payProperties.getGatewayHost();
config.signType = payProperties.getSignType();

config.appId = payProperties.getAppId();

// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
config.merchantPrivateKey = payProperties.getMerchantPrivateKey();

//注:证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径,优先从文件系统中加载,加载失败后会继续尝试从CLASS_PATH中加载
// config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->";
// config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->";
// config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt -->";

//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
config.alipayPublicKey = payProperties.getAlipayPublicKey();

//可设置异步通知接收服务地址(可选)
config.notifyUrl = "";

//可设置AES密钥,调用AES加解密相关接口时需要(可选) <-- 请填写您的AES密钥,例如:aa4BtZ4tspm2wnXLb1ThQA== -->
config.encryptKey = "";

return config;
}
}

(5)编写测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.itheima.alipay.controller;

import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeCreateResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
import com.alipay.easysdk.payment.facetoface.models.AlipayTradePrecreateResponse;
import com.alipay.easysdk.payment.wap.models.AlipayTradeWapPayResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
* @Description:
* @Version: V1.0
*/
@RestController
@RequestMapping("easy")
public class EasyPayController {

@Autowired
Config config;

// 下单支付
@GetMapping("pay/{code}")
public String pay(@PathVariable String code) {
// 1.初始支付宝配置
Factory.setOptions(config);
try {
// 2. 发起API调用(以创建当面付收款二维码为例) 2234567890
AlipayTradePrecreateResponse response = Factory.Payment.FaceToFace()
.preCreate("Apple iPhone11 128G", code, "0.01");

// 手机支付模式
// AlipayTradeWapPayResponse response = Factory.Payment.Wap()
// .pay("华为手机 128G", code, "0.01",
// "", "");

// 3. 处理响应或异常
if (ResponseChecker.success(response)) {

System.out.println("调用成功");
// String body = response.getBody();
String body = response.getHttpBody();
System.out.println(body);

// return response.getBody();
return response.getHttpBody();
} else {
System.err.println("调用失败,原因:" + response.toString());
}
} catch (Exception e) {
System.err.println("调用遭遇异常,原因:" + e.getMessage());
throw new RuntimeException(e.getMessage(), e);
}

return "ERROR";
}


// 查询支付状态
@GetMapping("querypay/{code}")
public String querypay(@PathVariable String code) {
Factory.setOptions(config);

try {
AlipayTradeQueryResponse response = Factory.Payment.Common().query(code);

Map<String, Map<String, String>> map = JSON.parseObject(response.getHttpBody(), Map.class);
if (map.get("alipay_trade_query_response").get("trade_status").equals("TRADE_SUCCESS")) {
System.out.println("支付成功......");
System.out.println("处理订单后续操作.....");
}

return response.getHttpBody();

} catch (Exception e) {
System.err.println("调用遭遇异常,原因:" + e.getMessage());
throw new RuntimeException(e.getMessage(), e);
}

}

}

1.3、微信支付

1.3.1、准备环境

详细参考:讲义/微信支付接入指南.pdf

餐掌柜C扫B需求要求可以手机网页交互,微信JSAPI/NATIVE支付符合需求。

总结步骤:

  1. 以企业身份注册微信公众号 https://mp.weixin.qq.com/
  2. 登录公众号,点击左侧菜单 “微信支付” 开通微信支付,需要提供营业执照、身份证等信息。
  3. 开通微信支付后即可在微信商户平台(pay.weixin.qq.com)开通JSAPI/NATIVE支付
  4. 获取openid,openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户,同时也是微信JSAPI支付的必传参数。
  5. 调用统一下单API,调取支付接口即可

1.3.2、快速入门

参考文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html

V3新版本:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml

JSAPI接口定义:

  1. 用户同意授权,获取code授权码
  2. 通过code换取网页授权access_token和openid
  3. 调用JSAPI/NATIVE 统一下单 接口

官方SDK与DEMO代码:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

NATIVE代码实现:

(1)添加依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.tedzhdz</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.10</version>
</dependency>

(2)编写配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.itheima.wxpay.config;

import com.github.wxpay.sdk.IWXPayDomain;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayConstants;

import java.io.InputStream;

public class WXPayConfigCustom extends WXPayConfig {

@Override
protected String getAppID() {
return "wx8397f8696b538317";
}

@Override
protected String getMchID() {
return "1473426802";
}

@Override
protected String getKey() {
return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb";
}

@Override
protected InputStream getCertStream() {
return null;
}

@Override
protected IWXPayDomain getWXPayDomain() {
return new IWXPayDomain() {
@Override
public void report(String s, long l, Exception e) {
}

@Override
public DomainInfo getDomain(WXPayConfig wxPayConfig) {
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
}
}

(3)编写下单测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.itheima.wxpay.controller.v3;

import cn.hutool.http.HttpUtil;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import com.itheima.wxpay.config.WXPayConfigCustom;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
* @Description:
* @Version: V1.0
*/
@Slf4j
@RestController
@RequestMapping("wxpay")
public class WxpayController {


@RequestMapping("notify")
public String payNotify(HttpServletRequest request, HttpServletResponse response) {
try {
String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
Map<String, String> map = WXPayUtil.xmlToMap(xmlResult);

// 加入自己处理订单的业务逻辑,需要判断订单是否已经支付过,否则可能会重复调用
String orderId = map.get("out_trade_no");
String tradeNo = map.get("transaction_id");
String totalFee = map.get("total_fee");
String returnCode = map.get("return_code");
String resultCode = map.get("result_code");

return WxPayNotifyResponse.success("处理成功!");
} catch (Exception e) {
log.error("微信回调结果异常,异常原因{}", e.getMessage());
return WxPayNotifyResponse.fail(e.getMessage());
}
}

@GetMapping("unifiedOrder/{code}")
public String unifiedOrder(@PathVariable String code) throws Exception {
WXPayConfigCustom config = new WXPayConfigCustom();
WXPay wxpay = new WXPay(config);

Map<String, String> data = new HashMap<String, String>();
data.put("body", "餐掌柜-餐饮消费");
// data.put("out_trade_no", "2138091910595900001012");
data.put("out_trade_no", code);
data.put("device_info", "");
data.put("fee_type", "CNY");
data.put("total_fee", "1");
data.put("spbill_create_ip", "123.12.12.123");
data.put("notify_url", "http://itheima.ngrok2.xiaomiqiu.cn/wxpay/notify");
data.put("trade_type", "NATIVE"); // NATIVE 指定为扫码支付 JSAPI 网站支付
// data.put("openid", "12");

try {
Map<String, String> resp = wxpay.unifiedOrder(data);
System.out.println(resp);
return resp.get("code_url");
} catch (Exception e) {
e.printStackTrace();
}

return "OK";
}

}