微信JSAPI支付对接

news2024/11/16 3:50:03

简介

JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。

应用场景

JSAPI支付适用于线下场所、公众号场景和PC网站场景。

商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。具体操作流程如下:

1.商户下发图文消息或者通过自定义菜单吸引用户点击进入商户网页
2.进入商户网页,用户选择购买,完成选购流程。
3.调起微信支付控件,用户开始输入支付密码
4.密码验证通过,支付成功。商户后台得到支付成功的通知
5.返回商户页面,显示购买成功。该页面由商户自定义
6.微信支付公众号下发支付凭证

接入前准备

直接跳转微信支付商户平台 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_1.shtml

生成密钥文件:
在这里插入图片描述

配置文件:

wx:
  appId: appId
  keyPath: apiclient_key.pem
  certPath: apiclient_cert.pem
  certP12Path: 暂不用
  platformCertPath: platform_cert.pem
  mchId: mchId
  apiKey3: 暂不用
  apiKey: apiKey
  domain: https://hous.exchang.cn
  serialNo: 序列号

pom文件:

<dependency>
     <groupId>com.github.wechatpay-apiv3</groupId>
     <artifactId>wechatpay-java</artifactId>
     <version>0.2.2</version>
 </dependency>

代码:

配置类:

//获取yml中微信配置
@Data
@ConfigurationProperties(prefix = "wx")
public class WechatProperties {

    private String appId;

    private String keyPath;

    private String certPath;

    private String platformCertPath;

    private String mchId;

    private String apiKey;

    private String domain;

    private String serialNo;
}
//配置类
@Configuration
@EnableConfigurationProperties(WechatProperties.class)
public class WechatJsapiConfig {


    private final WechatProperties wechatProperties;

    public WechatJsapiConfig(WechatProperties wechatProperties) {
        this.wechatProperties = wechatProperties;
    }

    @Bean
    public RSAConfig rsaConfig() throws IOException {
        ClassPathResource keyResource = new ClassPathResource(wechatProperties.getKeyPath());
        String apiClientKey = keyResource.getFile().getPath();
        ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
        String platformCertResourceKey = certResource.getFile().getPath();
        /*String apiClientKey = wechatProperties.getKeyPath();
//        ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
        String platformCertResourceKey = wechatProperties.getPlatformCertPath();*/
        return new RSAConfig.Builder()
                .merchantId(wechatProperties.getMchId())
                .privateKeyFromPath(apiClientKey)
                .merchantSerialNumber(wechatProperties.getSerialNo())
                //平台证书
                .wechatPayCertificatesFromPath(platformCertResourceKey)
                .build();
    }

    @Bean
    public JsapiServiceExtension jsapiService() throws IOException {
        return new JsapiServiceExtension.Builder()
                .config(this.rsaConfig())
                .build();
    }

    @Bean
    public NotificationParser notificationParser() throws IOException{
        ClassPathResource certResource = new ClassPathResource(wechatProperties.getPlatformCertPath());
        String platformCertResourceKey = certResource.getFile().getPath();
        //String platformCertResourceKey = wechatProperties.getPlatformCertPath();
        NotificationConfig config = new RSANotificationConfig.Builder()
                .apiV3Key(wechatProperties.getApiKey())
                .certificatesFromPath(platformCertResourceKey)
                .build();
        // 初始化 NotificationParser
        return new NotificationParser(config);

    }

    @Bean(name = "payRefundService")
    public RefundService refundService() throws IOException {
        return new RefundService.Builder().config(this.rsaConfig()).build();
    }

}

创建支付单:

@Slf4j
@Service
@EnableConfigurationProperties(WechatProperties.class)
public class WechatJsapiPayServiceImpl implements BasePayService {

    @Resource
    private WechatProperties wechatProperties;

    @Resource
    private JsapiServiceExtension jsapiService;

    @Value("${spring.application.name}")
    private String serviceName;

    @Resource
    private MemberDao memberDao;

    @Resource
    private RedisCache redisCache;

    @Resource
    private ShopDao shopDao;


    @Override
    public R<?> paymentOrder(List<Order> orderList) {
        BigDecimal total = orderList.stream().map(Order::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        Order order = orderList.get(0);
        Long mId = order.getMId();
        Member member = memberDao.queryById(mId);
        Integer payTotal = total.multiply(BigDecimal.valueOf(100)).intValue();
        PrepayRequest prepayRequest = new PrepayRequest();
        prepayRequest.setAppid(wechatProperties.getAppId());
        prepayRequest.setMchid(wechatProperties.getMchId());
        prepayRequest.setDescription("供应链下单");
        prepayRequest.setOutTradeNo(order.getBatchSn());
        prepayRequest.setNotifyUrl(wechatProperties.getDomain() + "/callback/wechat/pay");
        prepayRequest.setAttach("1");
        prepayRequest.setTimeExpire(this.getTimeExpire());
        Amount amount = new Amount();
        amount.setTotal(payTotal);
        Payer payer = new Payer();
        payer.setOpenid(member.getOpenId());
        prepayRequest.setAmount(amount);
        prepayRequest.setPayer(payer);
        log.info("创建预支付单入参:【{}】", JSONObject.toJSONString(prepayRequest));
        try {
            PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(prepayRequest);
            log.info("创建预支付单并生成二维码成功,出参:【{}】", JSONObject.toJSONString(response));
            //未支付监听
            redisCache.setCacheObject(RedisConstants.ORDER_PAY_RESULT.concat(order.getBatchSn()),
                    JSONObject.toJSONString(response), 10, TimeUnit.MINUTES);
            redisCache.setCacheObject(RedisConstants.ORDER_NOT_PAY_LISTENER.concat(order.getBatchSn()),
                    NumberUtils.INTEGER_ZERO, 10, TimeUnit.MINUTES);
            //发送未支付消息
            redisCache.setCacheObject(RedisConstants.ORDER_MSG_LISTENER.concat(order.getBatchSn()),
                    NumberUtils.INTEGER_ZERO, 2, TimeUnit.MINUTES);
            return R.ok(response);
        } catch (HttpException e) { // 发送HTTP请求失败
            log.error("发送HTTP请求失败:{}", e.getHttpRequest());
            return R.fail("发送HTTP请求失败");
        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
            log.error("微信支付返回状态异常,{}", e.getResponseBody());
            return R.fail("微信支付返回状态异常");
        } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
            log.error("返回类型不合法:{}", e.getMessage());
            return R.fail("返回类型不合法");
        }
    }

    @Override
    public void closeOrder(String outTradeNo) {
        CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
        closeOrderRequest.setMchid(wechatProperties.getMchId());
        closeOrderRequest.setOutTradeNo(outTradeNo);
        jsapiService.closeOrder(closeOrderRequest);
        log.info("订单关闭成功,入参:【{}】", JSONObject.toJSONString(closeOrderRequest));
    }

    @Override
    public Optional<Transaction> getOrderInfo(String outTradeNo) {
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setOutTradeNo(outTradeNo);
        request.setMchid(wechatProperties.getMchId());
        Transaction transaction = jsapiService.queryOrderByOutTradeNo(request);
        if (Objects.isNull(transaction)) {
            return Optional.empty();
        }
        return Optional.of(transaction);
    }
    
    private String getTimeExpire(){
        //过期时间  RFC 3339格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
        //支付订单过期时间
        return sdf.format(new Date(System.currentTimeMillis() + 1000 * 60 * 5));
    }

}

创建退款单:

@Slf4j
@Service
@EnableConfigurationProperties(WechatProperties.class)
public class WechatRefundServiceImpl implements BaseRefundService {

    @Resource
    private RefundService refundService;

    @Resource
    private WechatProperties wechatProperties;

    @Value("${spring.application.name:scm-ofc-system}")
    private String serviceName;

    @Resource
    private OrderDao orderDao;

    @Resource
    private OrderItemDao orderItemDao;

    @Resource
    private ShopDao shopDao;

    @Override
    public R<?> refundOrder(OrderRefund orderRefund) {
        try {
            BigDecimal refundAmount = orderRefund.getRefundAmount();
            Long orderId = orderRefund.getOrderId();
            Order order = orderDao.queryById(orderId);
            //退款金额
            long refundTotal = refundAmount.multiply(BigDecimal.valueOf(100)).longValue();
            //原支付金额
            String batchSn = order.getBatchSn();
            OrderDTO params = new OrderDTO();
            params.setBatchSn(batchSn);
            List<Order> orders = orderDao.selectOrderList(params);
            BigDecimal officialReceipts = orders.stream().map(Order::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
            long payTotal = officialReceipts.multiply(BigDecimal.valueOf(100)).longValue();
            //创建微信退款单
            CreateRequest createRequest = new CreateRequest();
            AmountReq amountReq = new AmountReq();
            amountReq.setCurrency("CNY");
            amountReq.setTotal(payTotal);
            amountReq.setRefund(refundTotal);
            createRequest.setOutTradeNo(batchSn);
            createRequest.setAmount(amountReq);
            createRequest.setOutRefundNo(orderRefund.getRefundSn());
            createRequest.setNotifyUrl(wechatProperties.getDomain() + "/" + serviceName +  "/callback/wechat/refund");
            log.info("退款单入参:{}", JSONObject.toJSONString(createRequest));
            Refund refund = refundService.create(createRequest);
            log.info("创建退款单成功:{}", JSONObject.toJSONString(refund));
            if (Objects.isNull(refund)) {
                log.error("退款异常,参数:{}", JSONObject.toJSONString(createRequest));
                return R.fail(500, "退款异常,请求微信返回值为null:参数" + JSONObject.toJSONString(createRequest));
            }
            if (Objects.equals(refund.getStatus(), Status.SUCCESS)) {
                return R.ok(refund);
            }
        } catch (Exception e) {
            log.error("退款异常:{}", e.getMessage());
            return R.fail(500, "退款异常,请求微信报错" + e.getMessage());
        }
        return R.ok();
    }
  }

支付与退款回调

@Slf4j
@Service
public class CallbackServiceImpl implements CallbackService {

    @Resource
    private NotificationParser notificationParser;

    @Resource
    private RedisCache redisCache;

    @Resource
    private OrderService orderService;

    @Resource
    private BillDao billDao;

    @Resource
    private ShopAccountDao shopAccountDao;

    @Resource
    private OrderScmService orderScmService;

    @Resource
    private OrderRefundDao orderRefundDao;

    @Resource
    private ShopSkuService shopSkuService;

    @Resource
    private OrderItemDao orderItemDao;

    @Resource
    private MessageService messageService;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void wechatPayCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
            log.info("=========================微信native支付回调通知============================");
            Transaction transaction = this.verifyAndDecrypt(request, Transaction.class);
            log.info("验证签名成功:{}", JSONObject.toJSONString(transaction));
            Transaction.TradeStateEnum tradeState = transaction.getTradeState();
            if (!Objects.equals(tradeState, Transaction.TradeStateEnum.SUCCESS)) {
                return;
            }
            String outTradeNo = transaction.getOutTradeNo();
            
            log.info("支付回调执行成功,待支付->待发货");
        } catch (Exception e) {
            log.error("支付回调异常:{}", e.getMessage());
        }
    }

    @Override
    public void wechatRefundCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("=========================微信native退款回调通知============================");
        RefundNotification refundNotification = this.verifyAndDecrypt(request, RefundNotification.class);
        Status refundStatus = refundNotification.getRefundStatus();
        if (!Objects.equals(refundStatus, Status.SUCCESS)) {
            return;
        }
        //执行退款业务
    }
    
    /**
     * 获取供应链订单id
     * @param data
     * @param orderList
     * @return
     */
    private Map<Long, String> getOrderIdMap(OrderByThirdIdVO data,List<Order> orderList){
        List<OrderSkuDetailVO> skus = data.getSkus();
        Map<String, String> skuOrderIdMap = skus.stream()
                .collect(Collectors.toMap(OrderSkuDetailVO::getSku, OrderSkuDetailVO::getOrderId));
        List<OrderItem> orderItems = orderList.stream().map(Order::getOrderItem).collect(Collectors.toList());
        return orderItems.stream().map(item -> {
            String originId = item.getOriginId();
            String orderId = skuOrderIdMap.get(originId);
            return new DefaultKeyValue<>(item.getOrderId(), orderId);
        }).collect(Collectors.toMap(DefaultKeyValue::getKey, DefaultKeyValue::getValue));
    }


    /**
     * 验证并解密报文
     *
     * @param request
     */
    private <T> T verifyAndDecrypt(HttpServletRequest request, Class<T> clazz) {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");
        String signType = request.getHeader("Wechatpay-Signature-Type");
        String body = this.getBody(request);
        log.info("\n请求头信息:\n" +
                        "Wechatpay-Timestamp:{},\n" +
                        "Wechatpay-Nonce:{},\n" +
                        "Wechatpay-Serial:{},\n" +
                        "Wechatpay-Signature:{},\n" +
                        "Wechatpay-Signature-Type:{},\n" +
                        "body: {}",
                timestamp, nonce, serialNo, signature, signType, body);
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serialNo)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(body)
                .build();
        return notificationParser.parse(requestParam, clazz);
    }

    /**
     * 获取请求体内容
     *
     * @param request
     * @return
     */
    private String getBody(HttpServletRequest request) {
        BufferedReader reader;
        String body = "";
        try {
            reader = request.getReader();
            String line;
            StringBuilder inputString = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                inputString.append(line);
            }
            body = inputString.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body;
    }

}

以上就是微信jsapi支付的对接,仅供参考

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

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

相关文章

我做了一个世界杯的可视化网站...

好吧我是标题党&#xff0c;标题用的是完成时&#xff0c;但是事实上我这个还没完成。 这学期有个数据可视化的作业&#xff0c;遂决定做一个世界杯主题的可视化网站。基于Python、Flask和Echarts。 大概思路可以用下图表示&#xff1a; 因此网站也将分为3个大块&#xff0c;…

解决图表内容超出xy轴范围

问题&#xff1a; 解决方式&#xff1a; xAxis: {boundaryGap: true, // 留白属性 }

忆联再次与 OpenCloudOS 完成产品兼容性互认证

近日&#xff0c;忆联数据中心级SSD UH711a与OpenCloudOS 完成相互兼容认证&#xff0c;测试期间&#xff0c;整体运行稳定&#xff0c;在功能、性能及兼容性方面表现良好。 兼容认证证书 作为国产开源操作系统社区&#xff0c;OpenCloudOS 沉淀了腾讯及多家厂商在软件和开源生…

JS清除字符串中的空格

一、replace()方法 replace方法在字符串中搜索值或正则表达式&#xff0c;返回已替换值的新字符串&#xff0c;不会更改原始字符串。 去除字符串内所有的空格&#xff1a;str str.replace(/\s*/g,“”) 去除字符串内两头的空格&#xff1a;str str.replace(/^\s*|\s*$/g,“…

人大加拿大女王大学金融硕士项目——不一定有逆风翻盘,但一定要向阳而生

只有足够努力&#xff0c;才会足够幸运。想要得到世界上最好的东西&#xff0c;得先让时间看到最好的你。不一定有逆风翻盘&#xff0c;但一定要向阳而生。作为在职人士&#xff0c;不断的提升自己是非常重要的。如果停滞不前&#xff0c;便会被社会所淘汰。而人大加拿大女王大…

计讯物联外贸公司--佰沃恩应邀出席第三届“嘉庚论坛”—科技创新推动经济高质量发展分论坛

10月22日&#xff0c;以“数智创新能动未来”为主题的第三届“嘉庚论坛”—科技创新推动经济高质量发展分论坛于集美海景皇冠假日酒店正式启幕。此论坛聚焦集美区战略前沿产业&#xff0c;汇聚来自全国各地优秀的企业家、高校及科研院所专家学者&#xff0c;并邀请相关领域的亲…

【EI会议征稿通知】第二届材料科学与智能制造国际学术会议(MSIM 2024)

第二届材料科学与智能制造国际学术会议&#xff08;MSIM 2024&#xff09; 2024 2nd International Conference on Materials Science and Intelligent Manufacturing 2024年第二届材料科学与智能制造国际学术会议 &#xff08;MSIM2024&#xff09;将于2024年1月19日至21日在…

【递归】Pow(x , n)(Java版)

目录 1.题目解析 2.讲解算法原理 2.1.如何来解决Pow问题&#xff1f; 2.2.为什么这道题可以用递归来做&#xff1f; 2.2.1 什么是递归 2.2.2 为什么会用到递归 3.如何编写递归代码&#xff1f; 4.递归的细节展开图 1.题目解析 50.Pow(x , n) 实现 pow(x, n) &#xf…

echarts 实现tooltip自动轮播显示,鼠标悬浮时暂停

在ECharts中&#xff0c;可以通过设置 tooltip.trigger"axis" &#xff0c;来显示数据轴上的提示框。 实现tooltip的自动轮播显示&#xff0c;结合使用 setInterval()和 dispatchAction()方法。 获取chart DOM 实例&#xff0c;监听鼠标事件&#xff0c;悬浮时清空定…

【java学习—九】接口 interface(5)

文章目录 1. 相关概念2. 接口的特点3. 应用举例3.1. 特点&#xff1a;接口中的成员变量和方法的默认修饰3.2. 特点&#xff1a;一个类可以实现多个无关的接口3.3. 特点&#xff1a;接口可以继承接口3.4. 特点&#xff1a;实现接口的类中必须提供接口中所有方法的具体实现内容&a…

2核4G服务器5M带宽?选腾讯云轻量应用服务器吧

2核4G服务器5M带宽&#xff1f;选腾讯云轻量应用服务器吧&#xff0c;因为划算&#xff0c;价格便宜&#xff01;2023腾讯云双11优惠活动&#xff1a;轻量2核4G5M服务器166.6元/年&#xff0c;3年566.6元&#xff0c;50GB SSD云硬盘&#xff0c;300GB月流量&#xff0c;值得买的…

结构伪类选择器

伪类选择器&#xff1a;用来描述一个元素的特殊状态&#xff01;比如第一个元素、某个元素的子元素、鼠标点击的元素 1 first-child/last-child /*ul的第一个子元素*/ ul li:first-child{ background: #0f35ad; } /*ul的最后一个子元素*/ ul li:last-child{ background: #0f3…

Java多线程篇(12)——ForkJoinPool

文章目录 1、基本原理2、源码2.1、sumbit干了啥&#xff1f;2.2、工作线程如何运行&#xff1f;怎么窃取&#xff1f;2.3、fork干了啥&#xff1f;2.4、join干了啥&#xff1f; 1、基本原理 假设有大量的CPU密集型计算任务&#xff0c;比如计算1-100的总和&#xff0c;普通的写…

两天写一个电影资讯速览微信小程序(附源码)

简介 基于原生小程序精仿的猫眼电影&#xff0c;仅供学习和参考。 首页效果图 数据请求 url: https://m.maoyan.com/ajax/movieOnInfoList?token,success(res) {const movieList0 _this.formatImgUrl(res.data.movieList)wx.hideLoading()_this.setData({movieIds0: res.…

虚幻中的网络概述一

前置&#xff1a;在学习完turbo强大佬的多人fps之后发觉自己在虚幻网络方面还有许多基础知识不太清楚&#xff0c;结合安宁Ken大佬与虚幻官方文档进行补足。 补充&#xff1a;官方文档中的描述挺好的&#xff0c;自己只算是搬运和将两者结合加强理解。 学习虚幻中的网络先从虚…

基于标签的协同过滤推荐方法研究

&#xff3b;摘要&#xff3d; 2.2标签相似度矩阵 惩罚jaccard相关热度系数

Q4已来,DAO发新生|CyberDAO子DAO种子会议

Q4已来&#xff0c;DAO发新生|CyberDAO子DAO种子会议&#xff0c;2023年10月25日在成都五星级希顿国际酒店隆重召开&#xff0c;本次会议百名DAO精英成员及CyberDAO大中华区运营团队合伙人JR、漫威、安祈出席&#xff01;以蓄势和重塑为主题带领大家乘势而上&#xff0c;创新引…

控制系统典型应用车型 —— 牵引式移动机器人

牵引式移动机器人&#xff08;AGV/AMR&#xff09;&#xff0c;通常由一个牵引车和一个或多个被牵引的车辆组成。牵引车是机器人的核心部分&#xff0c;它具有自主导航和定位功能&#xff0c;可以根据预先设定的路径或地标进行导航&#xff0c;并通过传感器和视觉系统感知周围环…

提高数据研究效率,优化成果分享及复用|ModelWhale 版本更新

秋高气爽、金桂飘香&#xff0c;十月末&#xff0c;我们又迎来了 ModelWhale 新一轮的版本更新。 本次更新中&#xff0c;ModelWhale 主要进行了以下功能迭代&#xff1a; 新增 添加模型服务到知识库&#xff08;专业版✓ 团队版✓&#xff09; 新增 自动生成数据引用信息&am…

MES管理系统解决方案实现生产信息全程追溯

生产制造企业在生产过程中&#xff0c;最令人头疼的问题之一就是产品信息的追溯。当产品出现质量问题时&#xff0c;需要在庞大的数据中寻找出问题批次的产品和同批次的物料&#xff0c;并进行召回处理&#xff0c;这样的工作量十分巨大。为了解决这一问题&#xff0c;许多企业…