微信支付项目四:微信支付笔记
发布日期:2021-05-28 16:44:38 浏览次数:10 分类:技术文章

本文共 14045 字,大约阅读时间需要 46 分钟。

1. 微信支付项目四:微信支付交付

文章目录

1.1. 微信支付交付方式

  1. post方式提交

  2. xml格式的协议

  3. 签名算法MD5

  4. 接口交易单位:分

  5. 交易类型:JSAPI–公众号交付,NATIVE–原生扫码支付,APP–app支付

  6. 交互业务规则:先判断协议字段返回,再判断业务返回,最后判断交易状态。

  7. 商户订单号规则:商户支付的订单号由商户自定义生成,仅支持使用,字母,数字,中划线,下划线,竖线,星号*这些英文半角字符的组合,不能使用汉字或全角等特殊字符,微信支付要求商户订单号保持唯一性。

  8. 安全规则:

  1. 支付模式:

1.2. 互联网架构知识时序图

  1. 时序图:

是一种UML交互图,描述了对象之间传递消息的时间顺序,用来表示用例中的行为顺序,是强调消息时间顺序的交互图。通俗的理解就是交互流程图。

  1. 时序图的四个元素:

对象(Object):生命线(LifeLine),激活(Activetion),消息(Message)

对象:时序图中的对象在交互中扮演的角色就是对象,使用矩形将对象名称包含起来,名称下有下划线。

生命线:生命线是一条垂直的虚线,这条虚线表示对象的存在,在时序图中,每个对象都有生命线。

激活:代表时序图中对象执行一项操作的时期,表示该对象被占用以完成某个任务,当对象处于激活时期,生命线可以扩宽为矩形。

消息:对象之间的交互是通过相互发消息来实现的,消息交互中实线表示请求消息,虚线表示响应返回消息。自己调用自己的方法:反身消息。

1.3. 微信支付模式二的时序图

  1. 微信支付模式二的时序图

微信支付时序图

  1. 微信支付官方文档

  1. 统一下单接口介绍:

商户系统先调用该接口在微信支付后台生成预支付交易单,返回正确的预支付交易会话标识后,再按扫码,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}") List
findMyOrderList(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生成订单接口测试

  1. 添加主要包含两个注解

@RunWith(SpringRunner.class)

@SpringBootTest

  1. 单元测试的用途

有些service层,Mapper层无法像Controller层那样可以用postmen测试的,单元测试的作用就是用来测试mapper或者一些实现类的。

  1. IDE自动生成单元测试

Ide生成单元测试

  1. 测试代码
@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. 创建微信签名等工具类

  1. 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; }}
  1. 微信支付中xml转Map和map转Xml工具类
public class WXPayUtil {
/** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map * @throws Exception */ public static Map
xmlToMap(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; }}
  1. 微信扫码支付业务逻辑

用户选择商品—》生成订单-----》第三方服务器获取订单,商户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 {
//生成签名 SortedMap
params=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 "";}
  1. 签名算法
/**     * 生成微信支付sign     * @param params     * @param key     * @return     */public static String createSign(SortedMap
params,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. 使用谷歌二维码工具生成二维码

  1. 参考资料

微信支付平台,拿到参数后会返回给服务器一个code_url,第三方服务器会通过代码将codeUrl连接生成一个支付二维码。

if(codeUrl==null){
throw new NullPointerException();}try{
//生成二维码 Map
hints=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. 微信支付回调功能

  1. 服务器获取到code_url后,第三方平台获取,并将连接生成二维码,然后用户扫码支付,支付确认后,微信将回调第三方平台第一次发出请求时所包含的回调连接。用于通知第三方服务平台表示用户已经支付了。
  2. 配置类
#微信商户平台wxpay.mer_id=0582sssssssssssss36wxpay.key=XL45y60225PVh1sssssss9m7LFH0Ygq02vrwxpay.callback=http://16sssssssssssssssssssest.ngrok2.xiaomiqiu.cn/api/v1/wechat/order/callback
  1. 回调接口
@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(); Map
callbackMap= WXPayUtil.xmlToMap(sb.toString()); System.out.println(callbackMap.toString());}

1.9. 微信回调处理以及更新订单状态,和幂等性问题

  1. 微信回调通知规则:

通知频率:15/15/30/180/1800/1800/1800/1800/3600 单位:秒

  1. 幂等性:

同样的参数和值,不管调用你的接口多少次,响应结果都和调用一次是一样的。

这个问题主要出现在优惠券,积分,等一些电商领域。

  1. 逻辑

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(); Map
callbackMap= 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. 微信支付下单的事务处理

  1. 事务的使用场景

当一个类里面需要执行多个插入数据库操作,执行多个更新数据库,删除数据库数据操作时,那就要采用事务,也就是当执行多个操作时,一旦一个执行不成功,就要回退过去。

  1. 使用事务步骤

springBoot开启事务,启动类里面添加@EnableTransactionManagement

需要事务的方法上加:@Transactional(propagation = Propagation.REQUIRED)

还有一种aop方式的事务处理,这种不是太好,导致性能降低。

转载地址:https://blog.csdn.net/qq_33322074/article/details/104722918 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:微信支付项目六:全局异常处理和Logback日志整合
下一篇:微信支付项目笔记二:mybatis分页插件的使用,mybatis动态sql语句,JWT的使用

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年02月23日 08时11分16秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章