目录

  • 一、微信支付介绍
  • 二、微信支付开发
    • 1. api 接口
    • 2. 前端
    • 3. 处理支付结果
  • 三、取消预约
    • 1. 需求描述
    • 2. 开发微信退款接口
    • 3. 前端

一、微信支付介绍

A、微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

申请步骤 :(了解)

第一步 :注册公众号 (类型须为:服务号)

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型。

第二步 :认证公众号

公众号认证后才可申请微信支付,认证费:300元/年。

第三步:提交资料申请微信支付

登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:在线签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

B、开发文档

微信支付接口调用的整体思路:

按 API 要求组装参数,以 XML 方式发送 (POST) 给微信支付接(口URL), 微信支付接口也是以 XML 方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

在线微信支付开发文档:
https://pay.weixin.qq.com/wiki/doc/api/index.html

  • appid:微信公众账号或开放平台APP的唯一标识
  • mch_id:商户号 (配置文件中的partner)
  • partnerkey:商户密钥
  • sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

C、微信支付 SDK

添加依赖

<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 接口

场景:用户扫描商户展示在各种场景的二维码进行支付

使用案例:

  • 线下:家乐福超市、7-11便利店、上品折扣线下店等
  • 线上:大众点评网站、携程网站、唯品会、美丽说网站等

开发模式:

  • 模式一:商户在后台给你生成二维码,用户打开扫一扫
  • 模式二:商户后台系统调用微信支付【统一下单API】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。

注意:该模式的预付单有效期为2小时,过期后无法支付。

微信支付:生成 xml 发送请求
操作模块:service-order

A、引入依赖

<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</version>
</dependency>

B、添加配置

在 application.properties 中添加商户信息

spring.redis.host=192.168.44.165
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
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#关联的公众号appid
weixin.pay.appid=wx74862e0dfcf69954
#商户号
weixin.pay.partner=1558950191
#商户key
weixin.pay.partnerkey=T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

C、引入工具类

@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;@Overridepublic void afterPropertiesSet() throws Exception {APPID = appid;PARTNER = partner;PARTNERKEY = partnerkey;}
}
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;/*** 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("?");elseurl.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) {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;}
}

D、添加交易记录接口

① 添加 Mapper

public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {}

② 添加 service 接口与实现

添加 PaymentService 类

public interface PaymentService extends IService<PaymentInfo> {/*** 保存交易记录* @param order* @param paymentType 支付类型(1:微信 2:支付宝)*/void savePaymentInfo(OrderInfo order, Integer paymentType);}

添加 PaymentServiceImpl 实现类

@Service
public class PaymentServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo>  implements PaymentService {/*** 保存交易记录* @param orderInfo* @param paymentType 支付类型(1:微信 2:支付宝)*/@Overridepublic void savePaymentInfo(OrderInfo orderInfo, Integer paymentType) {QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_id", orderInfo.getId());queryWrapper.eq("payment_type", paymentType);Integer count = baseMapper.selectCount(queryWrapper);if(count >0) return;// 保存交易记录PaymentInfo paymentInfo = new PaymentInfo();paymentInfo.setCreateTime(new Date());paymentInfo.setOrderId(orderInfo.getId());paymentInfo.setPaymentType(paymentType);paymentInfo.setOutTradeNo(orderInfo.getOutTradeNo());paymentInfo.setPaymentStatus(PaymentStatusEnum.UNPAID.getStatus());String subject = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd")+"|"+orderInfo.getHosname()+"|"+orderInfo.getDepname()+"|"+orderInfo.getTitle();paymentInfo.setSubject(subject);paymentInfo.setTotalAmount(orderInfo.getAmount());baseMapper.insert(paymentInfo);}
}

E、添加支付 service 接口与实现

添加 com.fancy.yygh.order.service.WeixinService 类

public interface WeixinService {/*** 根据订单号下单,生成支付链接*/Map createNative(Long orderId);
}

添加 com.fancy.yygh.order.service.impl.WeixinServiceImpl 类

@Service
public class WeixinServiceImpl implements WeixinService {@Autowiredprivate OrderService orderService;@Autowiredprivate PaymentService paymentService;@Autowiredprivate RedisTemplate redisTemplate;/*** 根据订单号下单,生成支付链接*/@Overridepublic Map createNative(Long orderId) {try {Map payMap = (Map) redisTemplate.opsForValue().get(orderId.toString());if(null != payMap) return payMap;//根据id获取订单信息OrderInfo order = orderService.getById(orderId);// 保存交易记录paymentService.savePaymentInfo(order, PaymentTypeEnum.WEIXIN.getStatus());//1、设置参数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");//2、HTTPClient来根据URL访问第三方接口并且传递参数HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");//client设置参数client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantPropertiesUtils.PARTNERKEY));client.setHttps(true);client.post();//3、返回第三方的数据String xml = client.getContent();Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);//4、封装返回结果集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, 1000, TimeUnit.MINUTES);}return map;} catch (Exception e) {e.printStackTrace();return new HashMap<>();}}
}

F、添加 controller 方法

@RestController
@RequestMapping("/api/order/weixin")
public class WeixinController {@Autowiredprivate WeixinService weixinPayService;/*** 下单 生成二维码*/@GetMapping("/createNative/{orderId}")public Result createNative(@ApiParam(name = "orderId", value = "订单id", required = true)@PathVariable("orderId") Long orderId) {return Result.ok(weixinPayService.createNative(orderId));}
}

2. 前端

A、封装 api 请求

添加 /api/order/weixin.js 文件

createNative(orderId) {return request({url: `/api/order/weixin/createNative/${orderId}`,method: 'get'})
},

B、引入 vue-qriously 生成二维码

安装vue-qriously

npm install vue-qriously

2、引入

在 /plugins/myPlugin.js 文件添加引入

import VueQriously from 'vue-qriously'
Vue.use(VueQriously)

C、页面展示

修改 /pages/order/show.vue 组件

<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="//img.114yygh.com/static/web/code_order_detail.png" class="code-img"><div class="content-wrapper"><div> 微信<span class="iconfont"></span>关注“预约挂号”</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: 350px;"><div class="wrapper wechat"><div><qriously :value="payObj.codeUrl" :size="220"/><div style="text-align: center;line-height: 25px;margin-bottom: 40px;">请使用微信扫一扫<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/weixin'
export default {data() {return {orderId: null,orderInfo: {param: {}},dialogPayVisible: false,payObj: {},timer: null  // 定时器名称}},created() {this.orderId = this.$route.query.orderIdthis.init()},methods: {init() {orderInfoApi.getOrders(this.orderId).then(response => {console.log(response.data);this.orderInfo = response.data})},pay() {this.dialogPayVisible = trueweixinApi.createNative(this.orderId).then(response => {this.payObj = response.dataif(this.payObj.codeUrl == '') {this.dialogPayVisible = falsethis.$message.error("支付错误")} else {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()})},closeDialog() {if(this.timer) {clearInterval(this.timer);}}}
}
</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>

说明:我们只有轮询查看支付状态,接下来我们处理支付查询接口

D、添加查询 api 请求

在 /api/weixin.js 文件添加方法

queryPayStatus(orderId) {return request({url: `/api/order/weixin/queryPayStatus/${orderId}`,method: 'get'})
},

3. 处理支付结果

A、支付查询

① 添加 service 接口与实现

在 WeixinService 类添加接口

/*** 根据订单号去微信第三方查询支付状态*/
Map queryPayStatus(Long orderId, String paymentType);

在WeixinServiceImpl类添加实现

@Override
public Map queryPayStatus(Long orderId, String paymentType) {try {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、返回第三方的数据,转成MapString xml = client.getContent();Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);//4、返回return resultMap;} catch (Exception e) {return null;}
}

② 添加 controller 方法

在 WeixinController 类添加方法

@Autowired
private PaymentService paymentService;
@ApiOperation(value = "查询支付状态")
@GetMapping("/queryPayStatus/{orderId}")
public Result queryPayStatus(@ApiParam(name = "orderId", value = "订单id", required = true)@PathVariable("orderId") Long orderId) {//调用查询接口Map<String, String> resultMap = weixinPayService.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("支付中");
}

说明:如果支付成功,我们要更新支付状态、通知医院该预约单已经支付成功

B、处理支付成功逻辑

① 添加 service 接口与实现

在 PaymentService 类添加接口

/*** 支付成功*/
void paySuccess(String outTradeNo, Integer paymentType, Map<String, String> paramMap);

在 PaymentServiceImpl 类添加实现

/*** 支付成功*/
@Override
public void paySuccess(String outTradeNo,Integer paymentType, Map<String,String> paramMap) {PaymentInfo paymentInfo = this.getPaymentInfo(outTradeNo, paymentType);if (null == paymentInfo) {throw new YyghException(ResultCodeEnum.PARAM_ERROR);}if (paymentInfo.getPaymentStatus() != PaymentStatusEnum.UNPAID.getStatus()) {return;}//修改支付状态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(outTradeNo, paymentInfoUpd);//修改订单状态OrderInfo orderInfo = orderService.getById(paymentInfo.getOrderId());orderInfo.setOrderStatus(OrderStatusEnum.PAID.getStatus());orderService.updateById(orderInfo);// 调用医院接口,通知更新支付状态
}
/*** 获取支付记录*/
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);
}

② 更新医院支付状态

参考《尚医通API接口文档.docx》业务接口

C、更新支付状态

/*** 支付成功*/
@Override
public void paySuccess(String outTradeNo,Integer paymentType, Map<String,String> paramMap) {PaymentInfo paymentInfo = this.getPaymentInfo(outTradeNo, paymentType);if (null == paymentInfo) {throw new YyghException(ResultCodeEnum.PARAM_ERROR);}if (paymentInfo.getPaymentStatus() != PaymentStatusEnum.UNPAID.getStatus()) {return;}//修改支付状态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(outTradeNo, paymentInfoUpd);//修改订单状态OrderInfo orderInfo = orderService.getById(paymentInfo.getOrderId());orderInfo.setOrderStatus(OrderStatusEnum.PAID.getStatus());orderService.updateById(orderInfo);// 调用医院接口,通知更新支付状态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/updatePayStatus");if(result.getInteger("code") != 200) {throw new YyghException(result.getString("message"), ResultCodeEnum.FAIL.getCode());}
}

三、取消预约

1. 需求描述

取消订单分两种情况:

  • 未支付取消订单,直接通知医院更新取消预约状态
  • 已支付取消订单,先退款给用户,然后通知医院更新取消预约状态

2. 开发微信退款接口

参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4该接口需要使用证书,详情参考文档并下载证书

A、配置证书

请下载的证书放在s ervice-order 模块 /resources/cert 文件夹下,在 application.properties 文件配置证书路径

weixin.cert= C:\\yygh_parent\\service\\service_order\\src\\main\\resources\\cert\\apiclient_cert.p12

B、添加获取支付记录接口

退款我们是根据支付记录发起退款的

在PaymentService类添加接口

/*** 获取支付记录* @param orderId
* @param paymentType
* @return
*/
PaymentInfo getPaymentInfo(Long orderId, Integer paymentType);

在 PaymentServiceImpl 类添加实现

@Override
public PaymentInfo getPaymentInfo(Long orderId, Integer paymentType) {QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_id", orderId);queryWrapper.eq("payment_type", paymentType);return baseMapper.selectOne(queryWrapper);
}

C、添加退款记录

① 添加 mapper

public interface RefundInfoMapper extends BaseMapper<RefundInfo> {}

② 添加 service 接口与实现

添加 service 接口

public interface RefundInfoService extends IService<RefundInfo> {/*** 保存退款记录* @param paymentInfo*/RefundInfo saveRefundInfo(PaymentInfo paymentInfo);
}

添加 service 接口实现

@Service
public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundInfo> implements RefundInfoService {@Autowiredprivate RefundInfoMapper refundInfoMapper;@Overridepublic RefundInfo saveRefundInfo(PaymentInfo paymentInfo) {QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_id", paymentInfo.getOrderId());queryWrapper.eq("payment_type", paymentInfo.getPaymentType());RefundInfo refundInfo = refundInfoMapper.selectOne(queryWrapper);if(null != refundInfo) return refundInfo;// 保存交易记录refundInfo = new RefundInfo();refundInfo.setCreateTime(new Date());refundInfo.setOrderId(paymentInfo.getOrderId());refundInfo.setPaymentType(paymentInfo.getPaymentType());refundInfo.setOutTradeNo(paymentInfo.getOutTradeNo());refundInfo.setRefundStatus(RefundStatusEnum.UNREFUND.getStatus());refundInfo.setSubject(paymentInfo.getSubject());//paymentInfo.setSubject("test");refundInfo.setTotalAmount(paymentInfo.getTotalAmount());refundInfoMapper.insert(refundInfo);return refundInfo;}
}

D、添加微信退款接口

在 WeixinService 添加接口

/**** 退款* @param orderId* @return*/
Boolean refund(Long orderId);

在 WeixinServiceImpl 添加实现

@Override
public Boolean refund(Long orderId) {try {PaymentInfo paymentInfoQuery = paymentService.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());RefundInfo refundInfo = refundInfoService.saveRefundInfo(paymentInfoQuery);if(refundInfo.getRefundStatus().intValue() == RefundStatusEnum.REFUND.getStatus().intValue()) {return true;}Map<String,String> paramMap = new HashMap<>(8);paramMap.put("appid",ConstantPropertiesUtils.APPID);       //公众账号IDparamMap.put("mch_id",ConstantPropertiesUtils.PARTNER);   //商户编号paramMap.put("nonce_str",WXPayUtil.generateNonceStr());paramMap.put("transaction_id",paymentInfoQuery.getTradeNo()); //微信订单号paramMap.put("out_trade_no",paymentInfoQuery.getOutTradeNo()); //商户订单编号paramMap.put("out_refund_no","tk"+paymentInfoQuery.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();//3、返回第三方的数据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 false;
}

E、完成取消预约

参考《尚医通API接口文档.docx》业务接口:取消预约

① 添加service接口与实现

在 OrderService 添加接口

/*** 取消订单* @param orderId*/
Boolean cancelOrder(Long orderId);

在 OrderServiceImpl 添加实现

@Override
public Boolean cancelOrder(Long orderId) {OrderInfo orderInfo = this.getById(orderId);//当前时间大约退号时间,不能取消预约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");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());this.updateById(orderInfo);//发送mq信息更新预约数 我们与下单成功更新预约数使用相同的mq信息,不设置可预约数与剩余预约数,接收端可预约数减1即可OrderMqVo orderMqVo = new OrderMqVo();orderMqVo.setScheduleId(orderInfo.getScheduleId());//短信提示MsmVo msmVo = new MsmVo();msmVo.setPhone(orderInfo.getPatientPhone());msmVo.setTemplateCode("SMS_194640722");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;
}

② 添加 controller 方法

在 OrderApiController 添加方法

@ApiOperation(value = "取消预约")
@GetMapping("auth/cancelOrder/{orderId}")
public Result cancelOrder(@ApiParam(name = "orderId", value = "订单id", required = true)@PathVariable("orderId") Long orderId) {return Result.ok(orderService.cancelOrder(orderId));
}

F、修改监听

操作:service-hosp模块

修改 HospitalReceiver 类

@RabbitListener(bindings = @QueueBinding(value = @Queue(value = MqConst.QUEUE_ORDER, durable = "true"),exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_ORDER),key = {MqConst.ROUTING_ORDER}
))
public void receiver(OrderMqVo orderMqVo, Message message, Channel channel) throws IOException {if(null != orderMqVo.getAvailableNumber()) {//下单成功更新预约数Schedule schedule = scheduleService.getScheduleId(orderMqVo.getScheduleId());schedule.setReservedNumber(orderMqVo.getReservedNumber());schedule.setAvailableNumber(orderMqVo.getAvailableNumber());scheduleService.update(schedule);} else {//取消预约更新预约数Schedule schedule = scheduleService.getScheduleId(orderMqVo.getScheduleId());int availableNumber = schedule.getAvailableNumber().intValue() + 1;schedule.setAvailableNumber(availableNumber);scheduleService.update(schedule);}//发送短信MsmVo msmVo = orderMqVo.getMsmVo();if(null != msmVo) {rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_MSM, MqConst.ROUTING_MSM_ITEM, msmVo);}
}

3. 前端

A、封装 api 请求

添加 /api/weixin.js 文件

cancelOrder(orderId) {return request({url: `/api/order/orderInfo/auth/cancelOrder/${orderId}`,method: 'get'})
},

B、页面展示

修改 /pages/order/show.vue 组件

cancelOrder() {this.$confirm('确定取消预约吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => { // promise// 点击确定,远程调用return weixinApi.cancelOrder(this.orderId)}).then((response) => {this.$message.success('取消成功')this.init()}).catch(() => {this.$message.info('已取消取消预约')})
}

尚医通 (三十六) --------- 微信支付相关推荐

  1. 尚医通 (三十五) --------- 预约下单

    目录 一.预约下单前端 1. 封装 api 请求 2. 页面修改 二.后端逻辑 1. 需求分析 2. 搭建 service-order 模块 3. 添加订单基础类 4. 封装 Feign 调用获取就诊 ...

  2. 尚医通项目124-149:微信登录以及阿里云OSS的使用

    开始时间:2022-06-17 课程链接:尚医通项目 微信登录 OAuth2 开放系统间授权 照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源 方式一:用户名密码复制:将受保 ...

  3. 尚医通 (三十一) --------- 手机登录

    目录 一.登录需求 1. 登录效果 2. 登录需求 二.登录 1. 搭建 service-user 模块 2. 添加用户基础类 3. 登录 API 接口 4. 生成 token 5. 阿里云短信 6. ...

  4. 尚硅谷在线教育十四:微信支付

    文章目录 1. 前期准备工作 1.1 创建订单相关的表 2.2 创建service_order模块 2. 微信支付后端相关的接口 2.1 生成订单的接口 2.2 根据订单id查询订单信息 2.3 微信 ...

  5. 尚医通 (十八)微信登录

    目录 一.生成微信登录二维码 1.准备工作 2.后端开发service_user 3.前端显示登录二维码 4.二维码出现不了进行调试 二.开发微信扫描回调 1.准备工作 2.后台开发 3.前台开发 三 ...

  6. 尚医通(十五)医院排班管理

    目录 一.医院排班管理需求 1.页面效果 2.接口分析 二.科室列表(接口) 1.添加service接口和实现 2.添加DepartmentController方法 三.科室列表(前端) 1.添加隐藏 ...

  7. 尚医通 (十九)用户认证

    目录 一.对象存储OSS 1.开通"对象存储OSS"服务 2.创建Bucket 3.上传默认头像 4.创建RAM用户 5.使用SDK 二.后端集成OSS 1.新建云存储微服务 2. ...

  8. 尚医通-OAuth2-微信登录接口开发(三十一)

    目录: (1)微信登录-OAuth2介绍 (2)前台用户系统-微信登录-准备工作 (3)微信登录-生成微信二维码-接口开发 (4)微信登录-生成验证码-前端整合 (5)微信登录-获取扫码人信息-实现分 ...

  9. 尚医通-阿里云短信服务(二十九)

    目录: (1)前台用户系统-手机登录-阿里云短信服务介绍 (2)手机登录-整合短信服务 (3)整合短信发送服务测试 (1)前台用户系统-手机登录-阿里云短信服务介绍 现在使用阿里云完成短信服务:注册登 ...

最新文章

  1. iOS中 加强日志输出 开发技术总结
  2. TID大会学习心得之敏捷软件架构-微服务
  3. TypeScript里数组foreach和map操作的区别
  4. ETAG is returned by the first read
  5. 牛客网暑期ACM多校训练营(第一场)
  6. block的使用(六)
  7. 境内外赌博网站被捣毁,程序员被抓!!
  8. 【codevs1282】约瑟夫问题
  9. 教你图片批量重命名编号,不要括号
  10. 前端jq实现视频跟图片一起混播
  11. 【转】NAT穿透技术
  12. word论文排版插件_word论文排版自动编号设置
  13. Could not connect to appstore: cURL error 28: Operation timed out after 60000 milliseconds with
  14. 关于几种常用的脱壳方法总结
  15. 笔记本电脑蓝牙设置开关消失不见的处理方法
  16. PostMan接口参数化、接口返回值传递
  17. Excel if函数用法
  18. 【Java源码解析】如何严谨地重写 equals 方法、getClass 方法与 instanceof 关键词用法比较
  19. 远程医疗是指通过计算机技术,E诊断:什么是远程医疗?
  20. Text组件新增内容通过tag_config设置前景色、背景色

热门文章

  1. 7_22_html_美食网设计
  2. 陈丹琦 关系抽取 2020 sota ner
  3. Win10系统无法启动的最终解决方案
  4. 十二星座 谁是 “小笨猪” ?
  5. 什么是dB、dBm、dBc?
  6. C# BLE蓝牙开发之使用Windows.Devices.Bluetooth获取小米体重秤的体重
  7. 易语言 执行java_CallJava易语言调用JAVA代码
  8. 集运转运系统源码,快递物流一件代付系统源码
  9. 互联网思维笔记(一)
  10. 2023广州大米展会