第一次做银联支付的开发,无论是支付还是退款都走了不少弯路,银联支付的sdk和demo并不容易让人明白如何调用sdk。所以,想写两篇文章把开发的过程分享出来供大家参考。这篇文章讲一下银联支付的网关支付产品的前台交易类型的付款开发流程。闲话少叙,进入正题。
报文中包含以下信息:
1 | 版本号 | version | NS5 | M | 按规范填写。 | 固定填写 |
2 | 编码方式 | encoding | ANS1..20 | M | 填写报文使用的字符编码,UTF-8|GBK|GB2312|GB18030 若不填写,默认取值:UTF-8 |
可取值UTF-8或GBK |
3 | 证书ID | certId | N1..128 | M | 填写签名私钥证书的 Serial Number, 该值可通过银联提 供的 SDK 获取 |
|
4 | 签名方法 | signMethod | N2 | M | 非对称签名:01(表示采用 RSA 签名) | 固定填写:01(表示采用 RSA 签名) |
5 | 签名 | signature | ANS1..1024 | M | 填写对报文摘要的签名,可通过SDK生成签名 | 填写对报文摘要的签名,可通过SDK生成签名 |
6 | 交易类型 | txnType | N2 | M | 取值:00:查询交易,01:消费,02:预授权, 03:预授权完成,04:退货,05:圈存, 11:代收,12:代付, 13:账单支付,14:转账(保留),21:批量交易, 22:批量查询,31:消费撤销, 32:预授权撤销,33:预授权完成撤销, 71:余额查询,72:实名认证-建立绑定关系, 73:账单查询,74:解除绑定关系, 75:查询绑定关系,77:发送短信验证码交易, 78:开通查询交易,79:开通交易, 94:IC卡脚本通知 95:查询更新加密公钥证书 |
固定填写:01 |
7 | 交易子类 | txnSubType | N2 | M | 依据实际交易类型填写。默认取值:00 | 固定填写:01:自助消费, 通过地址的方式区 分前台消费 和后台消费(含无跳转 支付) |
8 | 产品类型 | bizType | N6 | M | 依据实际业务场景填写 (目前仅使用后 4 位,签名 2 位 默认为 00) 默认取值:000000 具体取值范围: 000201:B2C 网关支付 000301:认证支付 2.0 000302:评级支付 000401:代付 000501:代收 000601:账单支付 000801: 跨行收单 000901:绑定支付 001001:订购 000202:B2B |
000201 |
9 | 渠道类型 | channelType | N2 | M | 05:语音07:互联网08:移动 16:数字机顶盒 | 固定填写:07 |
接入类型 |
accessType | N1 | M | 0:商户直连接入 1:收单机构接入 2:平台商户接入 |
0:普通商户直连接入 1:收单机构接入 |
|
2 | 商户代码 | merId | AN15 | M | 已被批准加入银联互联网系统 的商户代码 |
|
3 | 前台通知地址 | frontUrl | ANS1..256 | C | 前台返回商户结果时使用, 前台类交易需上送 不支持换行符等不可见字符 |
前台返回商户结果时使用, 例:https://xxx.xxx.com/xxx |
4 | 后台通知地址 | backUrl | ANS1..256 | M | 后台返回商户结果时使用, 如上送,则发送商户后台交 易结果通知, 不支持换行符等不可见字符, 如需通过专 线通知, 需要在通知地址前面加上前缀: 专线的首字母 加竖线 ZX| |
后台返回商户结果时使用, 例:https://xxx.xxx.com/xxx |
5 | 失败交易 前台跳转地址 |
frontFailUrl | ANS1..256 | O | 前台消费交易若商户上送此字段, 则在支付失败时, 页面跳转至商户该URL (不带交易信息,仅跳转), 支持HTTP与HTTPS协议, 互联网可访问 |
前台消费交易若商户上送此字段, 则在支付失败时, 页面跳转至商户该URL (不带交易信息,仅跳转), 支持HTTP与HTTPS协议,互联网可访问 |
商户订单号 |
orderId | AN8..40 | M | 商户订单号,不应含“-”或“_” | 商户端生成,例:12345asdf | |
2 | 交易币种 | currencyCode | AN3 | M | 币种格式必须为3位代码, 境内客户取值:156(人民币) |
固定填写:156 |
3 | 交易金额 | txnAmt | N1..12 | M | 单位为分,不能带小数点,样例:1元送100 | 单位为分,例:1元填写100 |
4 | 订单发送时间 | txnTime | YYYYMMDDHHmmss | M | 必须使用当前北京时间 (年年年年月月日日时时分分秒秒)24小时制, 样例:20151123152540,北京时间 商户发送交易时间 |
商户发送交易时间,例:20151118100505 |
5 | 订单接收超时时间 | orderTimeout | N1..10 | O | 单位为毫秒,交易发生时,该笔交易在银联全渠道系统中有效的最长时间。 当距离交易发送时间超过该时间时,银联全渠道系统不再为该笔交易提供支付服务 |
1、前台类消费交易时上送 2、认证支付 2.0,后台交易时可选 |
6 | 支付超时时间 | payTimeout | YYYYMMDDHHmmss | O | 订单支付超时时间,超过此时间用户支付成功的交易, 不通知商户,系统自动退款, 大约 5 个工作日金额返还 到用户账户 |
非网银的交易:超过此时间未完成支付时银联页面会提示超时,不允许后续支付。 跳转网银的交易银联无法控制,超过此时间用户支付成功的交易,不通知商户,系统自动退款, 大约5个工作日金额返还到用户账户。 |
7 | 发卡机构代码 | issInsCode | AN1..20 | C | 当账号类型为 02-存折时需填写 在前台类交易时填写默认银行代码, 支持直接跳转到网 银 |
1、当帐号类型为02-存折时需填写 2、在前台类交易时填写默认银行代码,支持直接跳转到网银。 银行简码列表参考附录C.1、C.2,其中C.2银行列表仅支持借记卡,例:工行填写ICBC |
8 | 请求方自定义域 | reqReserved | ANS1..1024 | O | 商户自定义保留域, 交易应答时会原样返回 |
商户自定义保留域,交易应答时会原样返回 |
收单机构代码 |
acqInsCode | AN8..11 | C | 已被批准加入银联互联网系统的收单机构代码, 适用于收单机构接入测试场景。 在商户接入时不返回此域, 且上送报文中不应出现此域。 |
||
2 | 商户类别 | merCatCode | N4 | C | 填写MCC码,接入类型为收单机构接入时需上送 | |
3 | 商户名称 | merName | ANS1..40 | C | 接入类型为收单机构接入时需上送, 不支持换行符等不可见字符 |
|
4 | 商户简称 | merAbbr | ANS1..8 | C | 接入类型为收单机构接入时需上送最长 8 位, 不支持换行符等不可见字符 |
1 | 版本号 | version | NS5 | R | 按规范填写。 | |
2 | 编码方式 | encoding | ANS1..20 | R | 填写报文使用的字符编码, UTF-8|GBK|GB2312|GB18030 若不填写,默认取值:UTF-8 |
|
3 | 签名方法 | signMethod | N2 | M | 非对称签名:01(表示采用 RSA 签名) | |
4 | 签名 | signature | ANS1..1024 | M | 填写对报文摘要的签名,可通过SDK生成签名 | |
5 | 交易类型 | txnType | N2 | R | 取值: 00:查询交易,01:消费,02:预授权, 03:预授权完成,04:退货,05:圈存, 11:代收,12:代付,13:账单支付, 14:转账(保留),21:批量交易,22:批量查询, 31:消费撤销,32:预授权撤销,33:预授权完成撤销, 71:余额查询,72:实名认证-建立绑定关系, 73:账单查询,74:解除绑定关系,75:查询绑定关系, 77:发送短信验证码交易,78:开通查询交易, 79:开通交易,94:IC卡脚本通知 95:查询更新加密公钥证书 |
|
6 | 交易子类 | txnSubType | N2 | R | 依据实际交易类型填写。默认取值:00 | |
7 | 产品类型 | bizType | N6 | R | 依据实际业务场景填写(目前仅使用后 4 位,签名 2 位 默认为 00) 默认取值:000000 具体取值范围: 000201:B2C 网关支付 000301:认证支付 2.0 000302:评级支付 000401:代付 000501:代收 000601:账单支付 000801:跨行收单 000901:绑定支付 001001:订购 000202:B2B |
|
8 | 签名公钥 证书 | signPubKeyCert | AN2048 | C | 使用 RSA 签名方式时必选, 此域填写银联签名公钥证书 |
使用 RSA 签名方式时必选, 此域填写银联签名公钥证书。 |
1 | 接入类型 | accessType | N1 | R | 0:商户直连接入1:收单机构接入 2:平台商户接入 | |
2 | 商户代码 | merId | AN15 | R | 已被批准加入银联互联网系统的商户代码 |
1 | 商户订单号 | orderId | AN8..40 | R | 商户订单号,不应含“-”或“_” | |
2 | 交易币种 | currencyCode | AN3 | R | 币种格式必须为3位代码,境内客户取值:156(人民币) | 默认为 156 |
3 | 交易金额 | txnAmt | N1..12 | R | 单位为分,不能带小数点,样例:1元送100 | |
4 | 订单发送时间 | txnTime | YYYYMMDDHHmmss | R | 必须使用当前北京时间(年年年年月月日日时时分分秒秒) 24小时制,样例:20151123152540,北京时间 商户发送交易时间 |
|
5 | 支付方式 | payType | N4 | C | 默认不返回此域,如需要返此域,需要提交申请, 视商户配置返回,可在消费类交易中返回以下中的一种: 0001:认证支付 0002:快捷支付 0004:储值卡支付 0005:IC卡支付 0201:网银支付 1001:牡丹畅通卡支付 1002:中铁银通卡支付 0401:信用卡支付——暂定 0402:小额临时支付 0403:认证支付 2.0 0404:互联网订单手机支付 9000:其他无卡支付(如手机客户端支付) |
根据商户配置返回 |
6 | 账号 | accNo | AN1..1024 | C | 交易账号。请求时使用加密公钥对交易账号加密, 并做 Base64 编码后上送; 应答时如需返回,则使用签名私钥 进行解密。 前台交易可由银联页面采集,也可由商户上送并返显。 如需锁定返显卡号,应通过保留域(reserved)上送卡 号锁定标识。 |
根据商户配置返回 |
7 | 支付卡类型 | payCardType | N2 | C | 消费交易,视商户配置返回。该域取值为: 00:未知 01:借记账户 02:贷记账户 03:准贷记账户 04:借贷合一账户 05:预付费账户 06:半开放预付费账户 |
根据商户配置返回 |
8 | 请求方自定义域 | reqReserved | ANS1..1024 | R | 商户自定义保留域,交易应答时会原样返回 | |
9 | 保留域 | reserved | ANS1..2048 | O | 保留域包含多个子域,所有子域需用“{}”包含,子域间以“&”符号链接。 格式如下:{子域名1=值&子域名2=值&子域名3=值}。 相关子域说明见注 5 |
|
1 | 查询流水号 | queryId | AN21 | M | 由银联返回,用于在后续类交易中唯一标识一笔交易 | 消费交易的流水号,供后续查询用 |
2 | 应答码 | respCode | AN2 | M | 具体参见应答码定义章节 | |
3 | 应答信息 | respMsg | ANS1..256 | M | 填写具体的应答信息 | |
4 | 银联受理订单号 | tn | N21 | C | 商户推送订单后银联系统返回该流水号, 商户调用支付控件时使用 |
商户推送订单后银联移动支付 系统返回该流水号, 商户调用支付控件时使用 |
相关交易流程如下:
交易步骤:
1、持卡人/企业浏览商户网页,选择消费交易;
2、商户组织交易报文,通过浏览器跳转至银联全渠道系统的支付页面;
3、持卡人/企业在银联全渠道系统支付页面输入相关交易信息;
4、全渠道系统完成用户的交易处理;
5、银联全渠道系统,将交易受理结果返回给商户;
6、因消费交易涉及资金清算,银联系统发送后台通知(交易结果)给商户。
下面展示的代码为开发实例,代码展示顺序按照交易流程进行排列。
1、用户浏览页面,进行消费交易,对订单进行付款。页面提交订单信息表单的js代码
$(function () {
//支付提交表单
$("#paySubmit").click(function () {
$("#mainForm").submit();
});
});
2、接收订单数据的控制器代码
public void GotoPay()
{
Dictionary<string,string> param = new Dictionary<string,string>();
param["orderId"] = Request.Form["orderId"].ToString();//订单号
param["merId"] = merId; //商户号
decimal orderSum = IOrderService.GetMoney(param["orderId"]);
param["txnAmt"] = ((int)orderSum*100).ToString(); //订单金额,单位为分
string payType = Request.Form["payType"].ToString();//支付类型
if (payType==PayType.unionpay.ToString()) //银联支付
{
UnionpayB2C.Unionpay(param);
}
}
public static void Unionpay(Dictionary<string, string> data)
{
Dictionary<string, string> param = new Dictionary<string, string>();
//以下信息非特殊情况不需要改动
param["version"] = "5.0.0";//版本号
param["encoding"] = "UTF-8";//编码方式
param["txnType"] = "01";//交易类型
param["txnSubType"] = "01";//交易子类
param["bizType"] = "000201";//业务类型
param["signMethod"] = "01";//签名方法
param["channelType"] = "08";//渠道类型
param["accessType"] = "0";//接入类型
param["frontUrl"] = SDKConfig.FrontUrl; //前台通知地址
param["backUrl"] = SDKConfig.BackUrl; //后台通知地址
param["currencyCode"] = "156";//交易币种
//TODO 以下信息需要填写
param["merId"] = data["merId"];//商户号,请改自己的测试商户号,此处默认取demo演示页面传递的参数
param["orderId"] = data["orderId"];//商户订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数,可以自行定制规则
param["txnTime"] = DateTime.Now.ToString("yyyyMMddHHmmss");//订单发送时间,格式为YYYYMMDDhhmmss,取北京时间,此处默认取demo演示页面传递的参数,参考取法: DateTime.Now.ToString("yyyyMMddHHmmss")
param["txnAmt"] = data["txnAmt"];//交易金额,单位分,此处默认取demo演示页面传递的参数
//param["reqReserved"] = "透传信息";//请求方保留域,透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据
//TODO 其他特殊用法请查看 pages/api_01_gateway/special_use_purchase.htm
AcpService.Sign(param, System.Text.Encoding.UTF8);
string html = AcpService.CreateAutoFormHtml(SDKConfig.FrontTransUrl, param, System.Text.Encoding.UTF8);// 将SDKUtil产生的Html文档写入页面,从而引导用户浏览器重定向
HttpContext.Current.Response.ContentEncoding = Encoding.UTF8; // 指定输出编码
HttpContext.Current.Response.Write(html);
}
/// <summary>
/// 前台交易返回结果
/// </summary>
[HttpPost]
public ActionResult RcvResponse()
{
Dictionary<string, string> result = UnionpayB2C.FrontRcvResponse();
JsonDTO json = new JsonDTO()
{
Res = false,
Msg = "支付失败,请重新支付"
};
if (result!=null)
{
if (result[RespData.respCode.ToString()]==RespCode.success) //交易成功
{
OrderDetailDTO dto = new OrderDetailDTO() {
OrderNum = result[RespData.orderId.ToString()],
OrderStatus = EnumOrderStatus.WatingReceive,
PayTime = Convert.ToDateTime(result[RespData.txnTime.ToString()])
//OrderNum = "20170217102907590110902120",
//OrderStatus = EnumOrderStatus.WatingReceive,
//PayTime = DateTime.Now
};
json.Res=IOrderService.UpdateOrder(dto);
if (json.Res)
{
IOrderService.AddOrderPayInfo(new OrderPayInfoDTO {
OrderID = result[RespData.orderId.ToString()],
QueryID = result[RespData.queryId.ToString()],
TxnTime = result[RespData.txnTime.ToString()],
TxnAmt =int.Parse(result[RespData.txnAmt.ToString()]),
});
json.Msg = "支付成功";
}
return View(json);
}
else
{
return View(json);
}
}
return View(json);
}
/// <summary>
/// 接收银联返回的前台交易结果报文
/// </summary>
public static Dictionary<string, string> FrontRcvResponse()
{
if (HttpContext.Current.Request.HttpMethod == "POST")
{
// 使用Dictionary保存参数
Dictionary<string, string> resData = new Dictionary<string, string>();
NameValueCollection coll = HttpContext.Current.Request.Form;
string[] requestItem = coll.AllKeys;
for (int i = 0; i < requestItem.Length; i++)
{
resData.Add(requestItem[i], HttpContext.Current.Request.Form[requestItem[i]]);
}
// 返回报文中不包含UPOG,表示Server端正确接收交易请求,则需要验证Server端返回报文的签名
if (AcpService.Validate(resData, System.Text.Encoding.UTF8))
{
//Response.Write("商户端验证返回报文签名成功\n");
//string respcode = resData["respCode"]; //00、A6为成功,其余为失败。其他字段也可按此方式获取。
//如果卡号我们业务配了会返回且配了需要加密的话,请按此方法解密
//if(resData.ContainsKey("accNo"))
//{
// string accNo = SecurityUtil.DecryptData(resData["accNo"], System.Text.Encoding.UTF8);
//}
//商户端根据返回报文内容处理自己的业务逻辑 ,DEMO此处只输出报文结果
return resData;
}
else
{
log.Error("商户端验证银联返回报文结果:验证签名失败");
return null;
}
}
return null;
}