微信支付(可复用)

3.1微信支付

本项目选择小程序支付

参考:产品中心 - 微信支付商户平台微信支付商户平台提供各类支付产品满足商家通过微信支付收款的需求;平台提供智慧经营,现金红包,代金券等运营工具,助力商家更好的玩转营销;资金管理产品满足商户常见资金诉求;安全医生为商家提供全方位安全保障,共建支付安全。微信支付见证每一位商户的经营成长。icon-default.png?t=N7T8https://pay.weixin.qq.com/static/product/product_index.shtml

微信支付接入流程:

微信小程序支付时序图:

微信支付相关接口:

JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)

微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)

3.2 微信支付准备工作

3.2.1 如何保证数据安全?

完成微信支付有两个关键的步骤:

第一个就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。

第二个就是支付成功之后微信后台会给推送消息。

这两个接口数据的安全性,要求其实是非常高的。

解决:微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。

获取微信支付平台证书、商户私钥文件:

在后绪程序开发过程中,就会使用到这两个文件,需要提前把这两个文件准备好。

3.2.2 如何调用到商户系统?

微信后台会调用到商户系统给推送支付的结果,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。

目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。

解决:内网穿透。通过cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。

cpolar软件的使用:

1). 下载与安装

下载地址:cpolar - secure introspectable tunnels to localhostcpolar secure introspectable tunnels to localhost webhook development tool and debugging toolicon-default.png?t=N7T8https://dashboard.cpolar.com/get-started

在资料中已提供,可无需下载。

安装过程中,一直下一步即可,不再演示。

2). cpolar指定authtoken

复制authtoken:

执行命令:

3). 获取临时域名

执行命令:

获取域名:

4). 验证临时域名有效性

访问接口文档

使用localhost:8080访问

使用临时域名访问

证明临时域名生效。

3.3 代码导入

导入资料中的微信支付功能代码即可

3.3.1 微信支付相关配置

application-dev.yml

sky:wechat:appid: wxcd2e39f677fd30basecret: 84fbfdf5ea288f0c432d829599083637mchid : 1561414331mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606privateKeyFilePath: D:\apiclient_key.pemapiV3Key: CZBK51236435wxpay435434323FFDuv3weChatPayCertFilePath: D:\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pemnotifyUrl: https://www.weixin.qq.com/wxpay/pay.phprefundNotifyUrl: https://www.weixin.qq.com/wxpay/pay.php

application.yml

sky:wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}mchid : ${sky.wechat.mchid}mchSerialNo: ${sky.wechat.mchSerialNo}privateKeyFilePath: ${sky.wechat.privateKeyFilePath}apiV3Key: ${sky.wechat.apiV3Key}weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}notifyUrl: ${sky.wechat.notifyUrl}refundNotifyUrl: ${sky.wechat.refundNotifyUrl}
​

WeChatProperties.java:读取配置

package com.sky.properties;
​
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {
​private String appid; //小程序的appidprivate String secret; //小程序的秘钥private String mchid; //商户号private String mchSerialNo; //商户API证书的证书序列号private String privateKeyFilePath; //商户私钥文件private String apiV3Key; //证书解密的密钥private String weChatPayCertFilePath; //平台证书private String notifyUrl; //支付成功的回调地址private String refundNotifyUrl; //退款成功的回调地址
}

3.3.2 Mapper层

在OrderMapper.java中添加getByNumberAndUserId和update两个方法

/*** 根据订单号和用户id查询订单* @param orderNumber* @param userId*/@Select("select * from orders where number = #{orderNumber} and user_id= #{userId}")Orders getByNumberAndUserId(String orderNumber, Long userId);
​/*** 修改订单信息* @param orders*/void update(Orders orders);
在OrderMapper.xml中添加<update id="update" parameterType="com.sky.entity.Orders">update orders<set><if test="cancelReason != null and cancelReason!='' ">cancel_reason=#{cancelReason},</if><if test="rejectionReason != null and rejectionReason!='' ">rejection_reason=#{rejectionReason},</if><if test="cancelTime != null">cancel_time=#{cancelTime},</if><if test="payStatus != null">pay_status=#{payStatus},</if><if test="payMethod != null">pay_method=#{payMethod},</if><if test="checkoutTime != null">checkout_time=#{checkoutTime},</if><if test="status != null">status = #{status},</if><if test="deliveryTime != null">delivery_time = #{deliveryTime}</if></set>where id = #{id}
</update>

3.3.3 Service层

在OrderService.java中添加payment和paySuccess两个方法定义

/*** 订单支付* @param ordersPaymentDTO* @return*/OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;
​/*** 支付成功,修改订单状态* @param outTradeNo*/void paySuccess(String outTradeNo);

在OrderServiceImpl.java中实现payment和paySuccess两个方法

 @Autowiredprivate UserMapper userMapper;@Autowiredprivate WeChatPayUtil weChatPayUtil;/*** 订单支付** @param ordersPaymentDTO* @return*/public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);
​//调用微信支付接口,生成预支付交易单JSONObject jsonObject = weChatPayUtil.pay(ordersPaymentDTO.getOrderNumber(), //商户订单号new BigDecimal(0.01), //支付金额,单位 元"苍穹外卖订单", //商品描述user.getOpenid() //微信用户的openid);
​if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {throw new OrderBusinessException("该订单已支付");}
​OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);vo.setPackageStr(jsonObject.getString("package"));
​return vo;}
​/*** 支付成功,修改订单状态** @param outTradeNo*/public void paySuccess(String outTradeNo) {// 当前登录用户idLong userId = BaseContext.getCurrentId();
​// 根据订单号查询当前用户的订单Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);
​// 根据订单id更新订单的状态、支付方式、支付状态、结账时间Orders orders = Orders.builder().id(ordersDB.getId()).status(Orders.TO_BE_CONFIRMED).payStatus(Orders.PAID).checkoutTime(LocalDateTime.now()).build();
​orderMapper.update(orders);}

3.3.4 Controller层

在OrderController.java中添加payment方法

 /*** 订单支付** @param ordersPaymentDTO* @return*/@PutMapping("/payment")@ApiOperation("订单支付")public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {log.info("订单支付:{}", ordersPaymentDTO);OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);log.info("生成预支付交易单:{}", orderPaymentVO);return Result.success(orderPaymentVO);}
PayNotifyController.javapackage com.sky.controller.notify;
​
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.annotation.IgnoreToken;
import com.sky.properties.WeChatProperties;
import com.sky.service.OrderService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
​
/*** 支付回调相关接口*/
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {@Autowiredprivate OrderService orderService;@Autowiredprivate WeChatProperties weChatProperties;
​/*** 支付成功回调** @param request*/@RequestMapping("/paySuccess")public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {//读取数据String body = readData(request);log.info("支付成功回调:{}", body);
​//数据解密String plainText = decryptData(body);log.info("解密后的文本:{}", plainText);
​JSONObject jsonObject = JSON.parseObject(plainText);String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
​log.info("商户平台订单号:{}", outTradeNo);log.info("微信支付交易号:{}", transactionId);
​//业务处理,修改订单状态、来单提醒orderService.paySuccess(outTradeNo);
​//给微信响应responseToWeixin(response);}
​/*** 读取数据** @param request* @return* @throws Exception*/private String readData(HttpServletRequest request) throws Exception {BufferedReader reader = request.getReader();StringBuilder result = new StringBuilder();String line = null;while ((line = reader.readLine()) != null) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();}
​/*** 数据解密** @param body* @return* @throws Exception*/private String decryptData(String body) throws Exception {JSONObject resultObject = JSON.parseObject(body);JSONObject resource = resultObject.getJSONObject("resource");String ciphertext = resource.getString("ciphertext");String nonce = resource.getString("nonce");String associatedData = resource.getString("associated_data");
​AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));//密文解密String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);
​return plainText;}
​/*** 给微信响应* @param response*/private void responseToWeixin(HttpServletResponse response) throws Exception{response.setStatus(200);HashMap<Object, Object> map = new HashMap<>();map.put("code", "SUCCESS");map.put("message", "SUCCESS");response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));response.flushBuffer();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/21698.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FCA-数知鸟 试题及答案

第1题【判断题】文档不可以设置层级关系 A. 正确B. 错误 正确答案&#xff1a;B 第2题【判断题】文档批量创建需求功能可以一次性创建不同类型的需求 A. 正确B. 错误 正确答案&#xff1a;B 第3题【判断题】甘特图中可以根据实际需要添加表格的显示字段&#xff0c;不可添加甘…

程序员应该有什么职业素养?

程序员的六大职业素养&#xff1a;构建成功职业生涯的基石 在不断变化的技术世界中&#xff0c;程序员不单要保持技术的锋利&#xff0c;也需要培养相应的职业素养&#xff0c;这些素养在很大程度上决定了一个程序员的职业生涯能否走得长远。以下是我认为最为重要的六大职业素…

C++ 20新特性之Concepts

C20的新特性之一Concepts&#xff0c;为C的模板编程带来了重大的改进和便利。以下是关于C20中Concepts特性的详细介绍&#xff1a; 基本概念 定义&#xff1a;Concepts是C20中引入的一种新的语言特性&#xff0c;用于限制类和函数模板的模板类型和非类型参数。它允许你为模板编…

LLM基础知识

LLM背景知识介绍 1 大语言模型 (LLM) 背景 大语言模型 (英文&#xff1a;Large Language Model&#xff0c;缩写LLM) 是一种人工智能模型, 旨在理解和生成人类语言. 大语言模型可以处理多种自然语言任务&#xff0c;如文本分类、问答、翻译、对话等等。 &#x1f4a5;通常&…

golang中2个只定义不需要初始化的高效字符缓存类型 bytes.Buffer和strings.Builder使用示例

在golang中&#xff0c;有2个高效的用于字符数据写入的缓存类型&#xff0c;同时他们也都实现了io.Writer接口&#xff0c;一个是bytes包中的Buffer 这个还实现了io.Reader接口&#xff1b; 另外一个是 strings包中的字符串构建类型 Builder。 在使用他们的时候是不需要初始化的…

gpedit.msc找不到文件

新建文件gpedit.bat内容如下,以管理员身份运行 echo off pushd "%~dp0" dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-…

NVIDIA - QPU

转载自 What Is a QPU? ( 2022 年 7 月 29 日 里克梅里特 https://blogs.nvidia.com/blog/what-is-a-qpu/ 文章目录 一、概述二、那么&#xff0c;什么是 QPU&#xff1f;三、量子处理器如何工作&#xff1f;四、制作量子比特的多种方法五、光的量子比特六、简单的芯片&#x…

git commit使用husky校验代码格式报错,没有将钩子 ‘.huskypre-commit‘ 设置为可执行,钩子被忽略。

使用git提交代码时&#xff0c;通过husky校验代码格式&#xff0c;终端报错 因为没有将钩子 .husky/pre-commit 设置为可执行 系统&#xff1a;Mac husky 在 Windows 上能够正常运行 解决办法 # 没有权限就给个权限 使用 chmod x 给权限 # 通过这行命令解决husky钩子不执行…

【C#】类和结构体的区别

目录 1.区别概述 ​编辑 2.细节区别 3.结构体的特别之处 4.如何选择结构体和类 1.区别概述 结构体和类的最大区别是在存储空间上&#xff0c;前者是值类型&#xff0c;存储在栈上&#xff0c;后者是引用类型&#xff0c;存储在堆上&#xff0c;它们在赋值上有很大的区别&a…

【C++入门到精通】C++ thread线程库 [ C++入门 ]

阅读导航 引言一、thread类的简单介绍二、thread类的用法1. 创建线程2. 使用 Lambda 表达式3. 传递参数给线程4. 线程的 join 和 detach5. 检查线程是否可 join6. 线程的 ID7. 线程的移动语义8. 线程的析构&#x1f6a8; 注意事项 三、线程函数参数温馨提示 引言 C thread线程…

实现流程化办公,可以相信拖拽表单设计器!

当前&#xff0c;竞争压力越来越大&#xff0c;利用什么样优良的办公软件实现流程化办公&#xff1f;可以一起来了解低代码技术平台、拖拽表单设计器的优势特点&#xff0c;看看它们是如何助力企业降本、增效、提质的。低代码技术平台的优势特点多&#xff0c;可以助力企业用拖…

Uni app 开发支付宝小程序,保存到相册,获取小程序权限列表失败问题记录及解决方案。

第一种实现方案思路&#xff1a;先获取所有用户的权限列表配置&#xff0c;进行判断是否可以访问相册。 我在做的时候&#xff0c;在测试环境可以获取权限列表&#xff0c;但是在正式环境就会报错&#xff0c;报错原因是获取不到当前用户的权限列表用的这个方法&#xff08;uni…

Jtti:租用的php服务器运行异常是什么原因导致的?

PHP服务器运行异常可能由多种原因引起。以下是一些常见问题及其相应的解决方案&#xff1a; 1. 服务器资源不足 原因&#xff1a; CPU、内存或磁盘空间不足&#xff0c;导致服务器性能下降。 解决方案&#xff1a; 检查系统资源&#xff1a;使用以下命令检查CPU、内存和磁盘使用…

轻松实现微信内下载,Xinstall让你的App推广更高效!

在微信中推广App&#xff0c;你是否遇到过这样的困扰&#xff1a;推广链接被微信拦截&#xff0c;用户需要手动复制链接到浏览器才能下载&#xff0c;大大降低了安装率&#xff1f;今天&#xff0c;我们要介绍的Xinstall&#xff0c;就是一款能够解决这一痛点的神器&#xff01…

2.2 OpenCV随手简记(三)

图像的阈值处理定义 &#xff1a;将图像转化为二值图像&#xff08;黑白图&#xff09;, 也可以用于彩色图形&#xff0c;达到夸张的效果 目的&#xff1a;是用来提取图像中的目标物体&#xff0c;将背景和噪声区分开&#xff08;可以近似的认为除了目标全是噪声&#xff09;。…

掌握TypeScript的类型断言与守卫:提升代码灵活性与安全性

引言 TypeScript的类型系统为JavaScript带来了结构和严谨性&#xff0c;但有时我们需要更多的灵活性。类型断言和类型守卫是TypeScript提供的两个特性&#xff0c;它们允许我们在强类型的环境中进行更细致的类型操作。 基础知识 类型断言&#xff1a;一种明确告诉编译器某个…

GSEA的算法只考虑排序吗

其实这个问题很好回答&#xff0c;只需要运行如下代码&#xff0c;如下的基因列表是顺序是完全相同&#xff0c;并且我们只是做了最基础的变换 library(clusterProfiler) library(org.Hs.eg.db)data(geneList, package"DOSE")ego1 <- gseGO(geneList geneLi…

【iOS】UI学习(二)

UI学习&#xff08;二&#xff09; 进度条和滑动条步进器与分栏控件警告对话框和提示等待器UITextFieldUITextField控件UITextFieldDelegate协议 UIScrollView布局子视图手动布局子视图自动布局子视图 进度条和滑动条 下面通过一个程序来讲解该内容&#xff1a; #import <…

Django 里的增删改查

下面是步骤 先更新 urls.py 来添加新的url from django.contrib import admin from django.urls import path from app01 import viewsurlpatterns [path(demo/, views.demo), ]在 models.py 里创建表 from django.db import models# Create your models here. class UserI…

力扣 226. 翻转二叉树

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ struct TreeNode* invertTree(struct Tr…