微信支付二维码生成
- 根据orderid生成二维码,放入redis中,设置过期时间。向支付表中添加支付信息。
- 二维码的生成需要设置参数,调用微信生成的二维码接口,设置的参数包括申请的公众号appid,商户号,商户key,就诊人的信息,订单编号,订单金额,回调地址。
- 用HttpClient来根据URL访问第三方接口并且传递参数,使用商户key将数据转成xml格式然后加密发送请求。
- 从微信方得到相关参数。
- 将返回的参数转为map形式,包括code_url,即二维码地址
- 如果code_url不空,则放入redis中,orderid为key,map为value
//生成微信支付二维码
@Override
public Map createNative(Long orderId) {
try {
//从redis获取数据
Map payMap = (Map) redisTemplate.opsForValue().get(orderId.toString());
if(null != payMap) {
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来根据URL访问第三方接口并且传递参数
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
//client设置参数
//ConstantPropertiesUtils.PARTNERKEY申请认证得到的,用来加密的,
//通过这个key将数据转成xml格式然后加密
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);
//封装返回的结果集
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(null != resultMap.get("result_code")) {
//微信支付二维码2小时过期,可采取2小时未支付取消订单
redisTemplate.opsForValue().set(orderId.toString(), map, 120, TimeUnit.MINUTES);
}
return map;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
前端二维码显示
根据code_url,用vue-qriously显示
<!-- 显示支付的二维码 -->
<qriously :value="payObj.codeUrl" :size="220"/>
查询支付状态
前端
每隔3秒调用查询支付状态的接口,setInterval(),设置定时器
//生成支付二维码方法
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 {
//每隔3秒调用查询支付状态的接口
this.timer = setInterval(() => {
this.queryPayStatus(this.orderId);
}, 3000);
}
});
},
//查询支付状态
queryPayStatus(orderId) {
weixinApi.queryPayStatus(orderId).then(response => {
if (response.message == '支付中') {
return
}
// 清楚定时器效果
clearInterval(this.timer);
window.location.reload()
})
},
controller
- 调用微信接口实现支付状态查询queryPayStatus,
- 通过返回的map的trade_status查看是否成功,
- 成功则更改订单状态,处理支付结果paysuccess,也就是把orderInfo表和支付表paymentInfo的支付状态修改成已支付,并且更新时间。
- 支付成功后前端显示支付成功,在payment_info表中,payment_status更新为2,医院的order_info中更新order_status=1。(注意order_info表是yygh_manage的而不是yygh_order的)
//根据订单id,查询支付状态
@ApiOperation(value = "查询支付状态")
@GetMapping("/queryPayStatus/{orderId}")
public Result queryPayStatus(
@ApiParam(name = "orderId", value = "订单id", required = true)
@PathVariable("orderId") Long orderId) {
//调用微信接口实现支付状态查询
Map<String, String> resultMap = weixinService.queryPayStatus(orderId, PaymentTypeEnum.WEIXIN.name());
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, PaymentTypeEnum.WEIXIN.getStatus(), resultMap);
return Result.ok().message("支付成功");
}
return Result.ok().message("支付中");
}
微信接口查询订单状态queryPayStatus,返回map
- 根据orderId获取订单信息,封装参数后向微信接口发送请求,查询订单状态,将结果返回map,通过map的trade_status查看是否成功
//调用微信接口实现支付状态查询
@Override
public Map queryPayStatus(Long orderId, String paymentType) {
try {
//根据orderid获取订单信息
OrderInfo orderInfo = orderService.getById(orderId);
//1、封装参数
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());
//2、设置请求,微信提供的订单状态查询路径
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantPropertiesUtils.PARTNERKEY));
client.setHttps(true);
client.post();
//3、返回第三方的数据,转成Map
String xml = client.getContent();
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
//4、返回
return resultMap;
} catch (Exception e) {
return null;
}
}
支付成功paySuccess
- 更新orderInfo的支付状态和时间
- 更新paymentInfo的支付状态
- 远程调用医院接口更新订单支付信息,即再医院接口中更新orderInfo的orderStatus=1
// 更改订单状态,处理支付结果
@Override
public void paySuccess(String out_trade_no, Integer paymentType, Map<String, String> paramMap) {
//1 根据订单编号得到支付记录
PaymentInfo paymentInfo = this.getPaymentInfo(out_trade_no, paymentType);
if (null == paymentInfo) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
if (paymentInfo.getPaymentStatus() != PaymentStatusEnum.UNPAID.getStatus()) {
return;
}
//2 更新修改支付状态
PaymentInfo paymentInfoUpd = new PaymentInfo();
paymentInfoUpd.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());
paymentInfoUpd.setTradeNo(paramMap.get("transaction_id"));
paymentInfoUpd.setCallbackTime(new Date());
paymentInfoUpd.setCallbackContent(paramMap.toString());
this.updatePaymentInfo(out_trade_no, paymentInfoUpd);
//3 根据订单号得到订单信息
OrderInfo orderInfo = orderService.getById(paymentInfo.getOrderId());
//4 修改订单状态
orderInfo.setOrderStatus(OrderStatusEnum.PAID.getStatus());
orderService.updateById(orderInfo);
//5 调用医院接口,通知更新支付状态
}
/**
* 获取支付记录
*/
private PaymentInfo getPaymentInfo(String outTradeNo, Integer paymentType) {
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("out_trade_no", outTradeNo);
queryWrapper.eq("payment_type", paymentType);
return baseMapper.selectOne(queryWrapper);
}
/**
* 更改支付记录
*/
private void updatePaymentInfo(String outTradeNo, PaymentInfo paymentInfoUpd) {
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("out_trade_no", outTradeNo);
baseMapper.update(paymentInfoUpd, queryWrapper);
}
取消预约
退款接口
未支付取消订单,直接通知医院更新取消预约状态
已支付取消订单,先退款给用户,然后通知医院更新取消预约状态
- 根据orderid查询订单
- 要判断是否可以取消,即现在的是否再允许取消的时间范围内
- 远程调用医院的接口,更改订单状态为取消预约,根据hoscode获取医院签名,封装订单参数发送请求
- 如果返回值为200,判断此订单是否已经退过款,即orderInfo的支付状态和枚举类进行比较
- 调用微信的refund方法进行退款
- 更新orderinfo的状态
- 发送mq,更新预约数量,我们与下单成功更新预约数使用相同的mq信息,不设置可预约数与剩余预约数,接收端可预约数加1即可
if(null != orderMqVo.getAvailableNumber()) {
//下单成功更新预约数
Schedule schedule = scheduleService.getById(orderMqVo.getScheduleId());
schedule.setReservedNumber(orderMqVo.getReservedNumber());
schedule.setAvailableNumber(orderMqVo.getAvailableNumber());
//update() : {scheduleRepository.save(schedule);},更新的是mongo中的剩余预约和可预约数量
scheduleService.update(schedule);
}else {
//取消预约更新预约数
Schedule schedule = scheduleService.getById(orderMqVo.getScheduleId());
int availableNumber = schedule.getAvailableNumber().intValue() + 1;
schedule.setAvailableNumber(availableNumber);
scheduleService.update(schedule);
}
- 向用户发送预约取消的短信,短信的内容封装在orderMqVo的MsmVo中,包括就诊人名字,日期等
//取消订单
@Override
public Boolean cancelOrder(Long orderId) {
//获取订单信息
OrderInfo orderInfo = this.getById(orderId);
//判断是否取消 现在时间超过quitTime,就不能退号了
DateTime quitTime = new DateTime(orderInfo.getQuitTime());
if(quitTime.isBeforeNow()) {
throw new YyghException(ResultCodeEnum.CANCEL_ORDER_NO);
}
//调用医院接口取消预约,即医院接口把订单状态改为取消预约
SignInfoVo signInfoVo = hospitalFeignClient.getSignInfoVo(orderInfo.getHoscode());
if(null == signInfoVo) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
Map<String, Object> reqMap = new HashMap<>();
reqMap.put("hoscode",orderInfo.getHoscode());
reqMap.put("hosRecordId",orderInfo.getHosRecordId());
reqMap.put("timestamp", HttpRequestHelper.getTimestamp());
String sign = HttpRequestHelper.getSign(reqMap, signInfoVo.getSignKey());
reqMap.put("sign", sign);
JSONObject result = HttpRequestHelper.sendRequest(reqMap, signInfoVo.getApiUrl()+"/order/updateCancelStatus");
// System.out.println("退款result"+result);
//根据results返回数据
if(result.getInteger("code") != 200) {
throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());
} else {
//是否支付 退款
if(orderInfo.getOrderStatus().intValue() == OrderStatusEnum.PAID.getStatus().intValue()) {
//已支付 退款
boolean isRefund = weixinService.refund(orderId);
if(!isRefund) {
throw new YyghException(ResultCodeEnum.CANCEL_ORDER_FAIL);
}
}
//更改订单状态
orderInfo.setOrderStatus(OrderStatusEnum.CANCLE.getStatus());
baseMapper.updateById(orderInfo);
//发送mq信息更新预约数 我们与下单成功更新预约数使用相同的mq信息,不设置可预约数与剩余预约数,接收端可预约数减1即可
OrderMqVo orderMqVo = new OrderMqVo();
orderMqVo.setScheduleId(orderInfo.getScheduleId());
//短信提示
MsmVo msmVo = new MsmVo();
msmVo.setPhone(orderInfo.getPatientPhone());
String reserveDate = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd") + (orderInfo.getReserveTime()==0 ? "上午": "下午");
Map<String,Object> param = new HashMap<String,Object>(){{
put("title", orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle());
put("reserveDate", reserveDate);
put("name", orderInfo.getPatientName());
}};
msmVo.setParam(param);
orderMqVo.setMsmVo(msmVo);
rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER, MqConst.ROUTING_ORDER, orderMqVo);
}
return true;
}
微信的refund
- 从payment表中获取支付记录信息 paymentInfo
- 添加信息到退款记录表 refundInfo
- 判断当前订单是否已经退款,看Status,已经退款就返回true,否则调用微信的退款接口
- 微信接口实现退款,封装参数
- 调用微信接口 Httpclient
- 设置证书信息
- 接收返回信息
- refundInfo 根据返回的resultMap更新退款记录表(更新时间和退款id)
- refundInfo 状态改为已退款
//微信退款
@Override
public Boolean refund(Long orderId) {
try {
//获取支付记录信息
PaymentInfo paymentInfo = paymentService.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());
//添加信息到退款记录表
RefundInfo refundInfo = refundInfoService.saveRefundInfo(paymentInfo);
//判断当前订单是否已经退款,看Status,已经退款就返回true,否则调用微信的退款接口
if(refundInfo.getRefundStatus().intValue() == RefundStatusEnum.REFUND.getStatus().intValue()) {
return true;
}
//微信接口实现退款
//封装参数
Map<String,String> paramMap = new HashMap<>(8);
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);
client.setCertPassword(ConstantPropertiesUtils.PARTNER);
client.post();
//接受返回数据
String xml = client.getContent();
Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);
System.out.println("退款resultMap"+resultMap);
if (null != resultMap && WXPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("result_code"))) {
//退款成功则更新退款记录表
refundInfo.setCallbackTime(new Date());
refundInfo.setTradeNo(resultMap.get("refund_id"));
//status改成已退款
refundInfo.setRefundStatus(RefundStatusEnum.REFUND.getStatus());
refundInfo.setCallbackContent(JSONObject.toJSONString(resultMap));
refundInfoService.updateById(refundInfo);
return true;
}
return false;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}