作者:我不是喷子X
恰逢五一闲闲无事,所以想着记录一下最近的所学,想了一想,这个微信支付是一个比较适合的项目,虽然它只是一个简单的API调用,但是因为涉及到了金钱,所以整个调用的流程和安全的保障都是比较重要的,特别是其中的加密解密思想,适合初学者进阶学习,在做毕业设计的时候也用得上,还是比较值得学习的,我这边也会分成几个部分来讲解,只要跟着思路一直走,百分百能学会。
那,为什么不选支付宝?
– 支付宝SDK和Demo做得比较好,容易看也容易上手,不需要教
– 微信这边无论是文档还是SDK,做的都让人摸不着头脑,相对的难度也会更高,搞懂了微信,支付宝那边自然就懂
PS:我是渣渣一个,大佬们如果发现有哪里讲的不对的,劳请指出
【开发环境】
开发语言选择了Java,框架是SpringBoot2,测试工具是PostMan。
微信支付版本为V3,SDK版本是0.4.4
操作系统为Windows10
开发软件是IDEA
【微信支付】
第一步,也是所有API调用都需要的一步,注册,这个相信大家都会吧?我就不赘述了,直入正题。
1、准备材料
这些是接入微信支付所需的材料,大家根据自己的情况去申请,微信这边没有提供沙箱环境,所以没办法,大家只能去准备这些材料了。
注册完成、提交好材料,审核通过之后,我们就可以等登录自己的账号,并且获取到对应的私人数据了,这些数据如何生成微信那边都有指引,跟着一步步来就可以。
需要注意的是:
–APPID,它是需要申请一个公众号(且类型一定要是服务号),才可以
–APIV3秘钥:接入V3版本必须要使用到的,V2你申不申请都可以。
–微信平台证书:这个证书是用来解密微信响应内容的,可以预先下载到本地也可以每次都通过网络去获取,因为它有一个过期的期限,所以建议是每次都通过网络获取最新的,本教程也是直接网络获取的。
2、如何保证安全性?
如何保证物品的安全性?
传统思维:先买个锁锁上,需要用到的时候再拿钥匙来打开。
只是现在这个物品,编程了在网络上传输的01010101,在流程上其实和传统思维差不多,只是其中的过程会有所不同。
假设我们正常的内容是(明文):我的ID——我不是喷子X
我们现在使用密钥对这个内容进行一个加密操作,这个加密操作的方式很多,算法也很多,那么我们把上的内容加密之后会得到一大串asd8979qa7wd979wd9a9dwqw8e9hqweh8,总之不是人看的内容,这个就是——密文。
然后我们将这个密文进行网络传输,传输到目的地之后,再使用密钥配合解密算法,把这一大串asd8979qa7wd979wd9a9dwqw8e9hqweh8变回正常的内容:我的ID——我不是喷子X
大体的思路就是这么一回事,而这其中还会有一些细节,我们徐徐道来。
1)加密
加密的方式主要有两种:对称加密、非对称加密
——对称加密:加密和解密使用的都是同一个密钥
主流的算法是AES加密算法,密钥长度是128、192甚至是256,安全强度相对较高的,而且性能也好。
但缺点也很明显,因为用的都是同一个密钥,所有信息交换的双方都需要持有,如果被窃取了的话,信息就会被随意破解,就不安全了。可以理解成你家人各有一把钥匙,小偷只要偷了其中一把,就可以开门进来。
——非对称加密:把钥匙分成公钥和私钥,公钥可以公开,私钥必须自己保存好,谁也不能给。
采用了非对称加密的话,这一个公钥加密后的内容,只能由对应的那一个私钥才能解密,反之,私钥加密后的内容,也必须使用对应的公钥来解密,非对称加密的安全性非常高,但是缺点是速度慢,性能相对差。
可以把公钥理解成小区大门的门禁密码,私钥就是你家的家门钥匙,你可以告诉你朋友公钥,也就是你们小区的门禁密码,但是你不会直接把私钥也就是你家门要是给朋友。如果你的朋友要来你家,那么它就必须要去到你那个小区,输入你提供的门禁密码,才能进来,去了其他小区输入这个门禁密码是进不去的。
我既想要安全又希望保证速度,那怎么办?
两个一起用呗。
我们用非对称加密,把需要传输的那个对称加密的密钥加密后传递,这样就保证了密钥的安全性和传输的速度了,可能有点绕,大家可以用笔慢慢的画一下这个思路,就可以理解了。
2)数字签名
传输的安全性保证了,那如何保证内容的完整和正确呢?
主要是依靠摘要算法来实现的,也就是常说的散列函数、哈希函数。一个字符串,经过了Hash算法之后会得到一个固定长度的字符串,比如asd经过Hash后得到asdboaibwodibqwiob,oiu经过Hash后得到qwioboadbnsinxqwoieh,这个经过Hash后得到的字符串就是我们的摘要。
摘要的算法需要具备四个特性:
–不可逆:只有算法,只能加密,不能解密
–难题友好性:想要破解只能通过暴力枚举,一次次不断的尝试直到破解成功
–发散性:还要原文发生了一点点改动,哪怕只是把其中一个a改成了b,计算后的摘要都会发生剧烈的变化。
–碰撞测试:原文不同,计算后的摘要也必须不同,不能说abc和cba计算出来的摘要是一样的。
用摘要来保证数据完整性的思路是这样的:
小红现在要发一封信给小明,小红先通过Hash算法计算出摘要,然后使用私钥将这个摘要加密后,得到一个数字签名,然后将这个数字签名放到信封中一起寄出去。
小明在收到信之后呢,先拿小红给的公钥,把这个数字签名,解密成摘要,然后拿原文出来,使用Hash算法把原文计算,得到一个摘要,然后我们比对一下小明计算出来的摘要和小红发过来的那个摘要是不是一样的,如果是一样的话,就说明内容没有被修改过,那么这个过程也被称为——验签
3)数字证书
好,现在我们保证了公钥私钥传输的安全性和传输内容的完整性之后,又出现了一个新的问题,如果黑客把自己的公钥给到用户,然后欺骗这个用户说这把钥匙就是微信提供的,那么黑客就可以用自己的私钥模拟出数据然后发送给到用户,用户用黑客给的那边骗他说是微信提供的公钥解密之后发现成功了,就会误以为真的是微信那边发送过来的消息,这种情况肯定是不允许的,数字证书就是用来解决这个问题的。
数字证书的主要信息:
– 公钥:例如 小红的公钥
– 所有者:小红
– 颁发者:CA(Certificate authority,证书认证机构)
– 有效期:证书的使用期限
– 签名哈希算法:指定生成摘要的算法,用来计算证书的摘要
– 指纹:证书的摘要,用来保证证书的内容没有被修改
– 签名:用户生成的签名,用来确保证书是由CA签发的
– 序列号:证书的唯一标识
颁发证书的流程:
CA使用证书中指定的Hash算法计算出摘要,然后用自己的私钥把摘要进行加密,生成一个数字签名,然后把这个签名附加在数字证书中进行返回。
有了证书之后,我发送信息的同时把CA证书也一起发送过去,用户在收到我的信息之后,先去CA那边验证这个证书的真实性到底是不是我发送的,验证完成之后再进行一个解密读取信息的内容。
那么,我们在上面通过微信的证书工具申请下来的证书,打开会得到三个文件。
第一个是一个程序版的,直接双击就可以打开。(本教程不使用)
第二个是我们的公钥,是文本版的,用编辑器打开就可以看到一串公钥。
第三个是私钥,也是文本版的,我们在程序中读入公钥和私钥然后使用。
【创建项目】
本教程使用的是SpringBoot2框架
1、首先导入你需要的pom依赖
这个步骤你们自己把握,就是基础的那些log4j、gson、webstart这些,总之你之前怎么跑起来的就怎么搭建就可以,看你自己项目需求。
2、创建配置文件(选做)
application.ym,里面配置一些启动端口号、服务名之类的,根据你自己的需求配置,不配置也不会报错。
弄完之后,启动这个项目,能访问就算成功。
【接入前准备】
1、首先,完善你的配置文件
具体字段对应的内容是什么,我都用中文标注了,当成填空题做就可以,这些数据你登录微信支付平台,都能够拿到。
——需要说明的是,商户私钥文件那里,是我们从微信那边下载下来的那个证书工具,解压后里面就有私钥文件,把这个文件放到项目里面来,到时候我们直接用程序读取里面的内容,切记!!!!私钥不要泄露给任何人!!!
——微信服务器地址,这个没什么好说的,你调用微信的API,肯定要去微信的网址调用,我不想每次都写一大串,所以我写在配置文件里面,到时候读取的时候通过前面那个domain就可以读取到这一段内容了(就是懒)
——接收结果通知地址,这里是微信给我们响应的地址,用户支付成功的异步回调中,微信是通过这个地址来找到我们并且告知我们支付的结果的,必须要外网能够访问,所以要用到内网穿透,这个我会在后面详细讲,可以先忽略这个。
2、创建一个配置类,读取配置文件中的内容
@Configuration
@PropertySource(“classpath:wxpay.properties”) // 读取这个名字的配置文件 @ConfigurationProperties(prefix = “wxpay”) // 通过这个前缀去匹配文件中的内容
@Data public class WxPayConfig {
private String mchId;
private String mchSerialNo;
private String privateKeyPath;
private String apiV3Key;
private String appid;
private String domain;
private String notifyDomain;
}
这个配置文件放在哪里都可以,看你自己想法,当然,规范的话肯定是新建一个config包,然后放在这个包下
3、把私钥文件放入项目中
找到我们生成好的那个私钥文件apiclient_key.pem,将其放到项目的根目录中
4、引入开发库SDK
通过Maven的方式引入,这个内容去微信支付的官方git上面就可以找到,这个版本是目前最新的,简化了验签和解密的一个步骤,以前要自己做的一些操作现在官方都帮我们封装并且自动做了,这个版本还是比较推荐使用的。
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
0.4.4
5、使用微信提供的工具加载私钥
在我们刚才新建好的配置文件中,加入以下方法代码,这个方法的作用就是,通过我们传进来的filename,去找到这个名字的文件,然后处理里面的私钥。这个filename就是我们上面写的那个privateKeyPath。
/* 获取商户的私钥文件 */
private PrivateKey getPrivateKey(String filename){
try {
returnPemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException(“私钥不存在”,e);
}
}
6、获取验签器和HttpClient
还是在刚才的配置文件中,添加以下代码,也是微信官方Git上提供好的,我做了一些小修改。
那些需要我们提供的内容,我也做了加粗,其就是我们的商户ID这些东西,配置文件里面写了。
这个方法的作用是先通过我们的私钥,去拿到微信的平台证书,然后在这个证书里面,拿到我们需要的验签器
@Bean // 这里添加Bean注解可以让系统启动的时候就加载,而且只加载一次,这样就不需要每次请求都重新调用了
public Verifier getVerifier() throws NotFoundException, GeneralSecurityException, IOException, HttpCodeException {
PrivateKey privateKey = getPrivateKey(privateKeyPath);
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo,privateKey); WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId,privateKeySigner);
// 获取证书管理器实例 CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息 certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier Verifier verifier = certificatesManager.getVerifier(mchId);
return verifier;
}
新版主要的不同就是这里,微信官方的SDK帮我们自动处理了验签之类的流程
// 获取http请求对象
@Bean public CloseableHttpClient getWxPayClient(Verifier verifier){
PrivateKey privateKey = getPrivateKey(privateKeyPath);
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,new PrivateKeySigner(mchSerialNo, privateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
Verifier verifier = certificatesManager.getVerifier(mchId);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId,mchSerialNo,privateKey).withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新 CloseableHttpClient httpClient = builder.build();
return httpClient;
}
7、记得添加gson依赖
V3版本已经全部使用Json进行数据交互了,所以我们需要引入一下gson来处理json数据
com.google.code.gson
gson
8、使用枚举类来定义微信支付的各个接口url——选做
写这个枚举类也是因为懒,没其他的原因,里面的内容就是微信支付对应功能的API,这些在微信官网全部都能找到。前面那串是固定的API地址,我们在配置文件里面写死的那个domain字段就是,所以我们把后面那串内容也弄成枚举类的话,我们使用的时候就可以直接把这两个东西拼接起来了,就不用每次都敲一遍https://xxxxxxxx
@AllArgsConstructor
@Getter
public enum WxApiType {
// Native下单
NATIVE_PAY(“/v3/pay/transactions/native“),
// 查询订单
ORDER_QUERY_BY_NO(“/v3/pay/transactions/id/%s“),
//关闭订单
CLOSE_ORDER_BY_NO(“/v3/pay/transactions/out-trade-no/%s/close“),
// 申请退款
DOMESTIC_REFUNDS(“/v3/refund/domestic/refunds“),
// 查询单笔退款
DOMESTIC_REFUNDS_QUERY(“/v3/refund/domestic/refunds/%s“);
private final String type;
}
9、创建工具类
用来处理微信给我们返回的那些数据的,我直接拿的尚硅谷的代码来用,当然,你也可以自己写,其实就是获取Servlet然后解析出里面的一个个返回值。
public class HttpUtils {
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append(“n”);
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
【微信支付V3——Native支付】
微信支付一共提供了6种不同的支付产品:付款码、JSAPI、小程序、Native、App、刷脸。
其中Native支付就是我们后台发送金额数据,然后微信会根据这个金额,给我们返回一个字符串,我们再把这个字符串解析成二维码让用户扫码付款,非常常用的一个功能,也是这个教程使用的。
我们先来看看官方给出的流程图:
– 用户点击提交订单按钮
– 系统生成自己的订单
– 统一调用微信支付的下单API(Native下单)
– 微信内部生成一个预支付的交易
– 微信将支付的信息返回给系统
– 系统拿到这个返回值之后通过二维码生成工具生成二维码,展示给到用户
– 用户进行扫码操作
– 微信验证连接的有效性
– 验证通过则允许用户进行支付
– 用户输入密码完成支付
下面这个部分是并行处理的,没有固定的先后顺序
– 微信返回通知给到用户的手机
– 微信返回相应给到我们系统,当系统接收到相应之后给微信返回响应,当由于网络原因我们没有接收到微信给我们的回调的时候,我们需要主动通过查询操作获取这个订单的状态
(开始写代码)
具体的实现我们都放在service层
我们先创建一个接口,返回值的类型你们想写什么都可以,甚至不返回都可以,看你们自己需求
public interface WxPayService {
Mapstring,
}
然后是实现类,我们的具体操作都在实现类中进行,主要是拼接请求的参数,然后调用这个请求,记得写service注解
@Service
@Slf4j
public class WxPayServiceImpl implementsWxPayService{
@Resource private WxPayConfig wxPayConfig; // 把我们的配置文件注入进来
// 这个是我们在配置文件里面写好的HttpClient,因为添加了@Bean注解,所以在Spring启动的时候就被加载到容器里面了,我们就可以直接通过autowired注解将这个Bean注入进来使用
@Autowired private CloseableHttpClient wxPayClient;
// 这里就是方法的具体实现了
@Override
public Mapstring,
// 这里我们要做的事情有两个 第一个是生成我们系统自己的订单 第二个是调用统一支付API
// 在这个位置,你可以先生成自己的系统订单然后写到本地数据库里面,这里我就不写了
// 创建POST请求对象,里面输入的是地址,也就是那个https://api.wxpayxxxxx 那个,只不过我们直接去配置文件里面读取然后拼接出来了,懒得每次都输入一遍
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
Gson gson = new Gson();
Map paramsMap =new HashMap();
paramsMap.put(“appid”,wxPayConfig.getAppid()); // 我们的APPID
paramsMap.put(“mchid”,wxPayConfig.getMchId()); // 我们的商户ID
paramsMap.put(“description”,”测试微信支付1″); // 扫码之后显示的标题
paramsMap.put(“out_trade_no”,”TEST12345678″); // 商户订单号 我们自己生成的那个
// 这里就是微信响应给我们系统的那个地址 必须是能外网访问的,这里先随便写一个
paramsMap.put(“notify_url”,”https://abcabcabc”);
Map amountMap = new HashMap();
amountMap.put(“total”,1); // 支付金额
amountMap.put(“currency”,”CNY”); // 交易货币的类型
paramsMap.put(“amount”,amountMap);
// 将这个json对象转换成字符串,用于后面进行网络传输
String jsonParams = gson.toJson(paramsMap);
// 设置请求体
StringEntity entity = new StringEntity(jsonParams,”utf-8″);
entity.setContentType(“application/json”);
httpPost.setEntity(entity);
httpPost.setHeader(“Accept”, “application/json”);
//完成签名并执行请求
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
String body = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功 有返回就会进入这里
log.info(“请求成功,返回结果 = ” + body);
} else if (statusCode == 204) { //处理成功,无返回就会进入到这里
System.out.println(“success”);
} else { // 接口调用失败的话就会进入这里
System.out.println(“Native下单失败,响应码 = ” + statusCode+ “,返回结果= ” + body);
throw new IOException(“request failed”);
}
// 相微信那边返回的结果 我们将json返回转成一个Map对象
HashMapstring,string
// 然后从这个Map里面拿到code_url,这个是微信定义的,我们拿这个结果生成二维码
String code_url = resultMap.get(“code_url”);
HashMapstring,
return returnMap;
returnMap.put(“codeUrl”,code_url);
} finally {
response.close();
}
return null;
}
}
到了这里,实现类的方法就写好了,其实主要就是拼接参数(标题、金额多少。。。),然后调用一下微信的接口,然后拿到微信的返回值,就是这样一个简单的流程。
那么微信的返回其实是一串字符,我们拿这个返回值,随便找个在线的二维码生成网站,就可以生成一个二维码,你拿微信扫就能够唤醒支付了,就是那么简单。
最后是我们的控制层,这里其实就是调用了一下Service层的实现类仅此而已。
@PostMapping(“/nativePay”)
public HxbsResponse nativePay() throws Exception {
Mapstring,object
return new HxbsResponse().data(map);
}
【结束】
到了这里唤醒支付的流程就结束了,整个流程还是非常简单的,跟着思路一直走就可以了,当然,这一篇仅仅是唤醒支付而已,我们后续还要处理微信的回调,还有查账、退款等等功能没实现。
等有时间会在下一篇给大家详细讲解,其实就是不同的API调用而已,道理都是一样的。
如果有哪里不懂的可以在评论留言,也欢迎各位大佬指点。
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容