微信支付的基本概念
1.商户号:登入微信商户平台看账号中心的登入账号
2.APPID:微信公众号的开发者ID
关联商户号和APPID 账号。
先登入商户平台进入产品中心进行关联APPID
再到公众号通知中心中同意即可。
3.设置密匙和证书:
到商户平台->账户中心->安全中心->API安全中心设置
对称加密和非对称加密:对称加密指加密和解密的密匙是一样的。
非对称加密为公匙和私匙 公匙为给其他用户加密或解密的 私匙为自己解密或加密的。
即有两种情况:
第一种,其他用户用公匙加密发送信息,这个信息只有自己的私匙可以解开看到信息。
第二种,自己用私匙加密发送信息,这个信息有公匙的其他用户都可以解开看到信息,所以这种情况下要自己发送的信息只有一个用户看到的话,要自己发送的信息用其用户的公匙发送,其用户的私匙解密。
用法和比较:非对称加密因为密匙不一样所以更安全,但解析慢,所以一般的用法是用非对称加密的方式来传输对称加密的密匙,再用对象加密的方式传输后面的内容。
4.身份认证
身份认证指的是非对称加密方式,自己用私匙加密发送信息,其他用户拿公匙去解密时,成功代表身份认证成功,不成功代表身份认证失败。
5.数字签名和验签
如何判断传输过来的文件内容是否被修改过?
数字签名是:利用hash算法将文件内容算值取模生成一定长度的hash值,再将hash值通过密匙加密后再发送。
数字验签是:文件内容传输过来后再通过hash算法取出hash值 和 放在文件内容中的数字签名来解密后的值是否一致来判断内容是否被修改过。
6.数字证书
第三方服务存放公匙。图中CA提供存放公匙。Pat只要知道CA的公匙就可以拿到任意Bob的公匙。
微信支付安全问题
商户证书和微信平台支付证书(第三方服务)
微信支付平台证书 包含微信支付平台的标识和公匙,商户可以用来验签操作。
微信支付Demo搭建
- 添加SDK等依赖
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
<!-- 其他springboot相关依赖就不列举-->
- 配置yml文件 没有相关微信方面的配置 正常的springboot项目配置
server:
port: 8090 #服务端口
spring:
application:
name: payment-demo #应用的名字
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/payment_demo?serverTimezone=GMT%2B8&characterEncoding=utf-8
username: root
password: 123456
mybatis-plus:
configuration: #sql日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/atguigu/paymentdemo/mapper/xml/*.xml
logging:
level:
root: info
- 配置微信相关配置文件 wxpay.properties 商户id,私匙等信息
# 商户id
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F
# 商户私匙
wxpay.private-key-path=apiclient_key.pem
# v3 对称加密 密匙
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信地址
wxpay.domain=https://api.mch.weixin.qq.com
# 穿透地址
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io
# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
- 启动类编写
@SpringBootApplication
//引入Spring Task 定时器 定时查询订单状态
@EnableScheduling
public class PaymentDemoApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentDemoApplication.class, args);
}
}
- 编写微信conf类 设置签名验证器和httpclient的bean注入到spring容器
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
// APIv2密钥
private String partnerKey;
/**
* 获取商户的私钥文件
* @param filename
* @return
*/
private PrivateKey getPrivateKey(String filename){
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取签名验证器
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier(){
log.info("获取签名验证器");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
/**
* 获取http请求对象
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient,无需进行应答签名验证,跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient(){
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, mchSerialNo, privateKey)
//无需进行签名验证、通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
}
}
- 测试业务 Native下单API的使用 官方API文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_4_1.shtml
Controller类设置请求ulr
/**
* Native下单
* @param productId
* @return
* @throws Exception
*/
@ApiOperation("调用统一下单API,生成支付二维码")
@PostMapping("/native/{productId}")
public R nativePay(@PathVariable Long productId) throws Exception {
log.info("发起支付请求 v3");
//返回支付二维码连接和订单号
Map<String, Object> map = wxPayService.nativePay(productId);
return R.ok().setData(map);
}
Service实现类
/**
* 创建订单,调用Native支付接口
* @param productId
* @return code_url 和 订单号
* @throws Exception
*/
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> nativePay(Long productId) throws Exception {
log.info("生成订单");
//生成订单
OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
String codeUrl = orderInfo.getCodeUrl();
if(orderInfo != null && !StringUtils.isEmpty(codeUrl)){
log.info("订单已存在,二维码已保存");
//返回二维码
Map<String, Object> map = new HashMap<>();
map.put("codeUrl", codeUrl);
map.put("orderNo", orderInfo.getOrderNo());
return map;
}
log.info("调用统一下单API");
//调用统一下单API 这里等价 HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com//v3/pay/transactions/native")
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数
Gson gson = new Gson();
Map paramsMap = new HashMap();
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("mchid", wxPayConfig.getMchId());
paramsMap.put("description", orderInfo.getTitle());
paramsMap.put("out_trade_no", orderInfo.getOrderNo());
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
// 官网文档上 接口的必输项参数必须设置上
Map amountMap = new HashMap();
amountMap.put("total", orderInfo.getTotalFee());
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);
// 设置http请求的参数,表头等信息
StringEntity entity = new StringEntity(jsonParams,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept"</