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

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

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

共用接口:

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. IT技术学习指导之Linux系统入门的4个阶段(纯干货带图)

    IT技术学习指导之Linux系统入门的4个阶段(纯干货带图) 全世界60%的人都在使用Linux.几乎没有人没有受到Linux系统的"恩惠",我们享受的大量服务(包括网页服务.聊天 ...

  2. Android中ListView的用法

    使用方法1 显示简单的文本 在layout文件中像加入普通控件一样在layout文件中引入ListView <ListView android:id="@+id/list_view&q ...

  3. Tomcat容器虚拟路径设置

    1.[官方文档]在tomcat\conf下server.xml中找到 <Host name="localhost" appBase="webapps" u ...

  4. CentOS 静态IP设置

    一.CentOS 修改IP地址修改对应网卡的IP地址的配置文件 # vi /etc/sysconfig/network-scripts/ifcfg-eth0   电信 # vi /etc/syscon ...

  5. 数组操作splice

    今天,实现某个功能时需要操作数组, var  array=[1,2,3,3,4]; console.log(array);           //结果: 1,2,3,3,4 var data=arr ...

  6. static的作用

    在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条. (1)先来介绍它的第一条也是最重要的一条:隐藏. 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有 ...

  7. How to realize the double os in the win8 background.

    Goal: to realize the Ubantu in the windows system os computer. Tools: Ubantu  install  USB MSD, Soft ...

  8. 010 winform

    2016-01-23 1.winform应用程序是一种智能客户端技术,我们可以使用winform应用程序帮助我们获得信息或者传输信息等. 2.属性Name:在后台要获得前台的控件对象,需要使用Name ...

  9. AFN断点续传思路

  10. U盘被写保护如何解除 (转)

    U盘被写保护如何解除   在使用U盘的时候,一直都很正常,但是突然有一天,U盘被提示被写保护了,不能够进行读写数据,如果这时正着急使用,一定会被气疯了吧,其实解决这个问题,是非常简单的. U盘被写保护 ...