本文共 14045 字,大约阅读时间需要 46 分钟。
1. 微信支付项目四:微信支付交付
文章目录
1.1. 微信支付交付方式
-
post方式提交
-
xml格式的协议
-
签名算法MD5
-
接口交易单位:分
-
交易类型:JSAPI–公众号交付,NATIVE–原生扫码支付,APP–app支付
-
交互业务规则:先判断协议字段返回,再判断业务返回,最后判断交易状态。
-
商户订单号规则:商户支付的订单号由商户自定义生成,仅支持使用,字母,数字,中划线,下划线,竖线,星号*这些英文半角字符的组合,不能使用汉字或全角等特殊字符,微信支付要求商户订单号保持唯一性。
-
安全规则:
- 支付模式:
1.2. 互联网架构知识时序图
- 时序图:
是一种UML交互图,描述了对象之间传递消息的时间顺序,用来表示用例中的行为顺序,是强调消息时间顺序的交互图。通俗的理解就是交互流程图。
- 时序图的四个元素:
对象(Object):生命线(LifeLine),激活(Activetion),消息(Message)
对象:时序图中的对象在交互中扮演的角色就是对象,使用矩形将对象名称包含起来,名称下有下划线。
生命线:生命线是一条垂直的虚线,这条虚线表示对象的存在,在时序图中,每个对象都有生命线。
激活:代表时序图中对象执行一项操作的时期,表示该对象被占用以完成某个任务,当对象处于激活时期,生命线可以扩宽为矩形。
消息:对象之间的交互是通过相互发消息来实现的,消息交互中实线表示请求消息,虚线表示响应返回消息。自己调用自己的方法:反身消息。
1.3. 微信支付模式二的时序图
- 微信支付模式二的时序图
- 微信支付官方文档
- 统一下单接口介绍:
商户系统先调用该接口在微信支付后台生成预支付交易单,返回正确的预支付交易会话标识后,再按扫码,JSAPI、APP等不同场景生成交易串调起支付。
统一下单就是调用微信支付平台的一个接口,然后传几个变量值。
1.4. 微信支付订单接口之增删改查操作
public interface VideoOrderMapper { /** * 保存订单,返回包含主键 * @param videoOrder * @return */ @Insert("INSERT INTO `xdclass`.`video_order`(`openid`, `out_trade_no`, `state`, `create_time`, `notify_time`, `total_fee`," + " `nickname`, `head_img`, `video_id`, `video_title`, `video_img`, `user_id`, `ip`, `del`)" + " VALUES (#{openid},#{outTradeNo},#{state},#{createTime},#{notifyTime},#{totalFee},#{nickname},#{headImg},#{videoId},#{videoTitle},#{videoImg},#{userId},#{ip},#{del});") @Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id") int insert(VideoOrder videoOrder); /** * 根据主键id查询订单记录 * @param id * @return */ @Select("select * from video_order where id=#{order_id}") VideoOrder findById(@Param("order_id") int id); /** * 根据交易订单号获取订单对象 * @param outTradeNo * @return */ @Select("select * from video_order where out_trade_no=#{out_trade_no} and del=0") VideoOrder findByOutTradeNo(@Param("out_trade_no")String outTradeNo); /** * 逻辑删除订单,并非物理删除 * @param id * @param userId * @return */ @Update("update video_order set del=0 where id=#{id} and user_id=#{userId}") int del(@Param("id") int id,@Param("userId") int userId); /** * 查找我的全部订单 * @param userId * @return */ @Select("select * from video_order where user_id=#{userId}") ListfindMyOrderList(int userId); /** * 根据订单流水号更新 * @param videoOrder * @return */ @Update("update video_order set state=#{state},notify_time=#{notifyTime},openId=#{openid} " + "where out_trade_no=#{outTradeNo} and state=0 and del=0") int updateVideoOrderByOutTradeNo(VideoOrder videoOrder);}
1.5. IDE生成订单接口测试
- 添加主要包含两个注解
@RunWith(SpringRunner.class)
@SpringBootTest
- 单元测试的用途
有些service层,Mapper层无法像Controller层那样可以用postmen测试的,单元测试的作用就是用来测试mapper或者一些实现类的。
- IDE自动生成单元测试
- 测试代码
@RunWith(SpringRunner.class)@SpringBootTestpublic class VideoOrderMapperTest { @Autowired private VideoOrderMapper videoOrderMapper; @Test public void insert() { VideoOrder videoOrder=new VideoOrder(); videoOrder.setDel(0); videoOrder.setTotalFee(1111); videoOrder.setHeadImg("dfjdsfidsl"); videoOrder.setVideoTitle("我是好人"); videoOrderMapper.insert(videoOrder); assertNotNull(videoOrder.getId()); } @Test public void findById() { VideoOrder videoOrder = videoOrderMapper.findById(1); assertNotNull(videoOrder); } @Test public void findByOutTradeNo() { } @Test public void del() { } @Test public void findMyOrderList() { } @Test public void updateVideoOrderByOutTradeNo() { }}
1.6. 创建微信签名等工具类
- uuid生成工具类和MD5工具类
public class CommonUtils { /** * 生成uuid * @return */ public static String generateUUID(){ String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); return uuid; } /** * md5常用工具类 * @param data * @return */ public static String MD5(String data){ /** * jdk中的md5算法实例 */ try{ //获取摘要 MessageDigest md5 = MessageDigest.getInstance("MD5"); //进行加密,生成字节数组 byte[] array = md5.digest(data.getBytes("UTF-8")); StringBuilder sb=new StringBuilder(); for(byte item:array){ //转成16进制 sb.append(Integer.toHexString((item & 0xFF)|0x100).substring(1,3)); } return sb.toString().toUpperCase(); }catch (Exception e){ e.printStackTrace(); } return null; }}
- 微信支付中xml转Map和map转Xml工具类
public class WXPayUtil { /** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map * @throws Exception */ public static MapxmlToMap(String strXML) throws Exception { try { Map data = new HashMap (); DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } try { stream.close(); } catch (Exception ex) { // do nothing } return data; } catch (Exception ex) { WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML); throw ex; } } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @throws Exception */ public static String mapToXml(Map data) throws Exception { org.w3c.dom.Document document = WXPayXmlUtil.newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (String key: data.keySet()) { String value = data.get(key); if (value == null) { value = ""; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); try { writer.close(); } catch (Exception ex) { } return output; }}
- 微信扫码支付业务逻辑
用户选择商品—》生成订单-----》第三方服务器获取订单,商户id,公众平台id,以及其他订单信息-----》生成签名,--------》并转成xml格式
public VideoOrder save(VideoOrderDto videoOrderDto) throws Exception { //查找视频信息 Video video = videoMapper.findById(videoOrderDto.getVideoId()); //查找用户信息 User user = userMapper.findByid(videoOrderDto.getUserId()); //生成订单 VideoOrder videoOrder=new VideoOrder(); videoOrder.setTotalFee(video.getPrice()); videoOrder.setVideoImg(video.getCoverImg()); videoOrder.setVideoTitle(video.getTitle()); videoOrder.setCreateTime(new Date()); videoOrder.setState(0); videoOrder.setUserId(user.getId()); videoOrder.setHeadImg(user.getHeadImg()); videoOrder.setNickname(user.getName()); videoOrder.setDel(0); videoOrder.setIp(videoOrderDto.getIp()); videoOrder.setOutTradeNo(CommonUtils.generateUUID()); videoOrderMapper.insert(videoOrder); //生成签名 unifiedOrder(videoOrder); //统一下单 //获取codeUrl //生成二维码 return null;}private String unifiedOrder(VideoOrder videoOrder) throws Exception { //生成签名 SortedMapparams=new TreeMap<>(); params.put("appid",weChatConfig.getAppId()); params.put("mch_id",weChatConfig.getMchId()); params.put("nonce_str",CommonUtils.generateUUID()); params.put("body",videoOrder.getVideoTitle()); params.put("out_trade_no",videoOrder.getOutTradeNo()); params.put("total_fee",videoOrder.getTotalFee().toString()); params.put("spbill_create_ip",videoOrder.getIp()); params.put("notify_url",weChatConfig.getPayCallbackUrl()); params.put("trade_type","NATIVE"); //sign签名 String sign = WXPayUtil.createSign(params, weChatConfig.getKey()); params.put("sign",sign); String payXml = WXPayUtil.mapToXml(params); System.out.println(payXml); //统一下单 return "";}
- 签名算法
/** * 生成微信支付sign * @param params * @param key * @return */public static String createSign(SortedMapparams,String key) { /** * SortedMap默认就是字典排序 */ StringBuilder sb=new StringBuilder(); Set > es = params.entrySet(); Iterator > it = es.iterator(); //生成 stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"; while(it.hasNext()){ Map.Entry entry = (Map.Entry )it.next(); String k=(String)entry.getKey(); String v=(String)entry.getValue(); if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){ sb.append(k+"="+v+"&"); } } sb.append("key=").append(key); //String sign = CommonUtils.MD5(sb.toString().toUpperCase()); String sign = CommonUtils.MD5(sb.toString()).toUpperCase(); return sign;}
1.7. 使用谷歌二维码工具生成二维码
- 参考资料
微信支付平台,拿到参数后会返回给服务器一个code_url,第三方服务器会通过代码将codeUrl连接生成一个支付二维码。
if(codeUrl==null){ throw new NullPointerException();}try{ //生成二维码 Maphints=new HashMap<>(); //设置纠错等级 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); //设置编码类型 hints.put(EncodeHintType.CHARACTER_SET,"UTF-8"); //生成一个度量,可以写入到浏览器中生成二维码 BitMatrix bitMatrix=new MultiFormatWriter().encode(codeUrl, BarcodeFormat.QR_CODE,400,400,hints); //获取一个输出流 OutputStream out = response.getOutputStream(); //通过MatrixToImageWriter生成一个二维码写出到浏览器。 MatrixToImageWriter.writeToStream(bitMatrix,"png",out);}catch(Exception e){ e.printStackTrace();}
1.8. 微信支付回调功能
- 服务器获取到code_url后,第三方平台获取,并将连接生成二维码,然后用户扫码支付,支付确认后,微信将回调第三方平台第一次发出请求时所包含的回调连接。用于通知第三方服务平台表示用户已经支付了。
- 配置类
#微信商户平台wxpay.mer_id=0582sssssssssssss36wxpay.key=XL45y60225PVh1sssssss9m7LFH0Ygq02vrwxpay.callback=http://16sssssssssssssssssssest.ngrok2.xiaomiqiu.cn/api/v1/wechat/order/callback
- 回调接口
@RequestMapping("/order/callback")public void orderCallback(HttpServletRequest request,HttpServletResponse response) throws Exception { ServletInputStream inputStream = request.getInputStream(); //BufferedReader是一个包装设计模式,性能更高。 BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8") ); StringBuffer sb=new StringBuffer(); String line; while((line=in.readLine())!=null){ sb.append(line); } in.close(); inputStream.close(); MapcallbackMap= WXPayUtil.xmlToMap(sb.toString()); System.out.println(callbackMap.toString());}
1.9. 微信回调处理以及更新订单状态,和幂等性问题
- 微信回调通知规则:
通知频率:15/15/30/180/1800/1800/1800/1800/3600 单位:秒
- 幂等性:
同样的参数和值,不管调用你的接口多少次,响应结果都和调用一次是一样的。
这个问题主要出现在优惠券,积分,等一些电商领域。
- 逻辑
1、用户确认后,微信平台经过处理,会调用第三方服务器的接口,通知商户。
2、微信返回的值也是一个xml格式的,我们将xml转成map集合
3、再将map集合转成sortedMap集合,然后验证返回的签名是否正确。
4、如果正确就要更新订单状态,
5、更新成功后就返回微信支付表示,支付已经成功。
6、如果返回失败,就返回微信支付失败。
@RequestMapping("/order/callback")public void orderCallback(HttpServletRequest request,HttpServletResponse response) throws Exception { ServletInputStream inputStream = request.getInputStream(); //BufferedReader是一个包装设计模式,性能更高。 BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8") ); StringBuffer sb=new StringBuffer(); String line; while((line=in.readLine())!=null){ sb.append(line); } in.close(); inputStream.close(); MapcallbackMap= WXPayUtil.xmlToMap(sb.toString()); System.out.println(callbackMap.toString()); //将获取得值转换为sortedmap SortedMap sortedMap = WXPayUtil.getSortedMap(callbackMap); //检验签名是否正确 if(WXPayUtil.isCorrentSign(sortedMap,weChatConfig.getKey())){ //System.out.println("ok"); if("SUCCESS".equals(sortedMap.get("result_code"))){ String outTradeNo=sortedMap.get("out_trade_no"); /** * 可以在此处加一个缓存,不从数据库里直接获取数据。最佳的是通过队列操作。 */ VideoOrder dbVideoOrder = videoOrderService.findByOutTradeNo(outTradeNo); if(dbVideoOrder!=null && dbVideoOrder.getState()==0){ //判断逻辑看业务场景 VideoOrder videoOrder=new VideoOrder(); videoOrder.setOpenid(sortedMap.get("openid")); videoOrder.setOutTradeNo(outTradeNo); videoOrder.setNotifyTime(new Date()); videoOrder.setState(1); int rows = videoOrderService.updateVideoOrderByOutTradeNo(videoOrder); if(rows==1){ //通知微信订单处理成功 response.setContentType("text/html"); response.getWriter().println("success"); return; } } } } //处理失败 response.setContentType("text/html"); response.getWriter().println("fail");
1.10. 微信支付下单的事务处理
- 事务的使用场景
当一个类里面需要执行多个插入数据库操作,执行多个更新数据库,删除数据库数据操作时,那就要采用事务,也就是当执行多个操作时,一旦一个执行不成功,就要回退过去。
- 使用事务步骤
springBoot开启事务,启动类里面添加@EnableTransactionManagement
需要事务的方法上加:@Transactional(propagation = Propagation.REQUIRED)
还有一种aop方式的事务处理,这种不是太好,导致性能降低。
转载地址:https://blog.csdn.net/qq_33322074/article/details/104722918 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!