近日写了些微信支付的接口怕日后忘了,写下来。方便以后回顾。
首先说明我用的是spring boot 的框架来+wxpay-sdk 来实现的。
微信支付分为很多种方式我这次实现的多以web为主所以只实现了 JSAPI支付,Native支付(pc上的扫码支付),H5支付。
首先我们要了加入微信支付的sdk的ar包。
maven:
com.github.wxpay
wxpay-sdk
0.0.3
加入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 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 map = WxPayEntityUtil.getPcSmPay(outTradeNo, productId, body, totalFee, urlH, request);
System.out.println(map.get("spbill_create_ip"));
Map 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 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 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 map = WxPayEntityUtil.getH5Pay(outTradeNo, productId, body, totalFee, urlH, request);
System.out.println(map.get("spbill_create_ip"));
Map 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 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 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 map = WxPayEntityUtil.getJsApiPay(outTradeNo, productId, body, totalFee, urlH, openId, request);
System.out.println(map.get("spbill_create_ip"));
Map responseMap = wxPay.unifiedOrder(map);
String returnCode = responseMap.get("return_code");
String resultCode = responseMap.get("result_code");
Map rmap = new HashMap<>();
if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
long timeStamp = new Date().getTime()/1000;
Map 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 queryPay(String orderId) throws SocketException {
Map resultMap = new HashMap();
for (int i = 0; i < 300; i++) {
//"*************************调用支付查询 start*************************//
Map 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 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 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