首页 > 其他分享 >微信支付商户系统Native支付

微信支付商户系统Native支付

时间:2024-11-01 14:08:48浏览次数:1  
标签:JsonAlias String 微信 private 支付 public Native

简易demo演示

demo演示

点击体验

### Native支付介绍 目前微信支付有以下几种场景 * JSAPi支付,适合微信公众号及微信小程序 * APP支付 * H5支付 * Native支付,适合PC网站页面支付 [微信支付商户平台](https://pay.weixin.qq.com/) [微信支付Native接口文档](https://pay.weixin.qq.com/doc/v3/merchant/4012075105) Native支付是指商户系统按照微信支付协议生成支付二维码,用户再用微信“扫一扫”实现支付 ### 前提准备 微信商家号、微信小程序或者微信公众号appId、商户证书 需要提前开启Native支付 ![](/i/l/?n=24&i=blog/1597479/202411/1597479-20241101102120164-1844181028.png) 需要将公众号或者小程序的appId在微信支付后台关联起来 ![](/i/l/?n=24&i=blog/1597479/202411/1597479-20241101102251617-1587831565.png) 在微信支付——账户中心——API安全,生成商户APIV3证书 ![](/i/l/?n=24&i=blog/1597479/202411/1597479-20241101102550425-1205822505.png)

业务流程图


主要步骤为,pc端生成订单调用Native下单接口生成微信的native 跳转链接,再生成二维码返回到页面,用户微信扫一扫完成支付,微信支付后台回调支付成功的链接。

支付接入

引入微信支付的SDK

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

生成二维码的工具,这里我选择了zxing

<dependency>
       <groupId>com.google.zxing</groupId>
      <artifactId>core</artifactId>
      <version>3.4.1</version>
  </dependency>
  <dependency>
      <groupId>com.google.zxing</groupId>
      <artifactId>javase</artifactId>
      <version>3.4.1</version>
  </dependency>

创建订单接口https://pay.weixin.qq.com/doc/v3/merchant/4012525095
需要商户号、商户APIV3密钥、商户证书序列号、商户API秘钥文件
正常秘钥文件放在服务器上读取,这里先直接放在项目文件resources下面

@Slf4j
@Service
public class WechatPayService extends AbstractPayService implements InitializingBean {
    
    /**
     * 商户号
     */
    public static String merchantId = "xxx";
    /**
     * 商户证书序列号
     */
    public static String merchantSerialNumber = "xxxx";
    /**
     * 商户APIV3密钥
     */
    public static String apiV3Key = "xxxx";

    private Config config;
    /**
     * 商户API私钥
     */
    private String privateKeyPem;

    @Override
    public void afterPropertiesSet() throws Exception {
        ClassPathResource resource = new ClassPathResource("wechat/apiclient_key.pem");
        privateKeyPem = IOUtils.toString(resource.getInputStream());

        config = new RSAAutoCertificateConfig.Builder()
                .merchantId(merchantId)
                .privateKey(privateKeyPem)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(apiV3Key)
                .build();
    }

    @Override
    public String createPayOrder(String clientIp, OrderRequest orderRequest) {
        final String orderNo = OrderUtils.generateOrderNo();
        // 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
        // 构建service
        NativePayService service = new NativePayService.Builder().config(config).build();
        // request.setXxx(val)设置所需参数,具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(1);
        request.setAmount(amount);
        request.setAppid("xxxx");
        request.setMchid(merchantId);
        request.setDescription("VIP体验卡");
        request.setNotifyUrl("http://myrkfh.natappfree.cc/mark_day/wechat/pay/notify");
        request.setOutTradeNo(orderNo);
        // 调用下单方法,得到应答
        PrepayResponse response = service.prepay(request);
        // 使用微信扫描 code_url 对应的二维码,即可体验Native支付
        log.info("调用微信生成订单返回结果:", response.toString());
        if (StrUtil.isBlank(response.getCodeUrl())) {
            throw new BaseException("创建订单失败");
        }
        String codeUrl = response.getCodeUrl();

        // 生成二维码
        QrConfig qrConfig = QrConfig.create();
        String qrBase64Text = QrCodeUtil.generateAsBase64(codeUrl, qrConfig, "svg");

        // 这里可以记录订单信息
        return qrBase64Text;
    }

orderNo在本系统中要唯一,不能有重复的订单号,否则下单失败

private static final String DATE_FORMAT = "yyyyMMddHHmmss";

    public static String generateOrderNo() {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        String timestamp = now.format(formatter);
        return timestamp + RandomUtil.randomNumbers(18);
    }

其中notifyUrl为支付成功回调地址,本地调试可以借助内网穿透工具,其他参数接口文档https://pay.weixin.qq.com/doc/v3/merchant/4012525095
微信返回的codeUrl为weixin://wxpay/bizpayurl?pr=Vd8RvS7z4格式的,放在手机上可以直接打开,我们要做的就是将codeUrl放入二维码的内容,也可以直接 使用Hutool,返回的二维码可以保存图片,将图片地址返回前端,也可以直接返回base64的图片地址,这里一般会记录订单数据为待支付状态数据,最后根据自己需要生成二维码的内容如下,有效期为两个小时

将地址返回过后后面就是等待微信支付的回调,

微信回调通知

微信支付通知接口文档 https://pay.weixin.qq.com/doc/v3/merchant/4012084431
如果处理成功了,需要返回HTTP状态码为200或204,否则微信会根据一定的策略进行重试

/**
 * 微信支付回调实体
 *
 * @author liufuqiang
 * @Date 2024-10-21 17:33:00
 */
@Data
public class WechatPayNotifyDTO {

    private String id;

    /**
     * 通知创建的时间
     */
    @JsonAlias("create_time")
    private String createTime;

    /**
     * 通知的资源数据类型,支付成功通知为encrypt-resource。
     */
    @JsonAlias("resource_type")
    private String resourceType;

    /**
     * 通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
     */
    @JsonAlias("event_type")
    private String eventType;

    /**
     * 通知资源数据
     */
    private Resource resource;

    @Data
    public static class Resource {

        /**
         * 加密算法AEAD_AES_256_GCM
         */
        private String algorithm;

        /**
         * Base64编码后的开启/停用结果数据密文。
         */
        private String ciphertext;

        /**
         * 附加数据。
         */
        @JsonAlias("associated_data")
        private String associatedData;

        /**
         * 原始回调类型,为transaction
         */
        @JsonAlias("original_type")
        private String originalType;

        /**
         * 加密使用的随机串。
         */
        private String nonce;
    }

    /**
     * 回调摘要
     */
    private String summary;

接受回调

@PostMapping("/notify")
    public void payNotify(HttpServletResponse response,
                          HttpServletRequest request,
                          @RequestHeader(Constant.WECHAT_PAY_SIGNATURE) String signature,
                          @RequestHeader(Constant.WECHAT_PAY_NONCE) String nonce,
                          @RequestHeader(Constant.WECHAT_PAY_TIMESTAMP) String timestamp,
                          @RequestHeader(Constant.WECHAT_PAY_SERIAL) String serialNo,
                          @RequestBody String requestBody
                          ) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.addHeader(Constant.WECHAT_PAY_SIGNATURE, signature);
        httpHeaders.addHeader(Constant.WECHAT_PAY_NONCE, nonce);
        httpHeaders.addHeader(Constant.WECHAT_PAY_TIMESTAMP, timestamp);
        httpHeaders.addHeader(Constant.WECHAT_PAY_SERIAL, serialNo);
        // 验证签名
        boolean isSignValid = wechatPayService.validSignature(httpHeaders, requestBody);
        if (!isSignValid) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }

        log.info("签名验证通过");
        WechatPayNotifyDTO notifyDTO = JsonUtils.genBeanByJson(requestBody, WechatPayNotifyDTO.class);
        wechatPayService.paymentHandle(notifyDTO);
        response.setStatus(HttpServletResponse.SC_OK);
    }

验签

防止伪造支付的回调,需要会返回的内容进行签名验证,
非文件/下载验证签名文档https://pay.weixin.qq.com/doc/v3/merchant/4012365350
如果嫌弃麻烦,也可以直接使用WechatPay2Validator
验证签名的时候尽量不要全部取出请求头的内容,根据HttpServletRequest全部取出的HeaderMap里面的key值全部是小写,签名的时候避免取到空值
签名的第三个参数为全部requestBody的内容,所以这里不能用实体对象接受数据,直接改用字符串接受数据

解密

根据API商户秘钥以及接口返回的associatedData、nonce对密文ciphertext进行解密,是AES对称加密解密

static final int TAG_LENGTH_BIT = 128;
public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException e) {
        } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException |
                 BadPaddingException e) {
        }
        return "";
    }

解析完成是json字符串,这俩使用jackjson转为对象

@Data
public class OutOrderDTO {

    private String appid;

    @JsonAlias("out_trade_no")
    private String outTradeNo;

    @JsonAlias("trade_type")
    private String tradeType;

    @JsonAlias("trade_state")
    private String tradeState;

    @JsonAlias("trade_state_desc")
    private String tradeStateDesc;

    @JsonAlias("bank_ype")
    private String bankType;

    @JsonAlias("success_time")
    private String payTime;

    private Payer payer;

    private Amount amount;

    @Data
    public static final class Payer {
        private String openid;
    }

    @Data
    public static final class Amount {
        private Integer total;
        @JsonAlias("payer_total")
        private String payerTotal;
    }

创建订单时的OutTradeNo订单号也会返回,为了避免重复处理,在订单表加状态判断或者加分布式锁

WechatPayNotifyDTO.Resource resource = wechatPayNotifyDTO.getResource();
   String jsonStr = decryptToString(resource.getAssociatedData().getBytes(StandardCharsets.UTF_8),
  resource.getNonce().getBytes(StandardCharsets.UTF_8), resource.getCiphertext());
  log.info("微信支付回调信息:{}", jsonStr);

  OutOrderDTO outOrderDTO = JsonUtils.genBeanByJson(jsonStr, OutOrderDTO.class);

  // 订单处理
  log.info("支付完成");

同一个二维码也就是生成的native链接如果已经被支付过会提示订单已经支付,请勿重复发起支付

标签:JsonAlias,String,微信,private,支付,public,Native
From: https://www.cnblogs.com/LiuFqiang/p/18519817

相关文章

  • 微信小程序的组件
    微信小程序的组件是构成小程序界面的基本单元,通过组合不同的组件可以实现丰富的页面效果。微信小程序提供了一系列基础组件和一些高级组件,开发者可以根据需要选择使用。下面是一些常用的组件:基础组件1.view:视图容器,用于布局。2.text:文本组件,用于显示文字。3.image:图片组件......
  • 基于微信小程序的蛋糕订购系统的设计与实现 -附源码
    摘 要在现代社会,蛋糕作为一种受欢迎的甜点,在各种场合都扮演着重要的角色。为了满足人们对蛋糕订购的需求,基于SpringBoot和微信小程序的蛋糕订购系统应运而生。本研究旨在开发一个方便、快捷的平台,让用户能够通过微信小程序轻松浏览、选择和订购各种美味的蛋糕。通过这个蛋......
  • 微信图片防盗链解决方案:自建代理绕过限制。
    当爬取微信里的图片使用的时候会发现,当我们浏览器地址栏访问微信中的图片URL是没有问题。但当我们在自己项目中使用img标签src引入的时候,就会出现,“此图片来自微信公众平台未经允许不可引用”的问题。这是因为微信为了防止其他平台引入,特意做了类似防盗链的功能。前往原文地址......
  • RN 中的createNativeStackNavigator,为什么出现顶部的 header
    在ReactNative中,createNativeStackNavigator是ReactNavigation库提供的一个用于创建原生堆栈导航器的函数。它允许你在应用程序中创建具有堆栈导航功能的屏幕,其中每个屏幕都可以被推送到堆栈上或从堆栈中弹出。createNativeStackNavigator生成的导航器默认包含一个......
  • Unity从0开发游戏上架微信小程序系列-1-完成小游戏逻辑
    本人的小游戏“方块消了个消”已经上线啦!!!欢迎各位来体验,希望大家多多提意见哦~微信公众号:unity学习加油站,注:本系列更完会在公众号上放置源码,本系列会包括以下几种游戏前言:本文主要内容,制作上图中第一个游戏,现在开始吧,喜欢的朋友点个赞吧!!!第一步:准备素材,导入Unity中制......
  • 【Unity】微信小游戏适配之屏幕常亮
    屏幕常亮Unity:Screen.sleepTimeout=SleepTimeout.NeverSleep;微信:wx.setKeepScreenOn(Objectobject)功能描述设置是否保持常亮状态。仅在当前小程序生效,离开小程序后设置失效。参数Objectobject属性类型默认值必填说明keepScreenOnboolean 是是否保......
  • 掌握微信自动回复设置,让沟通更简单更高效便捷!
    你是不是也想在忙碌的时候,仍然能够及时回复好友并提高沟通效率呢?如果是的话,那么微信管理系统中的机器人功能将是你的不二之选!1、自动通过好友请求当有人向你发送好友请求时,微信管理系统能够自动审核并通过这些请求。你就不必每次都手动查看新的请求,还可以确保你不会错过任......
  • 如何用pbootcmsAPI接口开发微信小程序UNIAPP示例
    1.准备工作在开始开发小程序之前,你需要:搭建好PbootCMS环境,确保其正常运行。注册小程序并获取AppID和AppSecret。配置PbootCMS与小程序的接口。2.封装API//获取站点信息exportconstpostSite=(config={})=>http.post('/cms/site',config)//获取自定义标签ex......
  • uniapp - 详细实现移动端公众号 H5 网页授权登录流程及示例代码,申请测试公众号全流程
    前言Vue版本,请访问这篇文章。在uni-appH5网站平台开发中,详解微信公众号网页接入微信授权登录示例代码,附带申请测试公众号全流程及配置教程,提供前端h5页面公众号网页实现授权登陆并获取用户昵称头像数据的示例源码,用自己项目跑出来的本地局域网IP段就可以拉起公众......
  • 微信用不了零钱支付是什么原因
    微信用不了零钱支付可能是由多个因素引起的,包括:1.账户安全问题;2.余额不足或者超过限额;3.网络或系统故障;4.地区性限制;5.支付行为异常等。了解这些因素不仅有助于解决问题,还能提醒我们在日常使用中更加注意账户安全和合规使用。1.账户安全问题账户安全是影响微信零钱支付功能......