支付项目之如何向快钱第三方提交账单
思考:
1.是否熟悉客户提交账单到如何转到第三方网关付钱接口平台上?
2.是否理解签名和验证签名?讲讲他们是怎么实现的?
一.快钱支付流程
文字描述:
首先,客户将自己的订单信息提交给商户,然后商户获得了客户订单的详细信息,根据第三方支付接口所需要客户信息,商品信息提交给第三方,然后第三方返回一个支付界面,客户在该页面完成订单的支付,然后第三方发送一个异步请求给商户告知商户订单完成。最后商户知道订单完成后就为客户提供相应的服务。
支付流程图:
从以上流程可知实现支付功能的关键点便是对第三方接口的调用。而调用第三方接口的关键点在于提交第三方平台所必须的订单参数和客户交易的关键信息。因此,服务端的关键是在处理用户提交的订单信息的同时如何将订单信息转化成第三方接口所必需要的参数。
二.实现订单提交给第三方平台快钱的具体操作
-
下载获取第三方快钱支付的模板代码,demo/java/网银支付/rmb/FI/WebRoot/FI/Fi_All
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@page import="Util.Pkipair"%> <% //人民币网关账号,该账号为11位人民币网关商户编号+01,该参数必填。 String merchantAcctId = "1001214035601";// //编码方式,1代表 UTF-8; 2 代表 GBK; 3代表 GB2312 默认为1,该参数必填。 String inputCharset = "1"; //接收支付结果的页面地址,该参数一般置为空即可。 String pageUrl = ""; //服务器接收支付结果的后台地址,该参数务必填写,不能为空。 String bgUrl = "http://192.168.46.250:8801/RMBPORT/receive.jsp"; //网关版本,固定值:v2.0,该参数必填。 String version = "v2.0"; //语言种类,1代表中文显示,2代表英文显示。默认为1,该参数必填。 String language = "1"; //签名类型,该值为4,代表PKI加密方式,该参数必填。 String signType = "4"; //支付人姓名,可以为空。 String payerName= "张三"; //支付人联系类型,1 代表电子邮件方式;2 代表手机联系方式。可以为空。 String payerContactType = "1"; //支付人联系方式,与payerContactType设置对应,payerContactType为1,则填写邮箱地址;payerContactType为2,则填写手机号码。可以为空。 String payerContact = "123456@qq.com"; //指定付款人,可以为空 String payerIdType = "3"; //付款人标识,可以为空 String payerId = "KQ33151000"; //付款人IP,可以为空 String payerIP = "192.168.1.1"; //商家的终端ip,支持Ipv4和Ipv6 String terminalIp = "192.168.1.1"; //网络交易平台简称,英文或中文字符串,除微信支付宝支付外其他交易方式必传 String tdpformName = "测试商户"; //商户订单号,以下采用时间来定义订单号,商户可以根据自己订单号的定义规则来定义该值,不能为空。 String orderId = "KQ"+new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date()); //订单金额,金额以“分”为单位,商户测试以1分测试即可,切勿以大金额测试。该参数必填。 String orderAmount = "1"; //订单提交时间,格式:yyyyMMddHHmmss,如:20071117020101,不能为空。 String orderTime = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date()); //快钱时间戳,格式:yyyyMMddHHmmss,如:20071117020101, 可以为空 String orderTimestamp= new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());; //商品名称,不可为空。 String productName= "Apple"; //商品数量,不可为空。 String productNum = "1"; //商品代码,可以为空。 String productId = "10000"; //商品描述,可以为空。 String productDesc = "Apple"; //扩展字段1,商户可以传递自己需要的参数,支付完快钱会原值返回,可以为空。 String ext1 = "扩展1"; //扩展自段2,商户可以传递自己需要的参数,支付完快钱会原值返回,可以为空。 String ext2 = "扩展2"; //支付方式,一般为00,代表所有的支付方式。如果是银行直连商户,该值为10-1或10-2,必填。 String payType = "00"; //银行代码,如果payType为00,该值可以为空;如果payType为10-1或10-2,该值必须填写,具体请参考银行列表。 String bankId = ""; //同一订单禁止重复提交标志,实物购物车填1,虚拟产品用0。1代表只能提交一次,0代表在支付不成功情况下可以再提交。可为空。 String redoFlag = "0"; //快钱合作伙伴的帐户号,即商户编号,可为空。 String pid = ""; //00:代表前台提交01:代表后台提交,为空默认为前台提交。 String submitType = ""; //订单超时时间(秒),可为空。 String orderTimeOut = ""; //终端IP,不可为空,但不参与拼接。 String terminalIp="192.168.12.13"; //网络交易平台简称,不可为空,但不参与拼接。 String tdpformName=""; //附加信息类型 固定值为NB2(分账时使用) String extDataType = "NB2"; //附加信息(分账时使用) String extDataContent = "<NB2>{\"sharingInfo\":{\"sharingFlag\":\"1\",\"feeMode\":\"0\",\"feePayer\":\"HAT_10012140356\",\"sharingData\":\"2^HAT_10012140356^8^0^分账方1|2^HAT_10012138334^2^0^分账方2\"}}</NB2>";// // signMsg 签名字符串 不可空,生成加密签名串 String signMsgVal = ""; signMsgVal = appendParam(signMsgVal, "inputCharset", inputCharset); signMsgVal = appendParam(signMsgVal, "pageUrl", pageUrl); signMsgVal = appendParam(signMsgVal, "bgUrl", bgUrl); signMsgVal = appendParam(signMsgVal, "version", version); signMsgVal = appendParam(signMsgVal, "language", language); signMsgVal = appendParam(signMsgVal, "signType", signType); signMsgVal = appendParam(signMsgVal, "merchantAcctId",merchantAcctId); signMsgVal = appendParam(signMsgVal, "payerName", payerName); signMsgVal = appendParam(signMsgVal, "payerContactType",payerContactType); signMsgVal = appendParam(signMsgVal, "payerContact", payerContact); signMsgVal = appendParam(signMsgVal, "payerIdType", payerIdType); signMsgVal = appendParam(signMsgVal, "payerId", payerId); signMsgVal = appendParam(signMsgVal, "payerIP", payerIP); signMsgVal = appendParam(signMsgVal, "orderId", orderId); signMsgVal = appendParam(signMsgVal, "orderAmount", orderAmount); signMsgVal = appendParam(signMsgVal, "orderTime", orderTime); signMsgVal = appendParam(signMsgVal, "orderTimestamp", orderTimestamp); signMsgVal = appendParam(signMsgVal, "productName", productName); signMsgVal = appendParam(signMsgVal, "productNum", productNum); signMsgVal = appendParam(signMsgVal, "productId", productId); signMsgVal = appendParam(signMsgVal, "productDesc", productDesc); signMsgVal = appendParam(signMsgVal, "ext1", ext1); signMsgVal = appendParam(signMsgVal, "ext2", ext2); signMsgVal = appendParam(signMsgVal, "payType", payType); signMsgVal = appendParam(signMsgVal, "bankId", bankId); signMsgVal = appendParam(signMsgVal, "redoFlag", redoFlag); signMsgVal = appendParam(signMsgVal, "pid", pid); signMsgVal = appendParam(signMsgVal, "submitType", submitType); signMsgVal = appendParam(signMsgVal, "orderTimeOut", orderTimeOut); signMsgVal = appendParam(signMsgVal, "period", period); signMsgVal = appendParam(signMsgVal, "extDataType", extDataType); signMsgVal = appendParam(signMsgVal, "extDataContent", extDataContent); System.out.println(signMsgVal); Pkipair pki = new Pkipair(); String signMsg = pki.signMsg(signMsgVal); %> <%!public String appendParam(String returns, String paramId, String paramValue) { if (returns != "") { if (paramValue != "") { returns += "&" + paramId + "=" + paramValue; } } else { if (paramValue != "") { returns = paramId + "=" + paramValue; } } return returns; }%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title></title> </head> <body> <div align="center"> <h2 align="center">提交到快钱页面</h2> <font color="#ff0000">(该页面仅做参考)</font> <table width="500" border="1" style="border-collapse: collapse" bordercolor="green" align="center"> <tr> <td align="center"> 订单号 </td> <td align="center"> <%=orderId%> </td> </tr> <tr> <td align="center"> 订单金额 </td> <td align="center"> <%=orderAmount%> </td> </tr> <tr> <td align="center"> 下单时间 </td> <td align="center"> <%=orderTime%> </td> </tr> <tr> <td align="center"> 商品名称 </td> <td align="center"> <%= productName%> </td> </tr> <tr> <td align="center"> 商品数量 </td> <td align="center"> <%= productNum%> </td> </tr> <tr> <td align="center"> 商户编号 </td> <td align="center"> <%= merchantAcctId%> </td> </tr> <tr> <td align="center"> 支付方式(payType) </td> <td align="center"> <%= payType%> </td> </tr> </table> </div> <div align="center" style="font-weight: bold;"> <form name="kqPay" action="https://sandbox.99bill.com/gateway/recvMerchantInfoAction.htm" method="get"> <input type="hidden" name="inputCharset" value="<%=inputCharset%>" /> <input type="hidden" name="pageUrl" value="<%=pageUrl%>" /> <input type="hidden" name="bgUrl" value="<%=bgUrl%>" /> <input type="hidden" name="version" value="<%=version%>" /> <input type="hidden" name="language" value="<%=language%>" /> <input type="hidden" name="signType" value="<%=signType%>" /> <input type="hidden" name="signMsg" value="<%=signMsg%>" /> <input type="hidden" name="merchantAcctId" value="<%=merchantAcctId%>" /> <input type="hidden" name="payerName" value="<%=payerName%>" /> <input type="hidden" name="payerContactType" value="<%=payerContactType%>" /> <input type="hidden" name="payerContact" value="<%=payerContact%>" /> <input type="hidden" name="payerIdType" value="<%=payerIdType%>" /> <input type="hidden" name="payerId" value="<%=payerId%>" /> <input type="hidden" name="payerIP" value="<%=payerIP%>" /> <input type="hidden" name="terminalIp" value="<%=terminalIp%>" /> <input type="hidden" name="tdpformName" value="<%=tdpformName%>" /> <input type="hidden" name="orderId" value="<%=orderId%>" /> <input type="hidden" name="orderAmount" value="<%=orderAmount%>" /> <input type="hidden" name="orderTime" value="<%=orderTime%>" /> <input type="hidden" name="orderTimestamp" value="<%=orderTimestamp%>" /> <input type="hidden" name="productName" value="<%=productName%>" /> <input type="hidden" name="productNum" value="<%=productNum%>" /> <input type="hidden" name="productId" value="<%=productId%>" /> <input type="hidden" name="productDesc" value="<%=productDesc%>" /> <input type="hidden" name="ext1" value="<%=ext1%>" /> <input type="hidden" name="ext2" value="<%=ext2%>" /> <input type="hidden" name="payType" value="<%=payType%>" /> <input type="hidden" name="bankId" value="<%=bankId%>" /> <input type="hidden" name="redoFlag" value="<%=redoFlag%>" /> <input type="hidden" name="pid" value="<%=pid%>" /> <input type="hidden" name="submitType" value="<%=submitType%>" /> <input type="hidden" name="orderTimeOut" value="<%=orderTimeOut%>" /> <input type="hidden" name="terminalIp" value="<%=terminalIp%>" /> <input type="hidden" name="tdpformName" value="<%=tdpformName%>" /> <input type="hidden" name="extDataType" value="<%=extDataType%>" /> <input type="hidden" name="extDataContent" value='<NB2>{"sharingInfo":{"sharingFlag":"1","feeMode":"0","feePayer":"HAT_10012140356","sharingData":"2^HAT_10012140356^8^0^分账方1|2^HAT_10012138334^2^0^分账方2"}}</NB2>'/> <input type="submit" name="submit" value="提交到快钱"> </form> </div> </body> </html>
-
将表单信息单独拿出来当成一个中转跳转页面。主要用来接受服务端处理完客户的订单信息。然后通过这个表单提交给第三方支付平台。
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>确认快钱支付</title> </head> <body> <div> <form name="kqPay" action="https://sandbox.99bill.com/gateway/recvMerchantInfoAction.htm" method="get"> <input type="hidden" name="inputCharset" th:value="${pay.inputCharset}" /> <input type="hidden" name="pageUrl" th:value="${pay.pageUrl}" /> <input type="hidden" name="bgUrl" th:value="${pay.bgUrl}" /> <input type="hidden" name="version" th:value="${pay.version}" /> <input type="hidden" name="language" th:value="${pay.language}" /> <input type="hidden" name="signType" th:value="${pay.signType}" /> <input type="hidden" name="signMsg" th:value="${pay.signMsg}" /> <input type="hidden" name="merchantAcctId" th:value="${pay.merchantAcctId}" /> <input type="hidden" name="payerName" th:value="${pay.payerName}" /> <input type="hidden" name="payerContactType" th:value="${pay.payerContactType}" /> <input type="hidden" name="payerContact" th:value="${pay.payerContact}" /> <input type="hidden" name="payerIdType" th:value="${pay.payerIdType}" /> <input type="hidden" name="payerId" th:value="${pay.payerId}" /> <input type="hidden" name="payerIP" th:value="${pay.payerIP}" /> <input type="hidden" name="terminalIp" th:value="${pay.terminalIp}" /> <input type="hidden" name="tdpformName" th:value="${pay.tdpformName}" /> <input type="hidden" name="orderId" th:value="${pay.orderId}" /> <input type="hidden" name="orderAmount" th:value="${pay.orderAmount}" /> <input type="hidden" name="orderTime" th:value="${pay.orderTime}" /> <input type="hidden" name="orderTimestamp" th:value="${pay.orderTimestamp}" /> <input type="hidden" name="productName" th:value="${pay.productName}" /> <input type="hidden" name="productNum" th:value="${pay.productNum}" /> <input type="hidden" name="productId" th:value="${pay.productId}" /> <input type="hidden" name="productDesc" th:value="${pay.productDesc}" /> <input type="hidden" name="ext1" th:value="${pay.ext1}" /> <input type="hidden" name="ext2" th:value="${pay.ext2}" /> <input type="hidden" name="payType" th:value="${pay.payType}" /> <input type="hidden" name="bankId" th:value="${pay.bankId}" /> <input type="hidden" name="redoFlag" th:value="${pay.redoFlag}" /> <input type="hidden" name="pid" th:value="${pay.pid}" /> <input type="hidden" name="submitType" th:value="${pay.submitType}" /> <input type="hidden" name="orderTimeOut" th:value="${pay.orderTimeOut}" /> <input type="hidden" name="extDataType" th:value="${pay.extDataType}" /> <input type="hidden" name="extDataContent" th:value="${pay.extDataContent}"/> //<input type="submit" value="支付快钱 "> </form> </div> </body> <script> document.forms[0].submit() </script>
-
建立一个dto用来接受服务端处理客户订单请求的接受类。
package com.dqw.domain; import cn.hutool.core.util.RandomUtil; import com.dqw.domain.query.GoodsForm; import com.dqw.util.kq.KqUtil; import com.dqw.util.kq.Pkipair; import lombok.Data; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; /** * */ @Data public class ApiParamValueDto { //人民币网关账号,该账号为11位人民币网关商户编号+01,该参数必填。 String merchantAcctId = "1001214035601";// //编码方式,1代表 UTF-8; 2 代表 GBK; 3代表 GB2312 默认为1,该参数必填。 String inputCharset = "1"; //接收支付结果的页面地址,该参数一般置为空即可。 String pageUrl = ""; //服务器接收支付结果的后台地址,该参数务必填写,不能为空。 String bgUrl = "http://192.168.46.250:8801/RMBPORT/receive.jsp"; //网关版本,固定值:v2.0,该参数必填。 String version = "v2.0"; //语言种类,1代表中文显示,2代表英文显示。默认为1,该参数必填。 String language = "1"; //签名类型,该值为4,代表PKI加密方式,该参数必填。 String signType = "4"; //支付人姓名,可以为空。 String payerName= ""; //支付人联系类型,1 代表电子邮件方式;2 代表手机联系方式。可以为空。 String payerContactType = "1"; //支付人联系方式,与payerContactType设置对应,payerContactType为1,则填写邮箱地址;payerContactType为2,则填写手机号码。可以为空。 String payerContact = "5601Haha@qq.com"; //指定付款人,可以为空 String payerIdType = "3"; //付款人标识,可以为空 String payerId = ""; //付款人IP,可以为空 String payerIP = "192.168.1.1"; //商家的终端ip,支持Ipv4和Ipv6 String terminalIp = "192.168.1.1"; //网络交易平台简称,英文或中文字符串,除微信支付宝支付外其他交易方式必传 String tdpformName = "大象科技"; //商户订单号,以下采用时间来定义订单号,商户可以根据自己订单号的定义规则来定义该值,不能为空。 String orderId = ""; //订单金额,金额以“分”为单位,商户测试以1分测试即可,切勿以大金额测试。该参数必填。 String orderAmount = ""; //订单提交时间,格式:yyyyMMddHHmmss,如:20071117020101,不能为空。 String orderTime = ""; //快钱时间戳,格式:yyyyMMddHHmmss,如:20071117020101, 可以为空 String orderTimestamp= ""; //商品名称,不可为空。 String productName= ""; //商品数量,不可为空。 String productNum = ""; //商品代码,可以为空。 String productId = ""; //商品描述,可以为空。 String productDesc = ""; //扩展字段1,商户可以传递自己需要的参数,支付完快钱会原值返回,可以为空。 String ext1 = ""; //扩展自段2,商户可以传递自己需要的参数,支付完快钱会原值返回,可以为空。 String ext2 = ""; //支付方式,一般为00,代表所有的支付方式。如果是银行直连商户,该值为10-1或10-2,必填。 String payType = "00"; //银行代码,如果payType为00,该值可以为空;如果payType为10-1或10-2,该值必须填写,具体请参考银行列表。 String bankId = ""; //同一订单禁止重复提交标志,实物购物车填1,虚拟产品用0。1代表只能提交一次,0代表在支付不成功情况下可以再提交。可为空。 String redoFlag = "0"; //快钱合作伙伴的帐户号,即商户编号,可为空。 String pid = ""; //00:代表前台提交01:代表后台提交,为空默认为前台提交。 String submitType = ""; //订单超时时间(秒),可为空。 String orderTimeOut = ""; //附加信息类型 固定值为NB2(分账时使用) String extDataType = ""; //附加信息(分账时使用) String extDataContent = ""; //分期 String period = ""; //签名字符串 String signMsg = ""; //使用构造初始必要的值 public ApiParamValueDto(GoodsForm goodsForm){ //付款人在商家的id this.payerId = String.valueOf(goodsForm.getPayerId()); //订单号: 时间戳 + 随机数(自增值) this.orderId="KQ" + new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + RandomUtil.randomNumbers(6); //订单金额分 this.orderAmount = new BigDecimal(goodsForm.getOrderMoney()) .multiply(new BigDecimal("100")).stripTrailingZeros().toPlainString(); this.orderTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); this.orderTimestamp = this.orderTime; this.productName = goodsForm.getProductName(); this.productNum = ""+goodsForm.getProductNum(); this.productId = goodsForm.getProductId(); this.productDesc = goodsForm.getProductDesc(); //签名字符串 this.signMsg = createSign(); } private String createSign() { // signMsg 签名字符串 不可空,生成加密签名串 String signMsgVal = ""; signMsgVal = KqUtil.appendParam(signMsgVal, "inputCharset", inputCharset); signMsgVal = KqUtil.appendParam(signMsgVal, "pageUrl", pageUrl); signMsgVal = KqUtil.appendParam(signMsgVal, "bgUrl", bgUrl); signMsgVal = KqUtil.appendParam(signMsgVal, "version", version); signMsgVal = KqUtil.appendParam(signMsgVal, "language", language); signMsgVal = KqUtil.appendParam(signMsgVal, "signType", signType); signMsgVal = KqUtil.appendParam(signMsgVal, "merchantAcctId",merchantAcctId); signMsgVal = KqUtil.appendParam(signMsgVal, "payerName", payerName); signMsgVal = KqUtil.appendParam(signMsgVal, "payerContactType",payerContactType); signMsgVal = KqUtil.appendParam(signMsgVal, "payerContact", payerContact); signMsgVal = KqUtil.appendParam(signMsgVal, "payerIdType", payerIdType); signMsgVal = KqUtil.appendParam(signMsgVal, "payerId", payerId); signMsgVal = KqUtil.appendParam(signMsgVal, "payerIP", payerIP); signMsgVal = KqUtil.appendParam(signMsgVal, "orderId", orderId); signMsgVal = KqUtil.appendParam(signMsgVal, "orderAmount", orderAmount); signMsgVal = KqUtil.appendParam(signMsgVal, "orderTime", orderTime); signMsgVal = KqUtil.appendParam(signMsgVal, "orderTimestamp", orderTimestamp); signMsgVal = KqUtil.appendParam(signMsgVal, "productName", productName); signMsgVal = KqUtil.appendParam(signMsgVal, "productNum", productNum); signMsgVal = KqUtil.appendParam(signMsgVal, "productId", productId); signMsgVal = KqUtil.appendParam(signMsgVal, "productDesc", productDesc); signMsgVal = KqUtil.appendParam(signMsgVal, "ext1", ext1); signMsgVal = KqUtil.appendParam(signMsgVal, "ext2", ext2); signMsgVal = KqUtil.appendParam(signMsgVal, "payType", payType); signMsgVal = KqUtil.appendParam(signMsgVal, "bankId", bankId); signMsgVal = KqUtil.appendParam(signMsgVal, "redoFlag", redoFlag); signMsgVal = KqUtil.appendParam(signMsgVal, "pid", pid); signMsgVal = KqUtil.appendParam(signMsgVal, "submitType", submitType); signMsgVal = KqUtil.appendParam(signMsgVal, "orderTimeOut", orderTimeOut); signMsgVal = KqUtil.appendParam(signMsgVal, "period", period); signMsgVal = KqUtil.appendParam(signMsgVal, "extDataType", extDataType); signMsgVal = KqUtil.appendParam(signMsgVal, "extDataContent", extDataContent); System.out.println(signMsgVal); Pkipair pki = new Pkipair(); String signMsg = pki.signMsg(signMsgVal); return signMsg; } }
关键地是通过案例的util工具类Pkipair 将订单信息生成签名字符串signMsg.
界面层向客端返回ApiParamValueDto对象,通过themyleaf模板引擎将相关参数赋值给对应的表单字段,通过表单向第三方提交支付相关的信息。
控制层代码:
@GetMapping("/confirm") public String confirm(GoodsForm goodsForm,Model model){ ApiParamValueDto paramValueDto = simpleOrderService.getApiParamValueSaveOrderRecord(goodsForm); //将数据放入页面 model.addAttribute("pay", paramValueDto); return "confirmorder"; }
业务逻辑层:
@Override public ApiParamValueDto getApiParamValueSaveOrderRecord(GoodsForm goodsForm) { //1.生成参数值 ApiParamValueDto apiParamValueDto = new ApiParamValueDto(goodsForm); //2.保存支付记录 SimpleOrder order = new SimpleOrder(); order.setOrderNo(apiParamValueDto.getOrderId()); order.setPayMoney(new BigDecimal(goodsForm.getOrderMoney())); order.setPayTime(new Date()); order.setUid(goodsForm.getPayerId()); order.setGoodsReplId(goodsForm.getProductId()); order.setRemark("创建支付记录"); order.setStatus(0); orderMapper.insert(order); return apiParamValueDto; }