这里记录一个例子,工厂模式的理论就不扯淡了。

遇到的问题:支付方式有很多种,比如微信支付 支付宝支付 银联支付 等等。我们在在实现的时候发现他么的流程上是相似的,以及每个方式都有大量的个性配置,在实例化时需要加载他们,以及为了清晰的讲调用方和实现方进行分离,就有来下面的小设计。

以银联为例,(通过测试)。

共用接口:

public interface CommonPay {

    /**
* <pre>
* 功能描述:
* 支付预处理:生成支付URL或提供给支付平台的数据
*
* @param param 预支付参数
* @return 支付URL或提供给支付平台的数据
* @throws Exception
*/
<T> PrepayDto<T> prePay(PrepayParam param) throws Exception;
}

工厂类:

public class CommonPayFactory implements InitializingBean {

    /** 银联支付 */
private CommonPay unionPay; @Override public void afterPropertiesSet() throws Exception {
if (unionPay == null) {
throw new RuntimeException("未配置任何支付实例");
}
} public CommonPay getUnionPay() {
return unionPay;
} public void setUnionPay(CommonPay unionPay) {
this.unionPay = unionPay;
}
}

我们利用spring,将bean初始化信息配置好:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="unionPayConfig" class="com.xxxxxx.cashier.web.union.UnionPayConfig">
<property name="payUrl" value="${pay.unionpay.payUrl}" />
<property name="queryUrl" value="${pay.unionpay.queryUrl}" />
<property name="merId" value="${pay.unionpay.merId}" />
<property name="signPfxFile" value="${pay.unionpay.signPfxFile}" />
<property name="signPfxPwd" value="${pay.unionpay.signPfxPwd}" />
<property name="verifySignCertFile" value="${pay.unionpay.verifySignCertFile}" />
<property name="payNotifyFrontUrl" value="${pay.unionpay.payNotifyFrontUrl}" />
<property name="payNotifyUrl" value="${pay.unionpay.payNotifyUrl}" />
</bean> <!-- 通用支付工厂实例 -->
<bean id="commonPayFactory" class="com.xxxxx.cashier.service.pay.CommonPayFactory">
<property name="unionPay">
<bean class="com.xxxxxx.cashier.service.pay.UnionPay">
<property name="UnionPayConfig" ref="unionPayConfig" />
</bean>
</property>
</bean>
</beans>

如此在项目启动时,银联实现会根据配置信息实例化,工厂类会自动加载入银联实例以备获取。

以下是银联实现:

public class UnionPay implements CommonPay {

    /** Logger */
private static final Logger LOGGER = LoggerFactory.getLogger(UnionPay.class); /** 日期时间格式 */
private static final String DATE_FORMAT_YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; /** 银联支付配置 */
private UnionPayConfig unionPayConfig; @Override public <T> PrepayDto<T> prePay(PrepayParam param) throws Exception { PrepayDto<String> prepayDto = new PrepayDto<>(); // 构造预下单签名参数
Map<String, String> params = new TreeMap<>(); /** 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 */
// 版本号,全渠道默认值
params.put("version", UnionPayConfig.VERSION);
// 字符集编码,可以使用UTF-8,GBK两种方式
params.put("encoding", UnionPayConfig.CHARSET);
// 签名方法,只支持 01:RSA方式证书加密
params.put("signMethod", "01");
// 交易类型 ,01:消费
params.put("txnType", "01");
// 交易子类型, 01:自助消费
params.put("txnSubType", "01");
// 业务类型,B2C网关支付,手机wap支付
params.put("bizType", "000201");
// 渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板 08:手机
params.put("channelType", "07"); /** 商户接入参数 */
// 商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
params.put("merId", unionPayConfig.getMerId());
// 接入类型,0:直连商户
params.put("accessType", "0");
// 商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
params.put("orderId", param.getOutTradeNo());
// 订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_YYYYMMDDHHMMSS);
Date orderAddTime = new Date();
String txtTime = sdf.format(orderAddTime);
params.put("txnTime", txtTime);
// 账号
// 1、 后台类消费交易时上送全卡号或卡号后4位
// 2、 跨行收单且收单机构收集银行卡信息时上送、
// 3、前台类交易可通过配置后返回,卡号可选上送
params.put("accNo", param.getCardNumber());
// 交易币种(境内商户一般是156 人民币)
params.put("currencyCode", "156");
// 交易金额,单位分,不要带小数点
params.put("txnAmt", param.getTotalFee());
// 请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,
// 对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节
params.put("reserved", "{cardNumberLock=1}"); // TODO
// 前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
// 如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
params.put("frontUrl", unionPayConfig.getPayNotifyFrontUrl());
// 后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
// 注意:1.需设置为外网能访问,否则收不到通知 2.http https均可
// 3.收单后台通知后需要10秒内返回http200或302状态码
// 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
// 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
params.put("backUrl", unionPayConfig.getPayNotifyUrl()); // 对请求参数拼接后进行消息摘要(SHA-1),然后用商户私钥进行签名
sign(params); // 生成表单HTML文档
String html = createAutoFormHtml(unionPayConfig.getPayUrl(), params, UnionPayConfig.CHARSET); prepayDto.setOrderId(param.getOrderId());
prepayDto.setOrderPaymentMethod(1);//TODO
prepayDto.setOutTradeNo(param.getOutTradeNo());
prepayDto.setResp(html);
return (PrepayDto<T>) prepayDto;
} private void sign(Map<String, String> params) {
// 证书ID 填写签名私钥证书的Serial Number,该值可通过银联提供的SDK获取
params.put("certId", unionPayConfig.getCertId()); // 对请求参数进行签名
String reqStr = PayUtil.mapToQueryStr(params, UnionPayConfig.CHARSET, false, true); // 将Map信息转换成key1=value1&key2=value2的形式
byte[] signDigest = SHA1Util.sha1X16(reqStr, UnionPayConfig.CHARSET); // SHA-1消息摘要
byte[] byteSign = RSA.signBySoft(unionPayConfig.getPrivateKey(), signDigest); // 用商户私钥签名
String sign = com.xiaoka.freework.utils.encrypt.Base64.encode(byteSign); // Base64编码
// 报文摘要的签名
params.put("signature", sign);
} private static String createAutoFormHtml(String reqUrl, Map<String, String> hiddens, String encoding) {
StringBuilder sb = new StringBuilder();
sb.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=").append(encoding).append("\"/></head><body>");
sb.append("<form id = \"pay_form\" action=\"").append(reqUrl).append("\" method=\"post\">");
if (null != hiddens && 0 != hiddens.size()) {
Set<Map.Entry<String, String>> set = hiddens.entrySet();
for (Map.Entry<String, String> ey : set) {
String key = ey.getKey();
String value = ey.getValue();
sb.append("<input type=\"hidden\" name=\"").append(key).append("\" id=\"")
.append(key).append("\" value=\"").append(value).append("\"/>");
}
}
sb.append("</form>");
sb.append("</body>");
sb.append("<script type=\"text/javascript\">");
sb.append("document.all.pay_form.submit();");
sb.append("</script>");
sb.append("</html>");
return sb.toString();
} public UnionPayConfig getUnionPayConfig() {
return unionPayConfig;
} public void setUnionPayConfig(UnionPayConfig unionPayConfig) {
this.unionPayConfig = unionPayConfig;
}
}

调用方只需获取银联实例调用方法即可:

        CommonPay unionPay = commonPayFactory.getUnionPay();

        PrepayParam prepayParam = new PrepayParam();
prepayParam.setOrderId(orderId);
prepayParam.setOutTradeNo(tradeNo);
prepayParam.setCardNumber(cardNo);
prepayParam.setTotalFee(amount.toString());
PrepayDto prepayDto = null;
try {
prepayDto = unionPay.prePay(prepayParam);
} catch (Exception e) {
logger.error("unionPay.prePay has error", e);
}

耐心点~

java工厂-积木系列的更多相关文章

  1. Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问

    本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...

  2. [转]Java多线程干货系列—(一)Java多线程基础

    Java多线程干货系列—(一)Java多线程基础 字数7618 阅读1875 评论21 喜欢86 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们 ...

  3. 推荐:Java性能优化系列集锦

    Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难.随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了.现代JVM持续演 ...

  4. java高并发系列 - 第5天:深入理解进程和线程

    进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.程序是指令.数据及其组织形式的描述,进程是程序的实体. 进程具有的 ...

  5. Java 数据持久化系列之池化技术

    在上一篇文章<Java 数据持久化系列之JDBC>中,我们了解到使用 JDBC 创建 Connection 可以执行对应的SQL,但是创建 Connection 会消耗很多资源,所以 Ja ...

  6. Java 数据持久化系列之 HikariCP (一)

    在上一篇<Java 数据持久化系列之池化技术>中,我们了解了池化技术,并使用 Apache-common-Pool2 实现了一个简单连接池,实验对比了它和 HikariCP.Druid 等 ...

  7. Java高并发系列——检视阅读

    Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...

  8. java工厂模式

    (1)概念大白话:java工厂模式就是客户端(main函数)要创建对象觉得麻烦就让另外一个叫工厂的类帮它创建,然后自己每次要创建对象就叫工厂帮它弄,举个例子,在没有工厂这个"手下" ...

  9. Java总结篇系列:Java String

    String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处. 1 ...

  10. Java总结篇系列:Java多线程(三)

    本文主要接着前面多线程的两篇文章总结Java多线程中的线程安全问题. 一.一个典型的Java线程安全例子 public class ThreadTest { public static void ma ...

随机推荐

  1. 深入理解iOS开发中的BitCode功能

    前言 做iOS开发的朋友们都知道,目前最新的Xcode7,新建项目默认就打开了bitcode设置.而且大部分开发者都被这个突如其来的bitcode功能给坑过导致项目编译失败,而这些因为bitcode而 ...

  2. css选择符

    E>F:子选择符,选择所有作为E元素的子元素F.<style type="text/css">li>a {color: #ccc;}</style&g ...

  3. WCF传输大数据的设置2

    本节主要内容:1.如何读取Binding中的binding元素.2.CustomBinding元素的基本配置.3.代码示例 一.Bingding是由binding元素构成的,可以根据实际需要,进行适当 ...

  4. gdb注意事项

    假设我当前有文件test.cpp,要gdb调试,先shell上执行:g++ test.cpp -o test -g 1. gdb调试针对的文件是debug模式的可执行文件. 上面-g参数即加上debu ...

  5. js图片拖放原理(很简单,不是框架,入门基础)

    <html> <meta> <script src='jquery-1.8.3.min.js'></script> <script> /* ...

  6. 使用rem缩放网页的javascript代码

    <script type="text/javascript"> (function(doc, win) { var docEl = doc.documentElemen ...

  7. jdbc escape

    假如有这样1个查询请求,模糊查询标题中包含a%b_cc’d的记录,正确的sql应该是下面这样的:      select * from t_sch_work_info t where  t.title ...

  8. Intellij IDEA 创建Web项目并在Tomcat中部署运行(不使用maven)【转载】

    原文链接:http://www.thinksaas.cn/topics/0/350/350000.html 一.创建Web项目 1.File -> New Module,进入创建项目窗口 2.选 ...

  9. 杭电ACM1004

    Let the Balloon Rise Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Oth ...

  10. AppCode 2016.2.3 发布,支持 Swift3 的特性

    AppCode 2016.2.3 (build 162.2380.5)发布了,AppCode 是一个全新的 Objective-C.Swift 的集成开发环境,用于帮助开发 Mac.iPhone 和 ...