微信支付爬坑记录(代码篇)
近日写了些微信支付的接口怕日后忘了,写下来。方便以后回顾。
首先说明我用的是spring boot 的框架来+wxpay-sdk 来实现的。
微信支付分为很多种方式我这次实现的多以web为主所以只实现了 JSAPI支付,Native支付(pc上的扫码支付),H5支付。
首先我们要了加入微信支付的sdk的ar包。
maven:
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
加入jar包后我去在我们的配置文件中加入我们的配置信息。
pay:
wxpay:
appID: 公众账号ID
mchID: 商户号
key: api密钥
sandboxKey: 沙箱环境密钥
certPath: API证书绝对路径
notifyUrl:退款异步通知地址
useSandbox: 是否看起沙箱测试
这里可以根据不同的项目进行配置。
之后我们去写获取配置文件。
@Data
@Slf4j
/** 获取配置文件的内容 */
@ConfigurationProperties(prefix = "pay.wxpay")
public class MyWXPayConfig implements WXPayConfig{
/** 公众账号ID */
private String appID;
/** 商户号 */
private String mchID;
/** API 密钥 */
private String key;
/** API 沙箱环境密钥 */
private String sandboxKey;
/** API证书绝对路径 */
private String certPath;
/** 退款异步通知地址 */
private String notifyUrl;
private Boolean useSandbox;
/** HTTP(S) 连接超时时间,单位毫秒 */
private int httpConnectTimeoutMs = 8000;
/** HTTP(S) 读数据超时时间,单位毫秒 */
private int httpReadTimeoutMs = 10000;
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
@Override
public InputStream getCertStream() {
File certFile = new File(certPath);
InputStream inputStream = null;
try {
inputStream = new FileInputStream(certFile);
} catch (FileNotFoundException e) {
log.error("cert file not found, path={}, exception is:{}", certPath, e);
}
return inputStream;
}
@Override
public String getKey(){
if (useSandbox) {
return sandboxKey;
}
return key;
}
}
配置写完了我们要将wxpay-sdk 里面的使用类放入配置文件里。
@Configuration
@EnableConfigurationProperties(MyWXPayConfig.class)
public class WXPayConfiguration {
@Autowired
private MyWXPayConfig wxPayConfig;
/**
* useSandbox 沙盒环境
*
* @return
*/
@Bean
public WXPay wxPay() {
return new WXPay(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox());
}
@Bean
public WXPayClient wxPayClient() {
return new WXPayClient(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox());
}
}
写到这里微信的基本配置差不多写完了。
为什么要用微信的sdk那。因为这里面微信内置了访问支付和退款等接口。对于微信的签名在这里不用自己写方法去做签名,基础参数如AppId,mchID,key 这些在配置文件中添加的不需要每次都拿出来。直接添加剩下的参数即可。省略了一些操作。在签名上sdk,会根据你的参数自行帮你签名。这样即减少了代码量。也解决了签名的错误问题。(ps:但也因为如此我前期做的时候没有看签名规则,吃了一次大亏。)
回归正题:
接下来我们开始写服务类。
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private WXPay wxPay;
@Autowired
private WXPayClient wxPayClient;
@Autowired
private MyWXPayConfig myWXPayConfig;
/**
* 扫码支付
* @param request 内置对象
* @param outTradeNo 订单号
* @param productId 商品id
* @param body 商品说明
* @param totalFee 标价金额
* @param notifyUrl 通知地址
* @return
* @throws Exception
*/
@Override
public Map<String, String> wxPcSmPay(HttpServletRequest request, String outTradeNo, String productId, String body, String totalFee, String notifyUrl) throws Exception {
String u = notifyUrl.startsWith("/") ? notifyUrl.substring(1) : notifyUrl;
String urlH = myWXPayConfig.getNotifyUrl().endsWith("/") ? myWXPayConfig.getNotifyUrl() + u : myWXPayConfig.getNotifyUrl() + "/" + u;
Map<String, String> map = WxPayEntityUtil.getPcSmPay(outTradeNo, productId, body, totalFee, urlH, request);
System.out.println(map.get("spbill_create_ip"));
Map<String, String> responseMap = wxPay.unifiedOrder(map);
String returnCode = responseMap.get("return_code");
String resultCode = responseMap.get("result_code");
if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
/*
为了方便使用不泄露太多其他的信息。我精简了返回参数
*/
Map<String, String> rmap = new HashMap<>();
String codeUrl = responseMap.get("code_url");
rmap.put("out_trade_no", outTradeNo);
rmap.put("code_url", codeUrl);
rmap.put("return_code", responseMap.get("return_code"));
return rmap;
}
return responseMap;
}
/**
* h5 支付
* @param request 内置对象
* @param outTradeNo 订单号
* @param productId 商品id
* @param body 商品说明
* @param totalFee 标价金额
* @param notifyUrl 通知地址
* @return
* @throws Exception
*/
@Override
public Map<String, String> wxH5Pay(HttpServletRequest request, String outTradeNo, String productId, String body, String totalFee, String notifyUrl) throws Exception {
String u = notifyUrl.startsWith("/") ? notifyUrl.substring(1) : notifyUrl;
String urlH = myWXPayConfig.getNotifyUrl().endsWith("/") ? myWXPayConfig.getNotifyUrl() + u : myWXPayConfig.getNotifyUrl() + "/" + u;
Map<String, String> map = WxPayEntityUtil.getH5Pay(outTradeNo, productId, body, totalFee, urlH, request);
System.out.println(map.get("spbill_create_ip"));
Map<String, String> responseMap = wxPay.unifiedOrder(map);
String returnCode = responseMap.get("return_code");
String resultCode = responseMap.get("result_code");
if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
Map<String, String> rmap = new HashMap<>();
String mweb_url = responseMap.get("mweb_url");
rmap.put("out_trade_no", outTradeNo);
rmap.put("mweb_url", mweb_url);
rmap.put("return_code", responseMap.get("return_code"));
return rmap;
}
return responseMap;
}
/**
* JSPAIpay 支付
* @param request 内置对象
* @param outTradeNo 订单号
* @param productId 商品id
* @param body 商品说明
* @param totalFee 标价金额
* @param notifyUrl 通知地址
* @param openId 用户唯一标识
* @return
* @throws Exception
*/
@Override
public Map<String, String> JSPAIpay(HttpServletRequest request, String outTradeNo, String productId, String body, String totalFee, String notifyUrl, String openId) throws Exception {
String u = notifyUrl.startsWith("/") ? notifyUrl.substring(1) : notifyUrl;
String urlH = myWXPayConfig.getNotifyUrl().endsWith("/") ? myWXPayConfig.getNotifyUrl() + u : myWXPayConfig.getNotifyUrl() + "/" + u;
Map<String, String> map = WxPayEntityUtil.getJsApiPay(outTradeNo, productId, body, totalFee, urlH, openId, request);
System.out.println(map.get("spbill_create_ip"));
Map<String, String> responseMap = wxPay.unifiedOrder(map);
String returnCode = responseMap.get("return_code");
String resultCode = responseMap.get("result_code");
Map<String, String> rmap = new HashMap<>();
if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
long timeStamp = new Date().getTime()/1000;
Map<String, String> signMap =new HashMap<>();
signMap.put("appId",responseMap.get("appid"));
signMap.put("timeStamp",timeStamp+"");
signMap.put("nonceStr",responseMap.get("nonce_str"));
signMap.put("package","prepay_id="+responseMap.get("prepay_id"));
signMap.put("signType","MD5");
signMap.put("paySign", WXPayUtil.generateSignature(signMap,myWXPayConfig.getKey()));
signMap.put("return_code","SUCCESS");
return signMap;
}
return responseMap;
}
/**
* 查询订单是否完成
* @param orderId
* @return
* @throws SocketException
*/
@Override
public Map<String, String> queryPay(String orderId) throws SocketException {
Map<String, String> resultMap = new HashMap<String, String>();
for (int i = 0; i < 300; i++) {
//"*************************调用支付查询 start*************************//
Map<String, String> key = new HashMap<>();
key.put("appid", myWXPayConfig.getAppID());
key.put("mch_id", myWXPayConfig.getMchID());
key.put("out_trade_no", orderId);
key.put("sign", myWXPayConfig.getMchID());
try {
resultMap = wxPay.orderQuery(key);
if ("SUCCESS".equals(resultMap.get("trade_state"))) {
return resultMap;
}
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
return resultMap;
}
/**
* 回调地址 定点完成后会被请求
* @param request
* @param response
* @throws Exception
*/
@Override
public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String> reqData = wxPayClient.getNotifyParameter(request);
String returnCode = reqData.get("return_code");
String resultCode = reqData.get("result_code");
if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
if (signatureValid) {
// TODO 业务处理
Map<String, String> responseMap = new HashMap<>(2);
responseMap.put("return_code", "SUCCESS");
responseMap.put("return_msg", "OK");
String responseXml = WXPayUtil.mapToXml(responseMap);
response.setContentType("text/xml");
response.getWriter().write(responseXml);
response.flushBuffer();
}
}
}
}
ps: 工具类,WxPayEntityUtil是我做的一个方便将参数封装成 map的工具类。
我的理解wxpay sdk :
微信的支付接口说到底他就是一个访问接口。为了安全系数访问的参数必须是xml形式的参数,而且每次访问都系要sign 签名。而且大部分呢的接口都要我们的基础参数(appId,mchID,key)。
我们在最初获取配置文件的时候 继承了一个接口 WXPayConfig 。在wxpay 生成bean中的将这些配置(也就是基础的数据)放入到了bean中可以直接调用。之后在方法中接收到你的map参数。将固定的参数放入map中在生成sign签名放入mxl中向接口发送请求。
入坑点:
交易类型trade_type
JSAPI–JSAPI支付(或小程序支付)、NATIVE–Native支付、APP–app支付,MWEB–H5支付
虽然支付的接口是统一的但不同的支付类型需要不同的参数。
测试方便,有一些接口需要在线上测试。这点spring Boot开发真的很不爽,每次都要打包上传重启。如果是tomcat 运行项目或许可以 替换class 重启就好了。
当然还有微信支付平台上面的配置也很饶人。(ps:第一次去配置很懵圈。)
最后接口文档还是仔仔细细的看啊:
注:微信接口:https://pay.weixin.qq.com/wiki/doc/api/index.html
- 一网打进Linux下那些查找命令
- 高颜值可定制在线绘图工具-第三版
- network3D 交互式网络生成
- 如何用六点教会老婆写 Python ?
- 连高晓松都想学的区块链江湖切口,「HODL」是什么意思?
- Spring Data REST 与 Spring RestTemplate 实战详解
- 程序员炒股,如何计算股票投资组合的风险和收益
- Docker 容器化部署运维 OpenStack 和 Ceph
- 关于设计模式的思考
- Spring 框架之 AOP 原理剖析
- Java 平台反应式编程(Reactive Programming)入门
- 从原理到实例,他用区块链技术做一了个COIN 客户端
- 解锁Spring Data Redis的正确使用姿势
- 互联网厂工必知必会:SQL基础篇
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 攻击LNMP架构Web应用的几个小Tricks
- Hbase-2.0.0_02_常用操作
- phpjiami 数种解密方法
- Hbase-2.0.0_03_Hbase数据模型
- ELK-elasticsearch-6.3.2部署
- Real World CTF 2018 bookhub 总结
- python http.server open redirect vulnerability
- ELK-elasticsearch-6.3.2插件【head,bigdesk,cerebro[kopf]】安装
- ELK-kibana-6.3.2部署
- node.js + postgres 从注入到Getshell
- ELK-logstash-6.3.2部署
- K8S节点异常怎么办?TKE"节点健康检查和自愈"来帮忙
- ELK-logstash-6.3.2-常用配置
- ELK-elkstack-使用消息队列
- kafka_2.11-2.0.0_安装部署