diff --git a/pay-java-fuiou/pom.xml b/pay-java-fuiou/pom.xml new file mode 100644 index 0000000..59d6739 --- /dev/null +++ b/pay-java-fuiou/pom.xml @@ -0,0 +1,27 @@ + + + + pay-java-parent + in.egan + 1.0.RELEASE + + 4.0.0 + pay-java-fuiou + + + + + + + + in.egan + pay-java-common + 1.0.RELEASE + + + + + + \ No newline at end of file diff --git a/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/Test.java b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/Test.java new file mode 100644 index 0000000..89684ab --- /dev/null +++ b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/Test.java @@ -0,0 +1,19 @@ +package in.egan.pay.fuiou;/** + * Created by Fuzx on 2017/2/10 0010. + */ + +import in.egan.pay.common.util.sign.SignUtils; + +/** + * @author Fuzx + * @create 2017 2017/2/10 0010 + */ +public class Test { + public static void main(String[] args) { + String content="0002230F0348879|17021013591343700615|805a9aphsvmbf6qih8k66vu1svafj99m"; + System.out.println(SignUtils.valueOf("MD5").createSign(content, "", "UTF-8")); + + + + } +} diff --git a/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/api/FuiouPayConfigStorage.java b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/api/FuiouPayConfigStorage.java new file mode 100644 index 0000000..7fdef12 --- /dev/null +++ b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/api/FuiouPayConfigStorage.java @@ -0,0 +1,44 @@ +package in.egan.pay.fuiou.api;/** + * Created by Fuzx on 2017/1/16 0016. + */ + +import in.egan.pay.common.api.BasePayConfigStorage; + +/** + * @author Fuzx + * @create 2017 2017/1/16 0016 + */ +public class FuiouPayConfigStorage extends BasePayConfigStorage { + + public String mchntCd;//商户代码 + public String mchntKey;//商户密钥 + + + @Override + public String getAppid() { + return null; + } + + @Override + public String getPartner() { + return mchntCd; + } + + public void setPartner(String partner) { + this.mchntCd = partner; + } + + @Override + public String getSeller() { + return null; + } + + @Override + public String getSecretKey() { + return mchntKey; + } + + public void setSecretKey(String mchntKey){ + this.mchntKey = mchntKey; + } +} diff --git a/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/api/FuiouPayService.java b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/api/FuiouPayService.java new file mode 100644 index 0000000..f968c2d --- /dev/null +++ b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/api/FuiouPayService.java @@ -0,0 +1,277 @@ +package in.egan.pay.fuiou.api;/** + * Created by Fuzx on 2017/1/16 0016. + */ + +import com.alibaba.fastjson.JSONObject; +import in.egan.pay.common.api.BasePayService; +import in.egan.pay.common.api.PayConfigStorage; +import in.egan.pay.common.api.RequestExecutor; +import in.egan.pay.common.bean.MethodType; +import in.egan.pay.common.bean.PayOrder; +import in.egan.pay.common.bean.PayOutMessage; +import in.egan.pay.common.bean.result.PayError; +import in.egan.pay.common.exception.PayErrorException; +import in.egan.pay.common.util.XML; +import in.egan.pay.common.util.sign.SignUtils; +import in.egan.pay.common.util.str.StringUtils; +import in.egan.pay.fuiou.utils.SimplePostRequestExecutor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.message.BasicNameValuePair; + +import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.util.*; + +/** + * @author Fuzx + * @create 2017 2017/1/16 0016 + */ +public class FuiouPayService extends BasePayService { + + protected final Log log = LogFactory.getLog(FuiouPayService.class); + + // public final static String fuiouBaseDomain = "https://pay.fuiou.com/";//正式域名 + public final static String fuiouBaseDomain = "http://www-1.fuiou.com:8888/wg1_run/";//测试域名 + + public final static String fuiouSmpGate = fuiouBaseDomain + "smpGate.do";//B2C/B2B支付 + + public final static String fuiouNewSmpGate = fuiouBaseDomain + "newSmpGate.do";//B2C/B2B支付(跨境支付) + public final static String fuiouSmpRefundGate = fuiouBaseDomain + "newSmpRefundGate.do";//订单退款 + + public final static String fuiouSmpQueryGate = fuiouBaseDomain + "smpQueryGate.do";//3.2 支付结果查询 + public final static String fuiouSmpAQueryGate = fuiouBaseDomain + "smpAQueryGate.do";//3.3 支付结果查询(直接返回) + + public FuiouPayService(PayConfigStorage payConfigStorage) { + setPayConfigStorage(payConfigStorage); + } + + + @Override + public String getHttpsVerifyUrl() { + return null; + } + + + @Override + public boolean verify(Map params) { + // TODO 2017/2/9 17:24 author: egan 需要校验签名,签名通过后,再校验订单的真实(根据单号查询对应的订单) + if (!"0000".equals(params.get("order_pay_code"))) { + log.debug(String.format("富友支付异常:order_pay_code=%s,错误原因=%s,参数集=%s", params.get("order_pay_code"), params.get("order_pay_error"), params)); + return false; + } + try { + return getSignVerify(params, params.get("md5")) && "0000".equals(verifyUrl(params.get("order_id")));//返回参数校验 和 重新请求订单检查是否真实支付成功 + } catch (PayErrorException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public boolean getSignVerify(Map params, String returnSign) { + LinkedHashSet keySet = new LinkedHashSet<>(); + keySet.add("mchnt_cd");//商户代码 + keySet.add("order_id");//商户订单号 + keySet.add("order_date");//订单日期 + keySet.add("order_amt");//交易金额 + keySet.add("order_st");//订单状态 + keySet.add("order_pay_code");//错误代码 + keySet.add("order_pay_error");//错误中文描述 + keySet.add("resv1");//保留字段 + keySet.add("fy_ssn");//富友流水号 + StringBuilder verifyMD5Str = new StringBuilder(); + for (String keyStr : keySet) { + String keyValue = params.get(keyStr); + if (null == keyValue){ + log.debug(String.format("富友支付返回结果校验:<参数:%s>不能为空,",keyStr)); + } + verifyMD5Str.append(keyValue).append("|"); + } + String sign = createSign(verifyMD5Str.deleteCharAt(verifyMD5Str.length() -1).toString(),payConfigStorage.getInputCharset()); +// System.out.println("加密串"+verifyMD5Str+",,返回参数生成MD5="+sign+",,返回MD5摘要值"+returnSign); + if(returnSign.equals(sign)){ + return true; + } + return false; + } + + @Override + public String verifyUrl(String order_id) throws PayErrorException { +// LinkedHashMap param = new LinkedHashMap(); +// param.put("mchnt_cd",payConfigStorage.getPartner()); +// param.put("order_id",order_id); +// param.put("md5",createSign(SignUtils.parameters2MD5Str(param,"|"),payConfigStorage.getInputCharset())); + List pairList = new ArrayList(); + pairList.add(new BasicNameValuePair("mchnt_cd",payConfigStorage.getPartner())); + pairList.add(new BasicNameValuePair("order_id",order_id)); + pairList.add(new BasicNameValuePair("md5",createSign(SignUtils.parameters2MD5Str(pairList,"|"),payConfigStorage.getInputCharset()))); + return execute(new SimplePostRequestExecutor(), fuiouSmpAQueryGate,pairList); +// JSONObject jsonObject = XML.toJSONObject(responseContent); + +// return getFormString(param,MethodType.POST,fuiouSmpAQueryGate); + } + + @Override + public T execute(RequestExecutor executor, String uri, E data) throws PayErrorException { + int retryTimes = 0; + do { + try { + return executeInternal(executor, uri, data); + } catch (PayErrorException e) { + PayError error = e.getError(); + if (error.getErrorCode() == 404) { + int sleepMillis = retrySleepMillis * (1 << retryTimes); + try { + log.debug(String.format("富友支付系统错误,错误信息:<%s>,(%s)ms 后重试(第%s次)",e.getMessage(),sleepMillis, retryTimes + 1)); + Thread.sleep(sleepMillis); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + } else { + throw e; + } + } + } while (++retryTimes < maxRetryTimes); + + throw new RuntimeException("富友支付服务端异常,超出重试次数"); + } + + @Override + public Map orderInfo(PayOrder order) { + LinkedHashMap parameters = getOrderInfo(order); + String sign = createSign(SignUtils.parameters2MD5Str(parameters, "|"), payConfigStorage.getInputCharset()); + parameters.put("md5", sign); + return parameters; + } + + private LinkedHashMap getOrderInfo(PayOrder order) { + LinkedHashMap parameters = new LinkedHashMap(); + parameters.put("mchnt_cd", payConfigStorage.getPartner());//商户代码 + parameters.put("order_id", order.getTradeNo());//商户订单号 + parameters.put("order_amt", order.getPrice());//交易金额 +// parameters.put("cur_type", null == order.getCurType() ? FuiouCurType.CNY:order.getCurType());//交易币种 + parameters.put("order_pay_type", order.getTransactionType());//支付类型 + parameters.put("page_notify_url", payConfigStorage.getReturnUrl());//商户接受支付结果通知地址 + parameters.put("back_notify_url", StringUtils.isBlank(payConfigStorage.getNotifyUrl()) ? "" : payConfigStorage.getNotifyUrl());//商户接受的支付结果后台通知地址 //非必填 + parameters.put("order_valid_time", "30m");//超时时间 1m-15天,m:分钟、h:小时、d天、1c当天有效, + parameters.put("iss_ins_cd", order.getBankType());//银行代码 + parameters.put("goods_name", order.getSubject()); + parameters.put("goods_display_url", "1");//商品展示网址 //非必填 + parameters.put("rem", "1");//备注 //非必填 + parameters.put("ver", "1.0.1");//版本号 + return parameters; + } + + @Override + public String createSign(String content, String characterEncoding) { + return SignUtils.valueOf(payConfigStorage.getSignType().toUpperCase()).createSign(content, "|" + payConfigStorage.getSecretKey(), characterEncoding); + } + + @Override + public Map getParameter2Map(Map parameterMap, InputStream is) { + Map params = new TreeMap(); + for (Iterator iter = parameterMap.keySet().iterator(); iter.hasNext(); ) { + String name = (String) iter.next(); + String[] values = parameterMap.get(name); + String valueStr = ""; + for (int i = 0; i < values.length; i++) { + valueStr = (i == values.length - 1) ? valueStr + values[i] + : valueStr + values[i] + ","; + } + //乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化 + //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk"); + params.put(name, valueStr); + } + return params; + } + + @Override + public PayOutMessage getPayOutMessage(String code, String message) { + return PayOutMessage.TEXT().content(code.toLowerCase()).build(); + } + + @Override + public String buildRequest(Map orderInfo, MethodType method) { + return getFormString(orderInfo, method,fuiouSmpGate); + } + + /** + * 根据参数行程form表单 + * @param param 参数 + * @param method 请求方式 get_post + * @param url + * @return + */ + private String getFormString(Map param, MethodType method,String url) { + StringBuffer formHtml = new StringBuffer(); + + formHtml.append("
"); + + for (String key : param.keySet()) { + Object o = param.get(key); + if (null == o || "null".equals(o) || "".equals(o)) { + continue; + } + formHtml.append(""); + } + + + //submit按钮控件请不要含有name属性 +// formHtml.append(""); + formHtml.append("
"); + formHtml.append(""); + + return formHtml.toString(); + } + + /** + * 生成二维码支付 + * 暂未实现或无此功能 + * + * @param orderInfo 发起支付的订单信息 + * @return + */ + @Override + public BufferedImage genQrPay(Map orderInfo) { + throw new UnsupportedOperationException(); + } + + + /** + * 支付结果查询(直接返回) + *

+ * 返回结果例子 + * + * 错误代码(0000表示成功 其他失败)错误中文描述商户订单号订单状态(‘00’ – 订单已生成(初始状态) ‘01’ – 订单已撤消 ‘02’ – 订单已合并 ‘03’ – 订单已过期 ‘04’ – 订单已确认(等待支付) ‘05’ – 订单支付失败 ‘11’ – 订单已支付 ‘18’ – 已发货 ‘19’ – 已确认收货)富友流水号保留字段md5 + *

+ * md5为plain域的内容+商户密钥做md5,不包括plain标签 + * 以下是MD5的内容 + * 错误代码(0000表示成功 其他失败)错误中文描述商户订单号订单状态(‘00’ – 订单已生成(初始状态) ‘01’ – 订单已撤消 ‘02’ – 订单已合并 ‘03’ – 订单已过期 ‘04’ – 订单已确认(等待支付) ‘05’ – 订单支付失败 ‘11’ – 订单已支付 ‘18’ – 已发货 ‘19’ – 已确认收货)富友流水号保留字段商户密钥 + * + * @param order_id + * @return + */ + public JSONObject vaildatePayResult(String order_id) { + LinkedHashMap param = new LinkedHashMap(); + param.put("mchnt_cd", payConfigStorage.getPartner()); + param.put("order_id", order_id); + param.put("md5", createSign(SignUtils.parameters2MD5Str(param, "|"), payConfigStorage.getInputCharset())); + try { + String result = execute(new SimplePostRequestExecutor(), fuiouSmpAQueryGate, param); + JSONObject object = XML.toJSONObject(result); + return object; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + +} diff --git a/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/bean/FuiouCurType.java b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/bean/FuiouCurType.java new file mode 100644 index 0000000..705a53d --- /dev/null +++ b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/bean/FuiouCurType.java @@ -0,0 +1,34 @@ +package in.egan.pay.fuiou.bean;/** + * Created by Fuzx on 2017/1/24 0024. + */ + +import in.egan.pay.common.bean.CurType; + +/** + * @author Fuzx + * @create 2017 2017/1/24 0024 + */ +public enum FuiouCurType implements CurType { + + CNY("人民币"), + USD("美元"), + HKD("港币"), + MOP("澳门元"), + EUR("欧元"), + TWD("新台币"), + KRW("韩元"), + JPY("日元"), + SGD("新加坡元"), + AUD("澳大利亚元"); + + private String name; + + private FuiouCurType(String name) { + this.name = name; + } + @Override + public String getCurType(){ + return this.name(); + } + + } diff --git a/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/bean/FuiouTransactionType.java b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/bean/FuiouTransactionType.java new file mode 100644 index 0000000..6234211 --- /dev/null +++ b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/bean/FuiouTransactionType.java @@ -0,0 +1,20 @@ +package in.egan.pay.fuiou.bean; + +import in.egan.pay.common.bean.TransactionType; + +/** + * 微信交易类型 + * @author egan + * @email egzosn@gmail.com + * @date 2016/10/19 22:58 + */ +public enum FuiouTransactionType implements TransactionType { + B2B, + B2C + ; + + @Override + public String getType() { + return this.name(); + } +} diff --git a/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/utils/SimplePostRequestExecutor.java b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/utils/SimplePostRequestExecutor.java new file mode 100644 index 0000000..902025d --- /dev/null +++ b/pay-java-fuiou/src/main/java/in/egan/pay/fuiou/utils/SimplePostRequestExecutor.java @@ -0,0 +1,76 @@ +package in.egan.pay.fuiou.utils; + +import com.alibaba.fastjson.JSONObject; +import in.egan.pay.common.api.RequestExecutor; +import in.egan.pay.common.bean.result.PayError; +import in.egan.pay.common.exception.PayErrorException; +import in.egan.pay.common.util.XML; +import in.egan.pay.common.util.http.Utf8ResponseHandler; +import org.apache.http.Consts; +import org.apache.http.HttpHost; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicNameValuePair; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * @author egan + * @email egzosn@gmail.com + * @date 2016-5-18 14:09:01 + */ +public class SimplePostRequestExecutor implements RequestExecutor { + + @Override + public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, Object postEntity) throws PayErrorException, ClientProtocolException, IOException { + HttpPost httpPost = new HttpPost(uri); +// httpPost.setHeader("Content-Type","application/x-www-from-urlencoded"); + if (httpProxy != null) { + RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); + httpPost.setConfig(config); + } + if (postEntity instanceof Map) { + StringBuilder builder = new StringBuilder(); + Map pe = (Map) postEntity; + for (Object key : pe.keySet()) { + builder.append(key).append("=").append(pe.get(key)).append("&"); + } + if (builder.length() > 1) { + builder.deleteCharAt(builder.length() - 1); + } + StringEntity entity = new StringEntity(builder.toString(), Consts.UTF_8); +// entity.setContentType("application/x-www-from-urlencoded"); + httpPost.setEntity(entity); + } else if (postEntity instanceof String) { + + StringEntity entity = new StringEntity((String) postEntity, Consts.UTF_8); + httpPost.setEntity(entity); + }else if(postEntity instanceof List){ + //表单方式 + httpPost.setEntity(new UrlEncodedFormEntity((List)postEntity, Consts.UTF_8)); + } + + try (CloseableHttpResponse response = httpclient.execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + System.out.println("直接返回的查询结果-->"+responseContent); +// responseContent = responseContent.replace("","").replace("","").replace("","").replace("",""); + JSONObject jsonObject = XML.toJSONObject(responseContent);//包含md5 + JSONObject plain = XML.toJSONObject(""+jsonObject.getString("plain")+"");//"plain" -> "5002验证签名失败" + + if("0000".equals(plain.getString("order_pay_code"))){ + return "0000"; + }else{ + throw new PayErrorException(new PayError(404,plain.getString("order_pay_error"),responseContent)); + } + }finally { + httpPost.releaseConnection(); + } + } +} \ No newline at end of file