草庐IT

微信支付APIV3统一支付接口(H5、JSAPI、H5、App、小程序)

一恍过去 2023-04-10 原文
😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: 微信支付统一支付接口(H5、JSAPI、H5、App、小程序)
⏱️ @ 创作时间: 2022年07月10日

目录

前言

对微信支付的H5、JSAPI、H5、App、小程序支付方式进行统一,此封装接口适用于普通商户模式支付,如果要进行服务商模式支付可以结合服务商官方API进行参数修改(未验证可行性)。

1、引入POM

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

2、配置Yaml

wxpay:
  #应用编号
  appId: xxxx
  #商户号
  mchId: xxx
  # APIv2密钥
  apiKey: xxxx
  # APIv3密钥
  apiV3Key: xxx
  # 微信支付V3-url前缀
  baseUrl: https://api.mch.weixin.qq.com/v3
  # 支付通知回调, pjm6m9.natappfree.cc 为内网穿透地址
  notifyUrl: http://pjm6m9.natappfree.cc/pay/payNotify
  # 退款通知回调, pjm6m9.natappfree.cc 为内网穿透地址
  refundNotifyUrl: http://pjm6m9.natappfree.cc/pay/refundNotify
  # 密钥路径,resources根目录下
  keyPemPath: apiclient_key.pem
  #商户证书序列号
  serialNo: xxxxx

3、配置密钥文件

在商户/服务商平台的”账户中心" => “API安全” 进行API证书、密钥的设置,API证书主要用于获取“商户证书序列号”以及“p12”、“key.pem”、”cert.pem“证书文件,j将获取的apiclient_key.pem文件放在项目的resources目录下。

4、配置PayConfig

WechatPayConfig:


import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;

/**
 * @Author:
 * @Description:
 **/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
    /**
     * 应用编号
     */
    private String appId;
    /**
     * 商户号
     */
    private String mchId;
    /**
     * 服务商商户号
     */
    private String slMchId;
    /**
     * APIv2密钥
     */
    private String apiKey;
    /**
     * APIv3密钥
     */
    private String apiV3Key;
    /**
     * 支付通知回调地址
     */
    private String notifyUrl;
    /**
     * 退款回调地址
     */
    private String refundNotifyUrl;

    /**
     * API 证书中的 key.pem
     */
    private String keyPemPath;

    /**
     * 商户序列号
     */
    private String serialNo;

    /**
     * 微信支付V3-url前缀
     */
    private String baseUrl;

    /**
     * 获取商户的私钥文件
     * @param keyPemPath
     * @return
     */
    public PrivateKey getPrivateKey(String keyPemPath){

            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
            if(inputStream==null){
                throw new RuntimeException("私钥文件不存在");
            }
            return PemUtil.loadPrivateKey(inputStream);
    }

    /**
     * 获取证书管理器实例
     * @return
     */
    @Bean
    public  Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {

        log.info("获取证书管理器实例");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(keyPemPath);

        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);

        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));

        return certificatesManager.getVerifier(mchId);
    }


    /**
     * 获取支付http请求对象
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(Verifier verifier)  {

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(keyPemPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, serialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient(){

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(keyPemPath);

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, serialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }
}

5、定义统一枚举

WechatPayUrlEnum:


@AllArgsConstructor
@Getter
public enum WechatPayUrlEnum {


	/**
	 * native
	 */
	NATIVE("native"),
	/**
	 * app
	 */
	APP("app"),
	/**
	 * h5
	 */
	H5("h5"),
	/**
	 *  jsapi
	 */
	JSAPI("jsapi"),

	/**
	 *  小程序jsapi
	 */
	SUB_JSAPI("sub_jsapi"),

	/**
	 * Native下单
	 */
	PAY_TRANSACTIONS("/pay/transactions/"),

	/**
	 * Native下单
	 */
	NATIVE_PAY_V2("/pay/unifiedorder"),

	/**
	 * 查询订单
	 */
	ORDER_QUERY_BY_NO("/pay/transactions/out-trade-no/"),

	/**
	 * 关闭订单
	 */
	CLOSE_ORDER_BY_NO("/pay/transactions/out-trade-no/%s/close"),

	/**
	 * 申请退款
	 */
	DOMESTIC_REFUNDS("/refund/domestic/refunds"),

	/**
	 * 查询单笔退款
	 */
	DOMESTIC_REFUNDS_QUERY("/refund/domestic/refunds/"),

	/**
	 * 申请交易账单
	 */
	TRADE_BILLS("/bill/tradebill"),

	/**
	 * 申请资金账单
	 */
	FUND_FLOW_BILLS("/bill/fundflowbill");

	/**
	 * 类型
	 */
	private final String type;
}

6、封装统一请求处理

WechatPayRequest:


import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;

/**
 * @Author: 
 * @Description:
 **/
@Component
@Slf4j
public class WechatPayRequest {
    @Resource
    private CloseableHttpClient wxPayClient;
    public  String wechatHttpGet(String url) {
        try {
            // 拼接请求参数
            HttpGet httpGet = new HttpGet(url);
            httpGet.setHeader("Accept", "application/json");

            //完成签名并执行请求
            CloseableHttpResponse response = wxPayClient.execute(httpGet);

            return getResponseBody(response);
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
    }

    public  String wechatHttpPost(String url,String paramsStr) {
        try {
            HttpPost httpPost = new HttpPost(url);
            StringEntity entity = new StringEntity(paramsStr, "utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");

            CloseableHttpResponse response = wxPayClient.execute(httpPost);
            return getResponseBody(response);
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
    }

   private String getResponseBody(CloseableHttpResponse response) throws IOException {

            //响应体
           HttpEntity entity = response.getEntity();
           String body = entity==null?"":EntityUtils.toString(entity);
            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();

            //处理成功,204是,关闭订单时微信返回的正常状态码
            if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
                log.info("成功, 返回结果 = " + body);
            } else {
                String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
                log.error(msg);
                throw new RuntimeException(msg);
            }
            return body;
    }
}

7、封装统一代码

7.1、统一下单处理

PayController:


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.lhz.demo.pay.WechatPayConfig;
import com.lhz.demo.model.enums.WechatPayUrlEnum;
import com.lhz.demo.pay.WechatPayRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;


/**
 * @Author: 
 * @Description:
 **/
@Api(tags = "支付接口(API3)")
@RestController
@RequestMapping("/test")
@Slf4j
public class PayController {

    @Resource
    private WechatPayConfig wechatPayConfig;

    @Resource
    private WechatPayRequest wechatPayRequest;

    /**
     * 无需应答签名
     */
    @Resource
    private CloseableHttpClient wxPayNoSignClient;

    /**
     * type:h5、jsapi、app、native、sub_jsapi
     * @param type
     * @return
     */
    @ApiOperation(value = "统一下单-统一接口", notes = "统一下单-统一接口")
    @ApiOperationSupport(order = 10)
    @GetMapping("/transactions")
    public Map<String,Object> transactions(String type) {
        log.info("统一下单API,支付方式:{}",type);

        // 统一参数封装
        Map<String, Object> params = new HashMap<>(8);
        params.put("appid", wechatPayConfig.getAppId());
        params.put("mchid", wechatPayConfig.getMchId());
        params.put("description", "测试商品");
        int outTradeNo = new Random().nextInt(999999999);
        params.put("out_trade_no", outTradeNo + "");
        params.put("notify_url", wechatPayConfig.getNotifyUrl());

        Map<String, Object> amountMap = new HashMap<>(4);
        // 金额单位为分
        amountMap.put("total", 1);
        amountMap.put("currency", "CNY");
        params.put("amount", amountMap);

        // 场景信息
        Map<String, Object> sceneInfoMap = new HashMap<>(4);
        // 客户端IP
        sceneInfoMap.put("payer_client_ip", "127.0.0.1");
        // 商户端设备号(门店号或收银设备ID)
        sceneInfoMap.put("device_id", "127.0.0.1");

        // 除H5与JSAPI有特殊参数外,其他的支付方式都一样
        if (type.equals(WechatPayUrlEnum.H5.getType())) {

            Map<String, Object> h5InfoMap = new HashMap<>(4);
            // 场景类型:iOS, Android, Wap
            h5InfoMap.put("type", "IOS");
            sceneInfoMap.put("h5_info", h5InfoMap);
        } else if (type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType())) {
            Map<String, Object> payerMap = new HashMap<>(4);
            payerMap.put("openid", "123123123");
            params.put("payer", payerMap);
        }

        params.put("scene_info", sceneInfoMap);

        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);

        // 重写type值,因为小程序会多一个下划线(sub_type)
        String[] split = type.split("_");
        String  newType = split[split.length - 1];

        String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType)), paramsStr);
        Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>(){});

        Map<String, Object> signMap = paySignMsg(resMap, type);
        resMap.put("type",type);
        resMap.put("signMap",signMap);
        return resMap;
    }


    private  Map<String, Object>  paySignMsg(Map<String, Object> map,String type){
        // 设置签名信息,Native与H5不需要
        if(type.equals(WechatPayUrlEnum.H5.getType()) || type.equals(WechatPayUrlEnum.NATIVE.getType()) ){
            return null;
        }

        long timeMillis = System.currentTimeMillis();
        String appId = wechatPayConfig.getAppId();
        String timeStamp = timeMillis/1000+"";
        String nonceStr = timeMillis+"";
        String prepayId = map.get("prepay_id").toString();
        String packageStr = "prepay_id="+prepayId;

        // 公共参数
        Map<String, Object> resMap = new HashMap<>();
        resMap.put("nonceStr",nonceStr);
        resMap.put("timeStamp",timeStamp);

        // JSAPI、SUB_JSAPI(小程序)
        if(type.equals(WechatPayUrlEnum.JSAPI.getType()) || type.equals(WechatPayUrlEnum.SUB_JSAPI.getType()) ) {
            resMap.put("appId",appId);
            resMap.put("package", packageStr);
            // 使用字段appId、timeStamp、nonceStr、package进行签名
            String paySign = createSign(resMap);
            resMap.put("paySign", paySign);
            resMap.put("signType", "HMAC-SHA256");
        }
        // APP
        if(type.equals(WechatPayUrlEnum.APP.getType())) {
            resMap.put("appid",appId);
            resMap.put("prepayid", prepayId);
            // 使用字段appId、timeStamp、nonceStr、prepayId进行签名
            String sign = createSign(resMap);
            resMap.put("package", "Sign=WXPay");
            resMap.put("partnerid", wechatPayConfig.getMchId());
            resMap.put("sign", sign);
            resMap.put("signType", "HMAC-SHA256");
        }
        return resMap;
    }

    /**
     * 获取加密数据
     */
    private  String createSign(Map<String, Object> params){
        try {
            Map<String, Object> treeMap = new TreeMap<>(params);
            List<String> signList = new ArrayList<>(5);
            for (Map.Entry<String, Object> entry : treeMap.entrySet())
            {
                signList.add(entry.getKey() + "=" + entry.getValue());
            }
            String signStr = String.join("&", signList);

            signStr = signStr+"&key="+wechatPayConfig.getApiV3Key();
            System.out.println(signStr);

            Mac sha = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(wechatPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha.init(secretKey);
            byte[] array = sha.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
            }
            signStr = sb.toString().toUpperCase();
            System.out.println(signStr);

            return signStr;
        }catch (Exception e){
            throw new RuntimeException("加密失败!");
        }
    }
}

7.2 、其他接口处理(退款、查询、取消订单等)

PayController:


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.lhz.demo.pay.WechatPayConfig;
import com.lhz.demo.model.enums.WechatPayUrlEnum;
import com.lhz.demo.pay.WechatPayRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;


/**
 * @Author: LiHuaZhi
 * @Description:
 **/
@Api(tags = "支付接口(API3)")
@RestController
@RequestMapping("/test")
@Slf4j
public class PayController {

    @Resource
    private WechatPayConfig wechatPayConfig;

    @Resource
    private WechatPayRequest wechatPayRequest;

    /**
     * 无需应答签名
     */
    @Resource
    private CloseableHttpClient wxPayNoSignClient;


    @ApiOperation(value = "根据订单号查询订单-统一接口", notes = "根据订单号查询订单-统一接口")
    @ApiOperationSupport(order = 15)
    @GetMapping("/transactions/{orderNo}")
    public  Map<String, Object> transactionsByOrderNo(@PathVariable("orderNo") String orderNo) {

        // TODO 如果是扫码支付时,该接口就很有必要,应该前端通过轮询的方式请求该接口查询订单是否支付成功

        log.info("根据订单号查询订单,订单号: {}", orderNo);

        String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.ORDER_QUERY_BY_NO.getType().concat(orderNo))
                .concat("?mchid=").concat(wechatPayConfig.getMchId());

        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询订单结果:{}",res);

        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});

        String outTradeNo = resMap.get("out_trade_no").toString();
        String appId = resMap.get("appid").toString();
        String mchId = resMap.get("mchid").toString();
        /**
         *         交易状态,枚举值:
         *         SUCCESS:支付成功
         *         REFUND:转入退款
         *         NOTPAY:未支付
         *         CLOSED:已关闭
         *         REVOKED:已撤销(仅付款码支付会返回)
         *         USERPAYING:用户支付中(仅付款码支付会返回)
         *         PAYERROR:支付失败(仅付款码支付会返回)
         */
        String tradeState = resMap.get("trade_state").toString();

        log.info("outTradeNo:"+outTradeNo);
        log.info("appId:"+appId);
        log.info("mchId:"+mchId);
        log.info("tradeState:"+tradeState);

        return resMap;
    }

    /**
     * 关闭(取消)订单
     * @param orderNo
     * @return
     */
    @ApiOperation(value = "关闭(取消)订单-统一接口", notes = "关闭(取消)订单-统一接口")
    @ApiOperationSupport(order = 20)
    @PostMapping("/closeOrder/{orderNo}")
    public void closeOrder(@PathVariable("orderNo") String orderNo) {

        // TODO 用于在客户下单后,不进行支付,取消订单的场景
        log.info("根据订单号取消订单,订单号: {}", orderNo);

        String url = String.format(WechatPayUrlEnum.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wechatPayConfig.getBaseUrl().concat(url);

        // 设置参数
        Map<String, String> params = new HashMap<>(2);
        params.put("mchid", wechatPayConfig.getMchId());

        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);

        String res = wechatPayRequest.wechatHttpPost(url,paramsStr);
    }

    /**
     * 申请退款
     * @param orderNo
     */
    @ApiOperation(value = "申请退款-统一接口", notes = "申请退款-统一接口")
    @ApiOperationSupport(order = 25)
    @PostMapping("/refundOrder/{orderNo}")
    public void refundOrder(@PathVariable("orderNo") String orderNo) {

        log.info("根据订单号申请退款,订单号: {}", orderNo);

        String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS.getType());

        // 设置参数
        Map<String, Object> params = new HashMap<>(2);
        // 订单编号
        params.put("out_trade_no", orderNo);
        // 退款单编号 - 自定义
        int outRefundNo = new Random().nextInt(999999999);
        log.info("退款申请号:{}",outRefundNo);
        params.put("out_refund_no",outRefundNo+"");
        // 退款原因
        params.put("reason","申请退款");
        // 退款通知回调地址
        params.put("notify_url", wechatPayConfig.getRefundNotifyUrl());

        Map<String, Object>  amountMap =new HashMap<>();
        //退款金额,单位:分
        amountMap.put("refund", 1);
        //原订单金额,单位:分
        amountMap.put("total", 1);
        //退款币种
        amountMap.put("currency", "CNY");
        params.put("amount", amountMap);

        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);


        String res = wechatPayRequest.wechatHttpPost(url,paramsStr);

        log.info("退款结果:{}",res);
    }

    /**
     * 查询单笔退款信息
     * @param refundNo
     * @return
     */
    @ApiOperation(value = "查询单笔退款信息-统一接口", notes = "查询单笔退款信息-统一接口")
    @ApiOperationSupport(order = 30)
    @GetMapping("/queryRefundOrder/{refundNo}")
    public  Map<String, Object> queryRefundOrder(@PathVariable("refundNo") String refundNo) {

        log.info("根据订单号查询退款订单,订单号: {}", refundNo);

        String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo));

        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询退款订单结果:{}",res);

        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});

        String successTime = resMap.get("success_time").toString();
        String refundId = resMap.get("refund_id").toString();
        /**
         * 款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
         * 枚举值:
         * SUCCESS:退款成功
         * CLOSED:退款关闭
         * PROCESSING:退款处理中
         * ABNORMAL:退款异常
         */
        String status = resMap.get("status").toString();

        /**
         * 枚举值:
         * ORIGINAL:原路退款
         * BALANCE:退回到余额
         * OTHER_BALANCE:原账户异常退到其他余额账户
         * OTHER_BANKCARD:原银行卡异常退到其他银行卡
         */
        String channel = resMap.get("channel").toString();

        log.info("successTime:"+successTime);
        log.info("channel:"+channel);
        log.info("refundId:"+refundId);
        log.info("status:"+status);

        // TODO 在查询单笔退款信息时,可以再去查询一次订单的状态,保证该订单已经退款完毕了

        return resMap;
    }

    /**
     * 申请交易账单
     * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请 ,如果传入日期未为当天则会出错
     * @param billType 分为:ALL、SUCCESS、REFUND
     * ALL:返回当日所有订单信息(不含充值退款订单)
     * SUCCESS:返回当日成功支付的订单(不含充值退款订单)
     * REFUND:返回当日退款订单(不含充值退款订单)
     * @return
     */
    @ApiOperation(value = "申请交易账单-统一接口", notes = "申请交易账单-统一接口")
    @ApiOperationSupport(order = 35)
    @GetMapping("/tradeBill")
    public  String tradeBill(@RequestParam("billDate") String billDate, @RequestParam("billType")  String billType) {

        log.info("申请交易账单,billDate:{},billType:{}", billDate,billType);

        String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.TRADE_BILLS.getType())
                .concat("?bill_date=").concat(billDate).concat("&bill_type=").concat(billType);

        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询退款订单结果:{}",res);

        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});

        String downloadUrl = resMap.get("download_url").toString();

        return downloadUrl;
    }

    /**
     *
     * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
     * @param accountType 分为:BASIC、OPERATION、FEES
     * BASIC:基本账户
     * OPERATION:运营账户
     * FEES:手续费账户
     * @return
     */
    @ApiOperation(value = "申请资金账单-统一接口", notes = "申请资金账单-统一接口")
    @ApiOperationSupport(order = 40)
    @GetMapping("/fundFlowBill")
    public String fundFlowBill(@RequestParam("billDate") String billDate, @RequestParam("accountType")  String accountType) {

        log.info("申请交易账单,billDate:{},accountType:{}", billDate,accountType);

        String url = wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.FUND_FLOW_BILLS.getType())
                .concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType);

        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询退款订单结果:{}",res);

        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>(){});

        String downloadUrl = resMap.get("download_url").toString();

        return downloadUrl;
    }


    @ApiOperation(value = "下载账单-统一接口", notes = "下载账单-统一接口")
    @ApiOperationSupport(order = 45)
    @GetMapping("/downloadBill")
    public void downloadBill(String downloadUrl) {

        log.info("下载账单,下载地址:{}",downloadUrl);

        HttpGet httpGet = new HttpGet(downloadUrl);
        httpGet.addHeader("Accept", "application/json");

        CloseableHttpResponse response =null;
        try {
            //使用wxPayClient发送请求得到响应
            response =  wxPayNoSignClient.execute(httpGet);

            String body = EntityUtils.toString(response.getEntity());

            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200 || statusCode == 204) {
                log.info("下载账单,返回结果 = " + body);
            } else {
                throw new RuntimeException("下载账单异常, 响应码 = " + statusCode+ ", 下载账单返回结果 = " + body);
            }
            // TODO 将body内容转为excel存入本地或者输出到浏览器,演示存入本地
            writeStringToFile(body);

        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
        finally {
            if(response!=null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void writeStringToFile(String body) {
        FileWriter fw = null;
        try {
            String filePath = "C:\\Users\\lhz12\\Desktop\\wxPay.txt";
             fw = new FileWriter(filePath, true);
            BufferedWriter  bw = new BufferedWriter(fw);
             bw.write(body);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(fw!=null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

有关微信支付APIV3统一支付接口(H5、JSAPI、H5、App、小程序)的更多相关文章

  1. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  2. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

    当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

  3. ruby - 使用 postgres.app 在 rvm 下要求 pg 时出错 - 2

    我正在使用Postgres.app在OSX(10.8.3)上。我已经修改了我的PATH,以便应用程序的bin文件夹位于所有其他文件夹之前。Rammy:~phrogz$whichpg_config/Applications/Postgres.app/Contents/MacOS/bin/pg_config我已经安装了rvm并且可以毫无错误地安装pggem,但是当我需要它时我得到一个错误:Rammy:~phrogz$gem-v1.8.25Rammy:~phrogz$geminstallpgFetching:pg-0.15.1.gem(100%)Buildingnativeextension

  4. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  5. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  6. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  7. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  8. ruby - 为 capybara 设置 app_host 的内容 - 2

    我的测试尝试访问网页并验证页面上是否存在某些元素。例如,它访问http://foo.com/homepage.html并检查Logo图像,然后访问http://bar.com/store/blah.html并检查页面上是否出现了某些文本。我的目标是访问经过Kerberos身份验证的网页。我发现Kerberos代码如下:主文件uri=URI.parse(Capybara.app_host)kerberos=Kerberos.new(uri.host)@kerberos_token=kerberos.encoded_tokenkerberos.rb文件classKerberosdefini

  9. Ubuntu20.04系统WineHQ7.0安装微信 - 2

    提供3种Ubuntu系统安装微信的方法,在Ubuntu20.04上验证都ok。1.WineHQ7.0安装微信:ubuntu20.04安装最新版微信--可以支持微信最新版,但是适配的不是特别好;比如WeChartOCR.exe报错。2.原生微信安装:linux系统下的微信安装(ubuntu20.04)--微信适配的最好,反应最快,但是微信版本只到2.1.1,版本太老,很多功能都没有。3.深度deepin-wine6安装微信:ubuntu20.04+系统deepin-wine6安装新版微信--综合比较好,当前个人使用此种方法1个月,微信版本3.4;没什么大问题,尚可。一、WineHQ7.0安装微信

  10. 微信小程序订餐系统 - 2

    对传统的餐饮商家来说,小程序很好地解决了餐厅线下线上连接的问题,在引流获客、节约人力、营销宣传、塑造会员体系、改善消费体验等方面都有很大帮助。小程序点餐可以帮助餐饮企业节省一大把人力开支。一个包含扫码点单、菜品管理、优惠券推送、外卖配送的小程序,商家花几万元就能完成开发测试并投入。商家为什么要开通“扫码点餐”1.解决服务员不够用的问题。2.不怕顾客跑单漏单。3.在微信就能管理菜品、查看营业额。4.订单小票显示顾客桌号和已点菜品。5.可在“附近的小程序”找到您的门店。如今餐饮业常用的三种经营模式:1堂食点单模式客人通过小程序堂食点单。商家可以在微信扫码点餐小程序管理后台根据自己店内情况来设置不同

随机推荐