聚合支付-- 使用策略模式实现聚合支付流程(本文只包含对接多个支付平台的设计,不包含支付实现)

﹥>﹥吖頭↗ 提交于 2020-01-24 07:18:06

策略模式概要

策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。

角色的划分

这个模式涉及到三个角色:
● 环境(Context)角色:持有一个Strategy的引用。
● 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
● 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

策略模式实现聚合支付流程图

在这里插入图片描述

1、渠道表设计

#### 1、渠道表
```clike
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for payment_channel
-- ----------------------------
DROP TABLE IF EXISTS `payment_channel`;
CREATE TABLE `payment_channel` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `CHANNEL_NAME` varchar(32) NOT NULL COMMENT '渠道名称',
  `CHANNEL_ID` varchar(32) NOT NULL COMMENT '渠道ID',
  `MERCHANT_ID` varchar(32) NOT NULL COMMENT '商户id',
  `SYNC_URL` text NOT NULL COMMENT '同步回调URL',
  `ASYN_URL` text NOT NULL COMMENT '异步回调URL',
  `PUBLIC_KEY` text COMMENT '公钥',
  `PRIVATE_KEY` text COMMENT '私钥',
  `CHANNEL_STATE` int(11) DEFAULT '0' COMMENT '渠道状态 0开启1关闭',
  `REVISION` int(11) DEFAULT NULL COMMENT '乐观锁',
  `CREATED_BY` varchar(32) DEFAULT NULL COMMENT '创建人',
  `CREATED_TIME` datetime DEFAULT NULL COMMENT '创建时间',
  `UPDATED_BY` varchar(32) DEFAULT NULL COMMENT '更新人',
  `UPDATED_TIME` datetime DEFAULT NULL COMMENT '更新时间',
  `CLASS_ADDRES` varchar(255) DEFAULT NULL COMMENT '不同渠道实现类完整路径地址,通过反射调用实现',
  PRIMARY KEY (`ID`,`CHANNEL_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='支付渠道 ';

-- ----------------------------
-- Records of payment_channel
-- ----------------------------
INSERT INTO `payment_channel` VALUES ('1', '银联支付', 'yinlian_pay', '777290058110048', 'http://localhost:8080/ACPSample_B2C/frontRcvResponse', 'http://222.222.222.222:8080/ACPSample_B2C/backRcvResponse', null, null, '0', null, null, null, null, null, null);
INSERT INTO `payment_channel` VALUES ('2', '支付宝', 'ali_pay', '777666655522521', 'test', 'test', null, null, '0', null, null, null, null, null, null);

2、StrategyFactory(工厂,通过class路径获取具体的支付实现类)

渠道表 CLASS_ADDRES

public class StrategyFactory {

	private static Map<String, PayStrategy> strategyBean = new ConcurrentHashMap<String, PayStrategy>();

	/**
	 * 使用Java反射机制初始化类
	 * 
	 * @param classAddres
	 * @return
	 */
	public static PayStrategy getPayStrategy(String classAddres) {
		try {
			PayStrategy beanPayStrategy = strategyBean.get(classAddres);
			if (beanPayStrategy != null) {
				return beanPayStrategy;
			}
			Class<?> forName = Class.forName(classAddres);
			PayStrategy payStrategy = (PayStrategy) forName.newInstance();
			strategyBean.put(classAddres, payStrategy);
			return payStrategy;
		} catch (Exception e) {
			return null;
		}

	}
}

3、PayStrategy(支付接口)

1、渠道表数据
2、支付令牌 通过 toket 查询支付订单信息(需提前做好,这里没有)

public interface PayStrategy {

	/**
	 * 
	 * @param paymentChannel
	 *            渠道<br>
	 * @param payToken
	 *            支付令牌<br>
	 * @return
	 */
	String toPayHtml(PaymentChannelEntity paymentChannel, PayMentTransacDTO payMentTransacDTO);

}

4、UnionPayStrategy(支付实现–> 拼接银联html支付表单)

注意: orderId = 支付Id

@Slf4j
@Component
public class UnionPayStrategy implements PayStrategy {

	@Override
	public String toPayHtml(PaymentChannelEntity paymentChannel, PayMentTransacDTO payMentTransacDTO) {
		log.info(">>>>>>>>银联支付组装参数开始<<<<<<<<<<<<");

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

		/*** 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 ***/
		requestData.put("version", UnionPayBase.version); // 版本号,全渠道默认值
		requestData.put("encoding", UnionPayBase.encoding); // 字符集编码,可以使用UTF-8,GBK两种方式
		requestData.put("signMethod", SDKConfig.getConfig().getSignMethod()); // 签名方法
		requestData.put("txnType", "01"); // 交易类型 ,01:消费
		requestData.put("txnSubType", "01"); // 交易子类型, 01:自助消费
		requestData.put("bizType", "000201"); // 业务类型,B2C网关支付,手机wap支付
		requestData.put("channelType", "07"); // 渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板
												// 08:手机

		/*** 商户接入参数 ***/
		String merchantId = paymentChannel.getMerchantId();
		requestData.put("merId", merchantId); // 商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
		requestData.put("accessType", "0"); // 接入类型,0:直连商户
		String paymentId = payMentTransacDTO.getPaymentId();
		requestData.put("orderId", paymentId); // 商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
		requestData.put("txnTime", format(payMentTransacDTO.getCreatedTime())); // 订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
		requestData.put("currencyCode", "156"); // 交易币种(境内商户一般是156 人民币)
		Long payAmount = payMentTransacDTO.getPayAmount();
		requestData.put("txnAmt", payAmount + ""); // 交易金额,单位分,不要带小数点
		// requestData.put("reqReserved", "透传字段");
		// //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。

		requestData.put("riskRateInfo", "{commodityName=测试商品名称}");

		// 前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
		// 如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
		// 异步通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知\
		String syncUrl = paymentChannel.getSyncUrl();
		requestData.put("frontUrl", syncUrl);

		// 后台通知地址(需设置为【外网】能访问 http
		// https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
		// 后台通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知
		// 注意:1.需设置为外网能访问,否则收不到通知 2.http https均可 3.收单后台通知后需要10秒内返回http200或302状态码
		// 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
		// 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d
		// 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
		String asynUrl = paymentChannel.getAsynUrl();
		requestData.put("backUrl", asynUrl);

		// 订单超时时间。
		// 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。
		// 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。
		// 此时间建议取支付时的北京时间加15分钟。
		// 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。
		requestData.put("payTimeout",
				new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 * 60 * 1000));

		//////////////////////////////////////////////////
		//
		// 报文中特殊用法请查看 PCwap网关跳转支付特殊用法.txt
		//
		//////////////////////////////////////////////////

		/** 请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面 **/
		Map<String, String> submitFromData = AcpService.sign(requestData, UnionPayBase.encoding); // 报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。

		String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl(); // 获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
		String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, UnionPayBase.encoding); // 生成自动跳转的Html表单

		LogUtil.writeLog("打印请求HTML,此为请求报文,为联调排查问题的依据:" + html);
		// 将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
		return html;
	}

	private String format(Date timeDate) {
		String date = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(timeDate);
		return date;
	}

}

5、AilPayStrategy(支付实现–> 拼接支付宝html支付表单)

@Slf4j
@Component
public class UnionPayStrategy implements PayStrategy {

	@Override
	public String toPayHtml(PaymentChannelEntity paymentChannel, PayMentTransacDTO payMentTransacDTO) {
		log.info(">>>>>>>>支付宝支付组装参数开始<<<<<<<<<<<<");
       return "支付宝支付form"
}

6、PayContextService(策略控制接口)

public interface PayContextService {
	@GetMapping("/toPayHtml")
	public BaseResponse<JSONObject> toPayHtml(String channelId, String payToken);
}

7、PayContextServiceImpl(策略控制实现)

@RestController
public class PayContextServiceImpl extends BaseApiService<JSONObject> implements PayContextService {

	@Autowired
	private PaymentChannelMapper paymentChannelMapper;
	@Autowired
	private PayMentTransacInfoService payMentTransacInfoService;

	public BaseResponse<JSONObject> toPayHtml(String channelId, String payToken) {
		// 1.使用渠道id查询渠道信息
		PaymentChannelEntity pymentChannel = paymentChannelMapper.selectBychannelId(channelId);
		if (pymentChannel == null) {
			return setResultError("没有查询到该渠道信息");
		}

		// 2.使用payToken查询待支付信息
		BaseResponse<PayMentTransacDTO> tokenByPayMentTransac = payMentTransacInfoService
				.tokenByPayMentTransac(payToken);
		if (!isSuccess(tokenByPayMentTransac)) {
			return setResultSuccess(tokenByPayMentTransac.getMsg());
		}
		// 3.使用Java反射机制初始化子类
		String classAddres = pymentChannel.getClassAddres();
		PayStrategy payStrategy = StrategyFactory.getPayStrategy(classAddres);
		if (payStrategy == null) {
			return setResultError("支付系统网关错误!");
		}
		PayMentTransacDTO payMentTransacDTO = tokenByPayMentTransac.getData();
		// 4.直接执行子类实现方法
		String payHtml = payStrategy.toPayHtml(pymentChannel, payMentTransacDTO);
		JSONObject data = new JSONObject();
		data.put("payHtml", payHtml);
		return setResultSuccess(data);
	}

}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!