业务需求,需要在微信小程序端加个富友支付。这和调微信支付和支付宝支付一个类型模板。按照自己支付需求去找对应的接口和参数:富友支付官方文档。把官方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格式报文。具体回调参数,参照官网。