赞
踩
按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。
在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html
1.appid:微信公众账号或开放平台APP的唯一标识
2.mch_id:商户号 (配置文件中的partner)
3.partnerkey:商户密钥
4.sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性
1.添加maven依赖:
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
我们主要会用到微信支付SDK的以下功能:
(1)获取随机字符串:
WXPayUtil.generateNonceStr()
(2)MAP转换为XML字符串(自动添加签名):
WXPayUtil.generateSignedXml(param, partnerkey)
(3)XML字符串转换为MAP:
WXPayUtil.xmlToMap(result)
1.开发模式:
模式一:商户在后台给你生成二维码,用户打开扫一扫
模式二:商户后台系统调用微信支付【统一下单API】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付。
微信支付:生成xml发送请求。
2.在对应的模块中添加配置(采用尚硅谷的配置):
#关联的公众号appid
weixin.pay.appid=wx74862e0dfcf69954
#商户号
weixin.pay.partner=1558950191
#商户key
weixin.pay.partnerkey=T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
3.引入工具类(也可以不用):
package com.tan.user.utils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author tanxiangwen * @date 2023/1/28 * @描述 */ @Component public class WxUtil 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; } }
package com.tan.user.utils; import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.ParseException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * @author tanxiangwen * @date 2023/1/28 * @描述 */ 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(ConstantWxPropertiesUtils.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.生成微信支付二维码:
4.1接口编写:
package com.tan.user.service;
import java.util.Map;
/**
* @author tanxiangwen
* @date 2023/1/28
* @描述
*/
public interface WeixinService {
Map createNative(Long orderId) throws Exception;
}
WeixinServiceImpl:
public class WeixinServiceImpl implements WeixinService { @Autowired orderService orderService; @Autowired PaymentService paymentService; @Autowired RedisTemplate redisTemplate; @Autowired RefundInfoService refundInfoService; //生成微信支付二维码 @Override public Map createNative(Long orderId) throws Exception { //先从redis中获取 Map payMap = (Map) redisTemplate.opsForValue().get(orderId.toString()); if(payMap!=null){ return payMap; } //根据订单id获取订单信息 OrderInfo orderInfo = orderService.getById(orderId); //向支付表中添加记录 paymentService.savePaymentInfo(orderInfo, PaymentTypeEnum.WEIXIN.getStatus()); //设置二维码参数 //把参数转化为xml格式,用商户key进行加密,生成签名 //把参数和签名放到map中返回 Map paramMap = new HashMap<>(); //设置参数(固定) //商户id paramMap.put("appid", WxUtil.APPID); //商户号 paramMap.put("mch_id", WxUtil.PARTNER); //随机字符串(唯一) paramMap.put("nonce_str", WXPayUtil.generateNonceStr()); //商品描述 String body = orderInfo.getReserveDate() + "就诊"+ orderInfo.getDepname(); paramMap.put("body", body); //商户订单号 paramMap.put("out_trade_no", orderInfo.getOutTradeNo()); //paramMap.put("total_fee", order.getAmount().multiply(new BigDecimal("100")).longValue()+""); //订单金额(0.01) paramMap.put("total_fee", "1"); //当前IP paramMap.put("spbill_create_ip", "127.0.0.1"); //回调地址 paramMap.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify"); //交易类型: NATIVE--原生扫码支付 paramMap.put("trade_type", "NATIVE"); //调用微信接口,生成二维码 HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder"); httpClient.setXmlParam(WXPayUtil.generateSignedXml(paramMap, WxUtil.PARTNERKEY)); httpClient.setHttps(true);//使用https httpClient.post(); //微信会返回数据,xml格式 String content = httpClient.getContent(); //xml转换为map Map<String, String> resultMap = WXPayUtil.xmlToMap(content); //4、封装返回结果集 Map map = new HashMap<>(); //订单号 map.put("orderId", orderId); //总金额 map.put("totalFee", orderInfo.getAmount()); //状态码 map.put("resultCode", resultMap.get("result_code")); //二维码地址 map.put("codeUrl", resultMap.get("code_url")); //存储到redis中120分钟有效 if(resultMap.get("result_code")!=null){ redisTemplate.opsForValue().set(orderId.toString(),map,120, TimeUnit.MINUTES); } return map; }
注:其中的paymentInfo和orderInfo分别是支付表和订单表,根据自己的实际情况实现。
流程:
因为采用的是模式2开发,存在有效为两小时的问题,所以把信息存放到redis中,设置过期时间为两个小时。
第一步先从redis中取数据,看是否存在,如果存在就返回redis中存在的数据,不存在就生成,首先根据订单id查询出订单信息,然后保存自己的支付记录表。接下来就是生成一些二维码的参数,用map来存储,
代码里的参数都是固定的。
paramMap.put(“total_fee”, “1”)是订单金额,为了方便测试value为1就是1分钱,然后用HttpClient来调用微信接口。
httpClient.setXmlParam(WXPayUtil.generateSignedXml(paramMap, WxUtil.PARTNERKEY));
httpClient.setHttps(true);//使用https
httpClient.post();
因为调用微信支付接口参数需要xml格式,微信支付的WXPayUtil.generateSignedXml可以自动转成xml格式,并且用商户号进行加密,然后开启https请求,post方法。
请求后微信会给我们返回数据,数据同样是xml格式,我们需要转成map来进行操作:
//微信会返回数据,xml格式
String content = httpClient.getContent();
//xml转换为map
Map<String, String> resultMap = WXPayUtil.xmlToMap(content);
``
最后从`resultMap中获取自己想要的数据,再次用map封装起来给前端。
```java
//二维码地址
map.put("codeUrl", resultMap.get("code_url"));
这个一定要传,用于前端生成二维码。最后存储到redis中设置两个小时有效。
<template> <!-- header --> <div class="nav-container page-component"> <!--左侧导航 #start --> <div class="nav left-nav"> <div class="nav-item "> <span class="v-link clickable dark" onclick="javascript:window.location='/user'">实名认证 </span> </div> <div class="nav-item selected"> <span class="v-link selected dark" onclick="javascript:window.location='/order'"> 挂号订单 </span> </div> <div class="nav-item "> <span class="v-link clickable dark" onclick="javascript:window.location='/patient'"> 就诊人管理 </span> </div> <div class="nav-item "> <span class="v-link clickable dark"> 修改账号信息 </span> </div> <div class="nav-item "> <span class="v-link clickable dark"> 意见反馈 </span> </div> </div> <!-- 左侧导航 #end --> <!-- 右侧内容 #start --> <div class="page-container"> <div class="order-detail"> <div class="title"> 挂号详情</div> <div class="status-bar"> <div class="left-wrapper"> <div class="status-wrapper BOOKING_SUCCESS"> <span class="iconfont"></span> {{ orderInfo.param.orderStatusString }} </div> </div> <div class="right-wrapper"> <img src="@/assets/images/1314.png" class="code-img"> <div class="content-wrapper"> <div> 微信<span class="iconfont"></span>关注“综合HOSP助手”</div> <div class="watch-wrapper"> 获取最新健康咨询</div> </div> </div> </div> <div class="info-wrapper"> <div class="title-wrapper"> <div class="block"></div> <div>挂号信息</div> </div> <div class="info-form"> <el-form ref="form" :model="form"> <el-form-item label="就诊人信息:"> <div class="content"><span>{{ orderInfo.patientName }}</span></div> </el-form-item> <el-form-item label="就诊日期:"> <div class="content"><span>{{ orderInfo.reserveDate }} {{ orderInfo.reserveTime == 0 ? '上午' : '下午' }}</span></div> </el-form-item> <el-form-item label="就诊医院:"> <div class="content"><span>{{ orderInfo.hosname }} </span></div> </el-form-item> <el-form-item label="就诊科室:"> <div class="content"><span>{{ orderInfo.depname }} </span></div> </el-form-item> <el-form-item label="医生职称:"> <div class="content"><span>{{ orderInfo.title }} </span></div> </el-form-item> <el-form-item label="医事服务费:"> <div class="content"> <div class="fee">{{ orderInfo.amount }}元 </div> </div> </el-form-item> <el-form-item label="挂号单号:"> <div class="content"><span>{{ orderInfo.outTradeNo }} </span></div> </el-form-item> <el-form-item label="挂号时间:"> <div class="content"><span>{{ orderInfo.createTime }}</span></div> </el-form-item> </el-form> </div> </div> <div class="rule-wrapper mt40"> <div class="rule-title"> 注意事项</div> <div>1、请确认就诊人信息是否准确,若填写错误将无法取号就诊,损失由本人承担;<br> <span style="color:red">2、【取号】就诊当天需在{{ orderInfo.fetchTime }}在医院取号,未取号视为爽约,该号不退不换;</span><br> 3、【退号】在{{ orderInfo.quitTime }}前可在线退号 ,逾期将不可办理退号退费;<br> 4、请于就诊当日,携带预约挂号所使用的有效身份证件到院取号;<br> 5、请注意北京市医保患者在住院期间不能使用社保卡在门诊取号。 </div> </div> <div class="bottom-wrapper mt60" v-if="orderInfo.orderStatus == 0 || orderInfo.orderStatus == 1"> <div class="button-wrapper"> <div class="v-button white" @click="cancelOrder()">取消预约</div> </div> <div class="button-wrapper ml20" v-if="orderInfo.orderStatus == 0"> <div class="v-button" @click="pay()">支付</div> </div> </div> </div> </div> <!-- 右侧内容 #end --> <!-- 微信支付弹出框 --> <el-dialog :visible.sync="dialogPayVisible" style="text-align: left" :append-to-body="true" width="500px" @close="closeDialog"> <div class="container"> <div class="operate-view" style="height: 450px;"> <div class="wrapper wechat"> <div> <qriously :value="payObj.codeUrl" :size="200" style="padding-top: 200px"/> <div style="text-align: center;line-height: 25px;margin-bottom: 350px;"> 请使用微信扫一扫<br/> 扫描二维码支付 </div> </div> </div> </div> </div> </el-dialog> </div> <!-- footer --> </template> <script> import '~/assets/css/hospital_personal.css' import '~/assets/css/hospital.css' import orderInfoApi from '@/api/orderInfo' import weixinApi from '@/api/WeixinPay' export default { data() { return { orderId: null, orderInfo: { param: {} }, dialogPayVisible: false, payObj: { codeUrl: '' }, timer: null // 定时器名称 } }, created() { this.orderId = this.$route.query.orderId this.init() }, methods: { init() { orderInfoApi.getOrders(this.orderId).then(response => { console.log(response.data); this.orderInfo = response.data }) }, pay() { this.dialogPayVisible = true weixinApi.createNative(this.orderId).then(response => { this.payObj = response.data if(this.payObj.codeUrl == '') { this.dialogPayVisible = false this.$message.error("支付错误") } else { this.timer = setInterval(() => { this.queryPayStatus(this.orderId) }, 3000); //3000表每隔3秒 } }) }, queryPayStatus(orderId) { weixinApi.queryPayStatus(orderId).then(response => { if (response.message == '支付中') { //继续查询 return } // 支付成功,关闭定时器,刷新页面 clearInterval(this.timer); window.location.reload() }) }, closeDialog() { if(this.timer) { clearInterval(this.timer); } }, cancelOrder() { this.$confirm('确定取消预约吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // promise // 点击确定,远程调用 return orderInfoApi.cancelOrder(this.orderId) }).then((response) => { this.$message.success('取消成功') this.init() }).catch(() => { this.$message.info('已取消预约') }) } } } </script> <style> .info-wrapper { padding-left: 0; padding-top: 0; } .content-wrapper { color: #333; font-size: 14px; padding-bottom: 0; } .bottom-wrapper { width: 100%; } .button-wrapper { margin: 0; } .el-form-item { margin-bottom: 5px; } .bottom-wrapper .button-wrapper { margin-top: 0; } </style>
主要看支付按钮,用户一点击就会调用pay方法,请求后端拿到微信二维码数据。二维码以弹出层的方式出现。
<el-dialog :visible.sync="dialogPayVisible" style="text-align: left" :append-to-body="true" width="500px" @close="closeDialog"> <div class="container"> <div class="operate-view" style="height: 450px;"> <div class="wrapper wechat"> <div> <qriously :value="payObj.codeUrl" :size="200" style="padding-top: 200px"/> <div style="text-align: center;line-height: 25px;margin-bottom: 350px;"> 请使用微信扫一扫<br/> 扫描二维码支付 </div> </div> </div> </div> </div> </el-dialog>
<qriously :value="payObj.codeUrl" :size="200" style="padding-top: 200px"/>
这里边会生成二维码。前端生成二维码的时候,使用了vue-qriously自行百度。关于支付成功后的逻辑可以根据自己的需求实现。我的逻辑就是用户在点击支付的同时会设置定时器调用另一个api来根据订单号查询这个订单的支付状态,如果为支付成功就清除定时器,关闭弹出层,刷新页面。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。