微信支付介绍
微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。 微信支付开发者文档
微信扫码支付申请
第一步:注册公众号(类型须为:服务号)
请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型。
第二步:认证公众号
公众号认证后才可申请微信支付,认证费:300元/年。
第三步:提交资料申请微信支付
登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。
第四步:开户成功,登录商户平台进行验证
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填
写财付通备付金打的小额资金数额,完成账户验证。
第五步:在线签署协议
本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。
微信扫码支付流程
1、用户在商户侧完成下单,使用微信支付进行支付
2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
4、中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)
5、如支付成功,商户后台会接收到微信侧的异步通知
6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
7、商户在展示页面,引导用户主动发起支付结果的查询
8,9、商户后台判断是否接收到微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态
10、展示最终的订单支付结果给用户
微信扫码支付具体实现
1、引入微信支付的SDK
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2、添加配置依赖
注:此处为什么要整合redis呢,就是让生成的二维码,在一定时间内扫码有效,有效时间过期后,无法扫码
#关联的公众号appid
weixin.appid=
#商户号
weixin.partner=
#商户key
weixin.partnerkey=
#redis配置
#ip
spring.redis.host=
#端口
spring.redis.port=6379
#使用redis第几个库
spring.redis.database=2
#超时时间
spring.redis.timeout=1800000
#lectuce配置
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间,负数表示没有限制
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
3、引入工具类
1、此工具类,是为了初始化配置文件中,微信支付相关配置内容
@Component
public class ConstantPropertiesUtils implements InitializingBean {
@Value("${weixin.appid}")
private String appid;
@Value("${weixin.partner}")
private String partner;
@Value("${weixin.partnerkey}")
private String partnerkey;
public static String APPID;
public static String PARTNER;
public static String PARTNERKEY;
@Override
public void afterPropertiesSet() throws Exception {
APPID = appid;
PARTNER = partner;
PARTNERKEY = partnerkey;
}
}
2、此工具类是为了进行Http请求的工具类
public class HttpClient {
private String url;
private Map<String, String> param;
private int statusCode;
private String content;
private String xmlParam;
private boolean isHttps;
private boolean isCert = false;
//证书密码 微信商户号(mch_id)
private String certPassword;
public boolean isHttps() {
return isHttps;
}
public void setHttps(boolean isHttps) {
this.isHttps = isHttps;
}
public boolean isCert() {
return isCert;
}
public void setCert(boolean cert) {
isCert = cert;
}
public String getXmlParam() {
return xmlParam;
}
public void setXmlParam(String xmlParam) {
this.xmlParam = xmlParam;
}
public HttpClient(String url, Map<String, String> param) {
this.url = url;
this.param = param;
}
public HttpClient(String url) {
this.url = url;
}
public String getCertPassword() {
return certPassword;
}
public void setCertPassword(String certPassword) {
this.certPassword = certPassword;
}
public void setParameter(Map<String, String> map) {
param = map;
}
public void addParameter(String key, String value) {
if (param == null)
param = new HashMap<String, String>();
param.put(key, value);
}
public void post() throws ClientProtocolException, IOException {
HttpPost http = new HttpPost(url);
setEntity(http);
execute(http);
}
public void put() throws ClientProtocolException, IOException {
HttpPut http = new HttpPut(url);
setEntity(http);
execute(http);
}
public void get() throws ClientProtocolException, IOException {
if (param != null) {
StringBuilder url = new StringBuilder(this.url);
boolean isFirst = true;
for (String key : param.keySet()) {
if (isFirst)
url.append("?");
else
url.append("&");
url.append(key).append("=").append(param.get(key));
}
this.url = url.toString();
}
HttpGet http = new HttpGet(url);
execute(http);
}
/**
* set http post,put param
*/
private void setEntity(HttpEntityEnclosingRequestBase http) {
if (param != null) {
List<NameValuePair> nvps = new LinkedList<NameValuePair>();
for (String key : param.keySet())
nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
}
if (xmlParam != null) {
http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
}
}
private void execute(HttpUriRequest http) throws ClientProtocolException,
IOException {
CloseableHttpClient httpClient = null;
try {
if (isHttps) {
if(isCert) {
//TODO 需要完善
FileInputStream inputStream = new FileInputStream(new File(ConstantPropertiesUtils.CERT));
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] partnerId2charArray = certPassword.toCharArray();
keystore.load(inputStream, partnerId2charArray);
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslContext,
new String[] {
"TLSv1" },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
} else {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.build();
}
} else {
httpClient = HttpClients.createDefault();
}
CloseableHttpResponse response = httpClient.execute(http);
try {
if (response != null) {
if (response.getStatusLine() != null)
statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
// 响应内容
content = EntityUtils.toString(entity, Consts.UTF_8);
}
} finally {
response.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpClient.close();
}
}
public int getStatusCode() {
return statusCode;
}
public String getContent() throws ParseException, IOException {
return content;
}
}
4、生成支付二维码
public Map createNative(Long orderId) {
try {
//从redis获取数据
Map payMap = (Map)redisTemplate.opsForValue().get(orderId.toString());
if(payMap != null) {
return payMap;
}
//1 根据orderId获取订单信息
OrderInfo order = orderService.getById(orderId);
//2 向支付记录表添加信息
paymentService.savePaymentInfo(order, PaymentTypeEnum.WEIXIN.getStatus());
//3设置参数,
//把参数转换xml格式,使用商户key进行加密
Map paramMap = new HashMap();
paramMap.put("appid", ConstantPropertiesUtils.APPID);
paramMap.put("mch_id", ConstantPropertiesUtils.PARTNER);
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
String body = order.getReserveDate() + "就诊"+ order.getDepname();
paramMap.put("body", body);
paramMap.put("out_trade_no", order.getOutTradeNo());
//paramMap.put("total_fee", order.getAmount().multiply(new BigDecimal("100")).longValue()+"");
paramMap.put("total_fee", "1"); //为了测试,统一写成这个值
paramMap.put("spbill_create_ip", "127.0.0.1");
paramMap.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify");
paramMap.put("trade_type", "NATIVE");
//4 调用微信生成二维码接口,httpclient调用
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
//设置map参数
client.setXmlParam(WXPayUtil.generateSignedXml(paramMap,ConstantPropertiesUtils.PARTNERKEY));
client.setHttps(true);
client.post();
//5 返回相关数据
String xml = client.getContent();
//转换map集合
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
System.out.println("resultMap:"+resultMap);
//6 封装返回结果集
Map map = new HashMap<>();
map.put("orderId", orderId);
map.put("totalFee", order.getAmount());
map.put("resultCode", resultMap.get("result_code"));
map.put("codeUrl", resultMap.get("code_url")); //二维码地址
if(resultMap.get("result_code") != null) {
redisTemplate.opsForValue().set(orderId.toString(),map,120, TimeUnit.MINUTES);
}
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
5、查询支付状态
1、controller
//查询支付状态
@GetMapping("queryPayStatus/{orderId}")
public Result queryPayStatus(@PathVariable Long orderId) {
//调用微信接口实现支付状态查询
Map<String,String> resultMap = weixinService.queryPayStatus(orderId);
//判断
if(resultMap == null) {
return Result.fail().message("支付出错");
}
if("SUCCESS".equals(resultMap.get("trade_state"))) {
//支付成功
//更新订单状态
String out_trade_no = resultMap.get("out_trade_no");//订单编码
paymentService.paySuccess(out_trade_no,resultMap);
return Result.ok().message("支付成功");
}
return Result.ok().message("支付中");
}
2、service
public Map<String, String> queryPayStatus(Long orderId) {
try {
//1 根据orderId获取订单信息
OrderInfo orderInfo = orderService.getById(orderId);
//2 封装提交参数
Map paramMap = new HashMap();
paramMap.put("appid", ConstantPropertiesUtils.APPID);
paramMap.put("mch_id", ConstantPropertiesUtils.PARTNER);
/**订单交易号*/
paramMap.put("out_trade_no", orderInfo.getOutTradeNo());
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
//3 设置请求内容
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
client.setXmlParam(WXPayUtil.generateSignedXml(paramMap,ConstantPropertiesUtils.PARTNERKEY));
client.setHttps(true);
client.post();
//4 得到微信接口返回数据
String xml = client.getContent();
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
System.out.println("支付状态resultMap:"+resultMap);
//5 把接口数据返回
return resultMap;
}catch(Exception e) {
return null;
}
}
6、退款功能
参考文档:微信退款
该接口需要使用证书,详情参考文档并下载证书
此处将退款证书,保存在当前模块目录下
public Boolean refund(Long orderId) {
try {
//获取支付记录信息
PaymentInfo paymentInfo = paymentService.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());
//添加信息到退款记录表
RefundInfo refundInfo = refundInfoService.saveRefundInfo(paymentInfo);
//判断当前订单数据是否已经退款
if(refundInfo.getRefundStatus().intValue() == RefundStatusEnum.REFUND.getStatus().intValue()) {
return true;
}
//调用微信接口实现退款
//封装需要参数
Map<String,String> paramMap = new HashMap<>();
paramMap.put("appid",ConstantPropertiesUtils.APPID); //公众账号ID
paramMap.put("mch_id",ConstantPropertiesUtils.PARTNER); //商户编号
paramMap.put("nonce_str",WXPayUtil.generateNonceStr());
paramMap.put("transaction_id",paymentInfo.getTradeNo()); //微信订单号
paramMap.put("out_trade_no",paymentInfo.getOutTradeNo()); //商户订单编号
paramMap.put("out_refund_no","tk"+paymentInfo.getOutTradeNo()); //商户退款单号
// paramMap.put("total_fee",paymentInfoQuery.getTotalAmount().multiply(new BigDecimal("100")).longValue()+"");
// paramMap.put("refund_fee",paymentInfoQuery.getTotalAmount().multiply(new BigDecimal("100")).longValue()+"");
paramMap.put("total_fee","1"); // 订单金额
paramMap.put("refund_fee","1"); //退款金额
String paramXml = WXPayUtil.generateSignedXml(paramMap,ConstantPropertiesUtils.PARTNERKEY);
//设置调用接口内容
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/secapi/pay/refund");
client.setXmlParam(paramXml);
client.setHttps(true);
//设置证书信息
client.setCert(true);
//TODO 此处括号中为读取后的证书内容
client.setCertPassword(。。。。);
client.post();
//接收返回数据
String xml = client.getContent();
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
if (null != resultMap && WXPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("result_code"))) {
refundInfo.setCallbackTime(new Date());
refundInfo.setTradeNo(resultMap.get("refund_id"));
refundInfo.setRefundStatus(RefundStatusEnum.REFUND.getStatus());
refundInfo.setCallbackContent(JSONObject.toJSONString(resultMap));
refundInfoService.updateById(refundInfo);
return true;
}
return false;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}