富友支付之微信小程序支付

业务需求,需要在微信小程序端加个富友支付。这和调微信支付和支付宝支付一个类型模板。按照自己支付需求去找对应的接口和参数:富友支付官方文档。把官方Demo贴出来,方便后续的查阅和学习。这里以微信支付小程序为例。

1.需要的导包

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpmime</artifactId>
      <version>4.5.2</version>
    </dependency>

2.准备支付的一些相关的秘钥,机构id等数据,具体根据官网来,这以富友本身支付公众号秘钥为例。

/**
 * 富友支付相关数据信息
 */
public class Const {

    //编码
    public static String charset = "GBK";

    //机构私钥
    public static final String INS_PRIVATE_KEY ="MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJgAzD8fEvBHQTyxUEeK963mjziM\n" +
           "WG7nxpi+pDMdtWiakc6xVhhbaipLaHo4wVI92A2wr3ptGQ1/YsASEHm3m2wGOpT2vrb2Ln/S7lz1\n" +
           "ShjTKaT8U6rKgCdpQNHUuLhBQlpJer2mcYEzG/nGzcyalOCgXC/6CySiJCWJmPyR45bJAgMBAAEC\n" +
           "gYBHFfBvAKBBwIEQ2jeaDbKBIFcQcgoVa81jt5xgz178WXUg/awu3emLeBKXPh2i0YtN87hM/+J8\n" +
           "fnt3KbuMwMItCsTD72XFXLM4FgzJ4555CUCXBf5/tcKpS2xT8qV8QDr8oLKA18sQxWp8BMPrNp0e\n" +
           "pmwun/gwgxoyQrJUB5YgZQJBAOiVXHiTnc3KwvIkdOEPmlfePFnkD4zzcv2UwTlHWgCyM/L8SCAF\n" +
           "clXmSiJfKSZZS7o0kIeJJ6xe3Mf4/HSlhdMCQQCnTow+TnlEhDTPtWa+TUgzOys83Q/VLikqKmDz\n" +
           "kWJ7I12+WX6AbxxEHLD+THn0JGrlvzTEIZyCe0sjQy4LzQNzAkEAr2SjfVJkuGJlrNENSwPHMugm\n" +
           "vusbRwH3/38ET7udBdVdE6poga1Z0al+0njMwVypnNwy+eLWhkhrWmpLh3OjfQJAI3BV8JS6xzKh\n" +
           "5SVtn/3Kv19XJ0tEIUnn2lCjvLQdAixZnQpj61ydxie1rggRBQ/5vLSlvq3H8zOelNeUF1fT1QJA\n" +
           "DNo+tkHVXLY9H2kdWFoYTvuLexHAgrsnHxONOlSA5hcVLd1B3p9utOt3QeDf6x2i1lqhTH2w8gzj\n" +
           "vsnx13tWqg==";
    //机构公钥
    public static final String INS_PUBLIC_KEY="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYAMw/HxLwR0E8sVBHivet5o84jFhu58aYvqQzHbVompHOsVYYW2oqS2h6OMFSPdgNsK96bRkNf2LAEhB5t5tsBjqU9r629i5/0u5c9UoY0ymk/FOqyoAnaUDR1Li4QUJaSXq9pnGBMxv5xs3MmpTgoFwv+gskoiQliZj8keOWyQIDAQAB";


   //富友公钥  用于验签
    public static final String FY_PUBLIC_KEY ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCBv9K+jiuHqXIehX81oyNSD2RfVn+KTPb7NRT5HDPFE35CjZJd7Fu40r0U2Cp7Eyhayv/mRS6ZqvBT/8tQqwpUExTQQBbdZjfk+efb9bF9a+uCnAg0RsuqxeJ2r/rRTsORzVLJy+4GKcv06/p6CcBc5BI1gqSKmyyNBlgfkxLYewIDAQAB";


    //机构号
    public static String ins_cd = "08A9999999";

    //商户号
    public static String mchnt_cd = "0002900F0370542";//0002900F0370542

    //终端号
    public static String term_id = "";

    //终端IP
    public static String term_ip = "127.0.0.1";

    //异步通知(回调地址)
    public static String notify_url = "http://www.wrx.cn";


    //下单
    public static String fuiou_21_url = "https://fundwx.fuiou.com/preCreate";
    //扫码
    public static String fuiou_22_url = "https://fundwx.fuiou.com/micropay";
    //公众号/服务窗统一下单
    public static String fuiou_23_url = "https://fundwx.fuiou.com/wxPreCreate";
    //退款
    public static String fuiou_24_url = "https://fundwx.fuiou.com/commonRefund";
    //资金划拨信息
//    public static String fuiou_xx_url = "https://fundwx.fuiou.com/queryChnlPayAmt";
    //查询可提现资金
    public static String fuiou_27_url = "https://fundwx.fuiou.com/queryWithdrawAmt";
    //查询手续费
    public static String fuiou_28_url = "https://fundwx.fuiou.com/queryFeeAmt";
    //提现
    public static String fuiou_29_url = "https://fundwx.fuiou.com/withdraw";

    //查询
    public static String fuiou_30_url = "https://fundwx.fuiou.com/commonQuery";

}

3.准备相关工具类

public class Utils {


    /**
     * 准备签名所需字段(并不是每个参数都需要进行签名,具体看官网)
     * @param map
     * @return
     */
    public static Map<String, String> paraFilter(Map<String, String> map) {

        Map<String, String> result = new HashMap<>();

        if (map == null || map.size() <= 0) {
            return result;
        }

        for (String key : map.keySet()) {
            String value = map.get(key);
            if (key.equalsIgnoreCase("sign") || (key.length() >= 8 && key.substring(0, 8).equalsIgnoreCase("reserved"))) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 准备请求生成订单的 拼接地址 参数
     * @param map
     * @return
     */
    public static String createLinkString(Map<String, String> map) {


        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = map.get(key);

            if (i == keys.size() - 1) {
                //拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }

    /**
     * 获取签名
     * @param map 准备签名所需要的字段参数
     * @return
     * @throws InvalidKeySpecException
     * @throws SignatureException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws IOException
     */
    public static String getSign(Map<String, String> map) throws InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {

        Map<String, String> mapNew = paraFilter(map);

        String preSignStr = createLinkString(mapNew);

        System.out.println("==============================待签名字符串==============================\r\n" + preSignStr);

        String sign = Sign.sign(preSignStr, Const.INS_PRIVATE_KEY);

        sign = sign.replace("\r\n", "");

        System.out.println("==============================签名字符串==============================\r\n" + sign);

        return sign;
    }

    /**
     * 校验返回签名
     * @param map
     * @param sign
     * @return
     * @throws Exception
     */
    public static Boolean verifySign(Map<String, String> map, String sign) throws Exception {

        Map<String, String> mapNew = paraFilter(map);

        String preSignStr = createLinkString(mapNew);

        return Sign.verify(preSignStr.getBytes(Const.charset), Const.FY_PUBLIC_KEY, sign);
    }

    /**
     * 将接收到的xml字符串转map
     * @param xmlStr
     * @return
     */
    public static Map<String,String> xmlStr2Map(String xmlStr){
        Map<String,String> map = new HashMap<String,String>();
        Document doc;
        try {
            doc = DocumentHelper.parseText(xmlStr);
            Element resroot = doc.getRootElement();
            List children = resroot.elements();
            if(children != null && children.size() > 0) {
                for(int i = 0; i < children.size(); i++) {
                    Element child = (Element)children.get(i);
//                    map.put(child.getName(), child.getTextTrim());//会将换行符转换成空格
                    map.put(child.getName(), child.getStringValue().trim());
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }
}

4.准备签名相关的加密工具类

public class Sign {

    /**
     * sign(RSA签名方法)
     * TODO(这里描述这个方法适用条件 – 可选)
     *
     * @param srcSignPacket URL拼接的参数
     * @param privateKey 私钥
     * @return String    DOM对象
     * @Exception 异常对象
     */
    public static String sign(String srcSignPacket, String privateKey)
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException, InvalidKeyException, SignatureException {
        // 解密由base64编码的私钥
        byte[] bytesKey = (new BASE64Decoder()).decodeBuffer(privateKey);
        // 构造PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(bytesKey);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        // 取私钥匙对象
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 用私钥对信息生成数字签名
        Signature signature = Signature.getInstance("MD5WithRSA");
        signature.initSign(priKey);
        signature.update(srcSignPacket.getBytes(Const.charset));
        String sign = (new BASE64Encoder()).encodeBuffer(signature.sign());
        return sign;
    }

    /**
     * 校验数字签名
     *
     * @param data      加密数据
     * @param publicKey 公钥
     * @param sign      数字签名
     * @return 校验成功返回true 失败返回false
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {

        // 解密由base64编码的公钥
        byte[] keyBytes = decryptBASE64(publicKey);

        // 构造X509EncodedKeySpec对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);

        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        // 取公钥匙对象
        PublicKey pubKey = keyFactory.generatePublic(keySpec);

        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initVerify(pubKey);
        signature.update(data);

        // 验证签名是否正常
        return signature.verify(decryptBASE64(sign));
    }

    /**
     * BASE64加密
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    /**
     * BASE64解密
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptBASE64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }

}

5.准备通用的http请求工具类

public class HttpUtils {

    /**
     * 发送 post请求访问本地应用并根据传递参数不同返回不同结果
     */
    public void post(String url, Map<String, String> map, StringBuffer result) {
        // 创建默认的httpClient实例.
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 创建httppost
        HttpPost httppost = new HttpPost(url);
        // 创建参数队列
        List<NameValuePair> formparams = new ArrayList<>();

        Iterator it=map.keySet().iterator();
        while(it.hasNext()){
            String key;
            String value;
            key=it.next().toString();
            value=map.get(key);

            formparams.add(new BasicNameValuePair(key, value));
        }

        UrlEncodedFormEntity uefEntity;
        try {
            uefEntity = new UrlEncodedFormEntity(formparams, Const.charset);
            httppost.setEntity(uefEntity);

            System.out.println("提交请求 " + httppost.getURI());
            CloseableHttpResponse response = httpclient.execute(httppost);
            try {
                HttpEntity entity = response.getEntity();

                if (entity != null && result != null) {
                    result.append(EntityUtils.toString(entity, Const.charset));
                }

                // 打印响应状态
                System.out.println(response.getStatusLine());
            } finally {
                response.close();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭连接,释放资源
            try {
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

6.准备微信小程序支付需要请求的参数

public class Builder {

    private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();

    private static SecureRandom random = new SecureRandom();


    /**
     * 统一下单
     *
     * @return
     */
    public static Map<String, String> buildFuiou21() {
        Map<String, String> map = new HashMap<>();

        map.put("version", "1");
        map.put("ins_cd", Const.ins_cd);
        map.put("mchnt_cd", Const.mchnt_cd);
        map.put("term_id", "12345678");
        map.put("random_str", randomNumberGenerator.nextBytes().toHex());
        map.put("sign", "");
        map.put("order_type", "ALIPAY");
        map.put("goods_des", "这是一个货物");
        map.put("goods_detail", "");
        map.put("addn_inf", "");
        SimpleDateFormat sdf_no = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        Calendar calendar = Calendar.getInstance();
        map.put("mchnt_order_no", sdf_no.format(calendar.getTime()) + (int) (random.nextDouble() * 100000));
        map.put("curr_type", "");
        map.put("order_amt", "1");
        map.put("term_ip", Const.term_ip);
        SimpleDateFormat sdf_ts = new SimpleDateFormat("yyyyMMddHHmmss");
        map.put("txn_begin_ts", sdf_ts.format(calendar.getTime()));
        map.put("goods_tag", "");
        map.put("notify_url", Const.notify_url);
        map.put("reserved_sub_appid", "");
        map.put("reserved_limit_pay", "");

        return map;
    }

    /**
     * 条码支付下单
     *
     * @return
     */
    public static Map<String, String> buildFuiou22() {
        Map<String, String> map = new HashMap<>();

        map.put("version", "1");
        map.put("ins_cd", Const.ins_cd);
        map.put("mchnt_cd", Const.mchnt_cd);
        map.put("term_id", "12345678");
        map.put("random_str", randomNumberGenerator.nextBytes().toHex());
        map.put("sign", "");
        map.put("order_type", "ALIPAY");
        map.put("goods_des", "这是一个货物");
        map.put("goods_detail", "");
        map.put("addn_inf", "");
        SimpleDateFormat sdf_no = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        Calendar calendar = Calendar.getInstance();
        map.put("mchnt_order_no", sdf_no.format(calendar.getTime()) + (int) (random.nextDouble() * 100000));
        map.put("curr_type", "");
        map.put("order_amt", "1");
        map.put("term_ip", Const.term_ip);
        SimpleDateFormat sdf_ts = new SimpleDateFormat("yyyyMMddHHmmss");
        map.put("txn_begin_ts", sdf_ts.format(calendar.getTime()));
        map.put("goods_tag", "");
        map.put("auth_code", "289387279140768146");
        map.put("sence", "1");
        map.put("reserved_sub_appid", "");
        map.put("reserved_limit_pay", "");

        return map;
    }

    /**
     * 公众号/服务窗统一下单
     *
     * @return
     */
    public static Map<String, String> buildFuiou23() {
        Map<String, String> map = new HashMap<>();

        map.put("version", "1.0");
        map.put("ins_cd", Const.ins_cd);
        map.put("mchnt_cd", "6510F5938854");//0001210F0976403富友商户号服务商模式0001210F0976403
        map.put("term_id", "88888888");
        map.put("random_str", randomNumberGenerator.nextBytes().toHex());
        map.put("sign", "");
        map.put("goods_des", "这是一个货物");
        map.put("goods_detail", "");
        map.put("goods_tag", "");
        map.put("product_id", "");
        map.put("addn_inf", "");
        SimpleDateFormat sdf_no = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        Calendar calendar = Calendar.getInstance();
        map.put("mchnt_order_no", sdf_no.format(calendar.getTime()) + (int) (random.nextDouble() * 100000));
        map.put("curr_type", "CNY");
        map.put("order_amt", "1");
        map.put("term_ip", Const.term_ip);
        SimpleDateFormat sdf_ts = new SimpleDateFormat("yyyyMMddHHmmss");
        map.put("txn_begin_ts", sdf_ts.format(calendar.getTime()));
        map.put("notify_url", Const.notify_url);
        map.put("limit_pay", "");
        map.put("trade_type", "JSAPI");//微信小程序
//           map.put("trade_type","LETPAY");//微信小程序
        //   map.put("trade_type","FWC");//支付宝服务窗
        map.put("openid", "ooIeqs5VwPJnDUYfLweOKcR5AxpE"); //富友公众号 ooIeqs5VwPJnDUYfLweOKcR5AxpE
        map.put("sub_openid", "osgI-t3iTLkEdGhhwTwyYy_QiqFM");//服务窗时填buyer_id的值  公众号的osgI-t3iTLkEdGhhwTwyYy_QiqFM
        map.put("sub_appid", "wx04bdf63c774e12ce");//公众号的 wx04bdf63c774e12ce
        map.put("reserved_fy_term_id", "");
        map.put("reserved_expire_minute", "0");
//        map.put("reserved_user_creid ","");
        map.put("reserved_user_truename", "");
        map.put("reserved_user_mobile", "");
        map.put("addn_inf", "");

        return map;
    }
}

7.最后调用支付就行

public class FyPayController {

    public static void main(String[] args) throws Exception {


        //2.1	统一下单
//        run(Builder.buildFuiou21(), Const.fuiou_21_url);
        //2.2	条码支付
//        run(Builder.buildFuiou22(), Const.fuiou_22_url);

        //2.3 公众号/服务窗统一下单  具体请阅读“公众号、服务窗下单必读”
        run(Builder.buildFuiou23(), Const.fuiou_23_url);
    }


    public static void run(Map<String, String> map, String url) throws Exception {
        Map<String, String> reqs = new HashMap<>();
        Map<String, String> nvs = new HashMap<>();

        reqs.putAll(map);

        String sign = Utils.getSign(reqs);
        reqs.put("sign", sign);

        Document doc = DocumentHelper.createDocument();
        Element root = doc.addElement("xml");

        Iterator it = reqs.keySet().iterator();
        while (it.hasNext()) {
            String key = it.next().toString();
            String value = reqs.get(key);

            root.addElement(key).addText(value);
        }

        // String reqBody = doc.getRootElement().asXML();
        String reqBody = "<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>" + doc.getRootElement().asXML();

        System.out.println("==============================待编码字符串==============================\r\n" + reqBody);

        reqBody = URLEncoder.encode(reqBody, Const.charset);

        System.out.println("==============================编码后字符串==============================\r\n" + reqBody);

        nvs.put("req", reqBody);

        StringBuffer result = new StringBuffer("");

        HttpUtils httpUtils = new HttpUtils();
        httpUtils.post(url, nvs, result);
        String rspXml = URLDecoder.decode(result.toString(), Const.charset);
        System.out.println("==============================响应报文==============================\r\n" + rspXml);
        //响应报文验签
        Map<String, String> resMap = Utils.xmlStr2Map(rspXml);
        String str = resMap.get("sign");
        System.out.println("str:" + str);
        System.out.println("验签结果:" + Utils.verifySign(resMap, str));

    }
}

回调那边根据自己设置的notify_url的值获取,回调形式:req={encode之后的xml报文}
收到回调后直接在 request 里面获取req参数的值,然后decode一下就得到xml格式报文。具体回调参数,参照官网。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值