草庐IT

微信支付V3 小程序支付API Java版

俗人码农 2023-04-14 原文

本文目的:快速接通微信支付V3 无需关注细节,实现支付功能,修改配置即可调用

文章目录


接入准备

申请APPID,申请mchid,绑定APPID及mchid,设置APIV3密钥,下载并配置商户证书

微信支付文档


微信支付流程整理(小程序版)

  1. 前端获取登录凭证(wx.login)
  2. 服务端换取用户openId(code2Session)
  3. 创建微信支付订单(/v3/pay/transactions/jsapi)
  4. 回调服务端

提示:以下是本篇文章正文内容,下面案例可供参考

一、导入微信支付扩展包

微信支付扩展包文档

 <!--微信支付-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.7</version>
        </dependency>

二、微信支付工具类

1.签名工具类

代码如下(示例):

@Component
public class WxSignUtil {

    protected static final SecureRandom RANDOM = new SecureRandom();

    /**
     * 微信调起支付参数
     * 返回参数如有不理解 请访问微信官方文档
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
     *
     * @param prepayId         微信下单返回的prepay_id
     * @param appId            应用ID
     * @param mch_id           商户号
     * @param privateKey       私钥
     * @return 当前调起支付所需的参数
     * @throws Exception
     */
    public static String WxAppPayTuneUp(String prepayId, String appId, String mch_id, String privateKey) throws Exception {
        if (StringUtils.isNotBlank(prepayId)) {
            long timestamp = System.currentTimeMillis() / 1000;
            String nonceStr = generateNonceStr();
            //加载签名
            String packageSign = sign(buildMessage(appId, timestamp, nonceStr, prepayId).getBytes(), privateKey);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("appId", appId);
            jsonObject.put("prepayId", prepayId);
            jsonObject.put("timeStamp", timestamp);
            jsonObject.put("nonceStr", nonceStr);
            jsonObject.put("package", "Sign=WXPay");
            jsonObject.put("signType", "RSA");
            jsonObject.put("sign", packageSign);
            jsonObject.put("partnerId", mch_id);
            return jsonObject.toJSONString();
        }
        return "";
    }

    public static String sign(byte[] message, String privateKey) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
        //签名方式
        Signature sign = Signature.getInstance("SHA256withRSA");
        //私钥
        sign.initSign(PemUtil
                .loadPrivateKey(privateKey));
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }

    //生成随机字符串 微信底层的方法,直接copy出来了
    protected static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(RANDOM.nextInt("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".length()));
        }
        return new String(nonceChars);
    }

    /**
     * 按照前端签名文档规范进行排序,\n是换行
     *
     * @param appId     appId
     * @param timestamp 时间
     * @param nonceStr  随机字符串
     * @param prepay_id prepay_id
     * @return
     */
    public static String buildMessage(String appId, long timestamp, String nonceStr, String prepay_id) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + "prepay_id="+prepay_id + "\n";
    }

}


2.微信支付工具类

代码如下(示例):

@Slf4j
@Component
public class WxMiniPayUtils {

    public static CloseableHttpClient httpClient;

    public static Verifier verifier;


    /**
     * 初始化 HttpClient
     * @throws IOException
     */
    public static void initWXPayClient() throws IOException {
        try {
            // 加载商户私钥(privateKey:私钥字符串)
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey({{微信支付私钥字符串}});

            // 也可使用证书 查询商户证书序列号
//            X509Certificate wechatPayCert = PemUtil.loadCertificate(new ByteArrayInputStream(StaticVariable.WX_PAY_CERT.getBytes(StandardCharsets.UTF_8)));
//            String serialNo = wechatPayCert.getSerialNumber().toString(16).toUpperCase();

            String serialNo ={{微信支付商户证书序列号}};
            //merchantId:商户号,serialNo:商户证书序列号
            // 获取证书管理器实例
            CertificatesManager certificatesManager = CertificatesManager.getInstance();
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant({{微信支付-商户号}}, new WechatPay2Credentials({{微信支付-商户号}},
                    new PrivateKeySigner(serialNo, merchantPrivateKey)), {{微信支付-v3 密钥}}.getBytes(StandardCharsets.UTF_8));
            // 从证书管理器中获取verifier
            //版本>=0.4.0可使用 CertificatesManager.getVerifier(mchId) 得到的验签器替代默认的验签器。
            // 它会定时下载和更新商户对应的微信支付平台证书 (默认下载间隔为UPDATE_INTERVAL_MINUTE)。
            verifier = certificatesManager.getVerifier({{微信支付-商户号}});

            //创建一个httpClient
            httpClient = WechatPayHttpClientBuilder.create()
                    .withMerchant({{微信支付-商户号}}, serialNo, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier)).build();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("加载秘钥文件失败");
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            log.error("获取平台证书失败");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭 HttpClient
     * @throws IOException
     */
    public static void closeWXClient() throws IOException {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 创建微信支付订单
     * @param openId 用户唯一标识
     * @param orderId 订单id
     * @param amount 支付价格(单位分)
     * @param description 订单说明
     * @return
     * @throws Exception
     */
    public static String creatOrderJSAPI(String openId,String orderId,Integer amount,String description) throws Exception {
        try {
            initWXPayClient();
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");

            String reqdata = "{"
                    + "\"amount\": {"
                    + "\"total\": "+amount+","
                    + "\"currency\": \"CNY\""
                    + "},"
                    + "\"mchid\": \""+{{微信支付-商户号}}+"\","
                    + "\"description\": \""+description+"\","
                    + "\"notify_url\": \""+{{微信支付-回调地址}}+"\","
                    + "\"payer\": {"
                    + "\"openid\": \""+openId+"\"" + "},"
                    + "\"out_trade_no\": \""+orderId+"\","
                    + "\"goods_tag\": \"WXG\","
                    + "\"appid\": \""+{{微信APPID}}+"\"" + "}";

            httpPost.setEntity(new StringEntity(reqdata, "utf-8"));
            CloseableHttpResponse response = httpClient.execute(httpPost);
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        }catch (Exception e){
            e.printStackTrace();
            log.error("创建微信支付订单失败");
        }finally {
            closeWXClient();
        }
        return "";
    }
}

三、创建订单

代码如下(示例):

		String response= WxMiniPayUtils.creatOrderJSAPI({{用户openId}}, {{订单id}},{{订单金额}},{{订单描述}});
		JSONObject jsonObject= JSONObject.fromObject(response);
		if (jsonObject.containsKey("prepay_id")){
			/**补充相关业务*/
		}

创建微信支付订单,微信会返回 prepay_id (预下单id)用于前端调起支付


四、调起支付

调起支付文档

前端通过prepay_id调起支付


五、支付回调

Controller代码如下(示例):

	@ApiOperation(value = "微信支付回调", notes = "微信支付回调")
	@PostMapping(value = "wxAppPayNotify.do")
	public String wxAppPayNotify( @RequestHeader("Wechatpay-Serial") String wechatpaySerial,
									  @RequestHeader("Wechatpay-Signature") String wechatpaySignature,
									  @RequestHeader("Wechatpay-Timestamp") String wechatpayTimestamp,
									  @RequestHeader("Wechatpay-Nonce") String wechatpayNonce,
									  @RequestBody String callback) throws Exception {
	    return Service.wxAppPayNotify(wechatpaySerial,wechatpaySignature,wechatpayTimestamp,wechatpayNonce,callback);
	}

验签和解密并返回通知

Servicer代码如下(示例):

	@Override
	public String wxAppPayNotify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String callback) throws Exception {
		//按照文档要求拼接验签串
		String verifySignature = wechatpayTimestamp + "\n"
				+ wechatpayNonce + "\n" + callback + "\n";

		//使用官方验签工具进行验签
		boolean verify1 = WxMiniPayUtils.verifier.verify(wechatpaySerial, verifySignature.getBytes(), wechatpaySignature);
		//判断验签的结果
		if (!verify1) {
			//验签失败,应答接口
			com.alibaba.fastjson.JSONObject jsonResponse = new com.alibaba.fastjson.JSONObject();
			jsonResponse.put("code", "FAIL");
			jsonResponse.put("message", "失败");
			return jsonResponse.toString();
		}
		
		JSONObject parseObject = JSONObject.parseObject(callback);
		if ("TRANSACTION.SUCCESS".equals(parseObject.getString("event_type"))
				&& "encrypt-resource".equals(parseObject.getString("resource_type"))) {
			//通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
			//通知的资源数据类型,支付成功通知为encrypt-resource
			JSONObject resourceJson = JSONObject.parseObject(parseObject.getString("resource"));
			String associated_data = resourceJson.getString("associated_data");
			String nonce = resourceJson.getString("nonce");
			String ciphertext = resourceJson.getString("ciphertext");

			//解密,如果这里报错,就一定是APIv3密钥错误
			AesUtil aesUtil = new AesUtil({{微信支付-v3 密钥}}.getBytes());
			String resourceData = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
			System.out.println("解密后=" + resourceData);
			//dosomething 处理业务
			JSONObject resource =JSONObject.fromObject(resourceData);
			if (resource.containsKey("out_trade_no")){
				/**支付成功,补充业务**/
			}

		}
		JSONObject jsonResponse = new JSONObject();
		jsonResponse.put("code", "SUCCESS");
		jsonResponse.put("message", "成功");
		return jsonResponse.toString();

	}

总结

微信支付文档

网上的微信支付V3 教程无需看太多 ,专注官方文档

参考文档:https://www.cnblogs.com/cchilei/p/16077207.html

https://blog.csdn.net/m0_59588838/article/details/127204694

有关微信支付V3 小程序支付API Java版的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  3. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  6. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  8. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  9. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  10. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

随机推荐