银联在线支付商户UPMP接口的使用和说明
- 29509
- PHP
- 7
- super_dodo
- 2014/09/13
现在互联网支付日渐流行和成熟。除了支付宝支付外,传统的银联支付也走进很多商家的视野。毕竟现在一些传统用户还是以银联银行卡为主。银联的商家的API接口也成为网站开发的必备知识和技能。
银联开发的主要步骤是,先申请测试账户。使用测试账户进行开发和测试。提交测试报告。通过审核之后,分配给商家入户参数。根据如何参数小范围的修改和测试后,即可投入使用。
相关的文档可以参阅。http://202.101.25.178:8080/sim/docs/
这个接口文档还是比较完备的,相信很多技术人员稍加摸索,就能实现了。
我的是使用Yii的框架,我为了便捷的操作。把文档地址上面的目录结构多余的地方去除了。全部在一个文件夹下面(Upmp)。目录结构为
Upmp ----upmp_config.php ----upmp_core.php ----UpmpService.php ----notify_url.php
直接上代码:upmp_config.php
<?php
/**
* 类名:配置类
* 功能:配置类
* 类属性:公共类
* 版本:1.0
* 日期:2012-10-11
* 作者:中国银联UPMP团队
* 版权:中国银联
* 说明:以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己的需要,按照技术文档编写,并非一定要使用该代码。该代码仅供参考。
* */
class upmp_config
{
static $timezone = "Asia/Shanghai"; //时区
static $version = "2.1.4"; // 版本号(测试的时候用2.1.4版本)
static $charset = "UTF-8"; // 字符编码
static $sign_method = "MD5"; // 签名方法,目前仅支持MD5
static $mer_id = "8800000000*****"; // 商户号
static $security_key = "1nHFngmByQgZ5N71OrfO7yS*******"; // 商户密钥
static $mer_back_end_url = "http://pay.dodobook.net/api/upmp/successByUpmp"; // 后台通知地址
static $mer_front_end_url = "http://pay.dodobook.net/api/upmp/successByUpmp"; // 前台通知地址
static $upmp_trade_url = "http://202.101.25.178:8080/gateway/merchant/trade";
static $upmp_query_url = "http://202.101.25.178:8080/gateway/merchant/query";
const VERIFY_HTTPS_CERT = false;
const RESPONSE_CODE_SUCCESS = "00"; // 成功应答码
const SIGNATURE = "signature"; // 签名
const SIGN_METHOD = "signMethod"; // 签名方法
const RESPONSE_CODE = "respCode"; // 应答码
const RESPONSE_MSG = "respMsg"; // 应答信息
const QSTRING_SPLIT = "&"; // &
const QSTRING_EQUAL = "="; // =
}
?>
upmp_core.php
<?php
/**
* 类名:交易服务类
* 功能:接口公用函数类
* 版本:1.0
* 日期:2012-10-11
* 作者:中国银联UPMP团队
* 版权:中国银联
* 说明:以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己的需要,按照技术文档编写,并非一定要使用该代码。该代码仅供参考。
* */
require_once("upmp_config.php");
/**
* 除去请求要素中的空值和签名参数
* @param para 请求要素
* @return 去掉空值与签名参数后的请求要素
*/
function paraFilter($para) {
$result = array ();
while ( list ( $key, $value ) = each ( $para ) ) {
if ($key == upmp_config::SIGNATURE || $key == upmp_config::SIGN_METHOD || $value == "") {
continue;
} else {
$result [$key] = $para [$key];
}
}
return $result;
}
/**
* 生成签名
* @param req 需要签名的要素
* @return 签名结果字符串
*/
function buildSignature($req) {
$prestr = createLinkstring($req, true, false);
$prestr = $prestr.upmp_config::QSTRING_SPLIT.md5(upmp_config::$security_key);
return md5($prestr);
}
/**
* 把请求要素按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param para 请求要素
* @param sort 是否需要根据key值作升序排列
* @param encode 是否需要URL编码
* @return 拼接成的字符串
*/
function createLinkString($para, $sort, $encode) {
$linkString = "";
if ($sort){
$para = argSort($para);
}
while (list ($key, $value) = each ($para)) {
if ($encode){
$value = urlencode($value);
}
$linkString.=$key.upmp_config::QSTRING_EQUAL.$value.upmp_config::QSTRING_SPLIT;
}
//去掉最后一个&字符
$linkString = substr($linkString,0,count($linkString)-2);
return $linkString;
}
/**
* 对数组排序
* @param $para 排序前的数组
* return 排序后的数组
*/
function argSort($para) {
ksort($para);
reset($para);
return $para;
}
/*
* curl_call
*
* @url: string, curl url to call, may have query string like ?a=b
* @content: array(key => value), data for post
*
* return param:
* mixed:
* false: error happened
* string: curl return data
*
*/
function post($url, $content = null)
{
if (function_exists("curl_init")) {
$curl = curl_init();
if (is_array($content)) {
$data = http_build_query($content);
}
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $content);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_TIMEOUT, 60); //seconds
// https verify
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, upmp_config::VERIFY_HTTPS_CERT);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, upmp_config::VERIFY_HTTPS_CERT);
$ret_data = curl_exec($curl);
if (curl_errno($curl)) {
printf("curl call error(%s): %s\n", curl_errno($curl), curl_error($curl));
curl_close($curl);
return false;
}
else {
curl_close($curl);
return $ret_data;
}
} else {
throw new Exception("[PHP] curl module is required");
}
}
?>
UpmpService.php
<?php
/**
* 类名:接口处理核心类
* 功能:组转报文请求,发送报文,解析应答报文
* 版本:1.0
* 日期:2012-10-11
* 作者:中国银联UPMP团队
* 版权:中国银联
* 说明:以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己的需要,按照技术文档编写,并非一定要使用该代码。该代码仅供参考。
*/
require_once("upmp_core.php");
if (function_exists("date_default_timezone_set")) {
date_default_timezone_set(upmp_config::$timezone);
}
class UpmpService {
/**
* 交易接口处理
* @param req 请求要素
* @param resp 应答要素
* @return 是否成功
*/
static function trade($req, &$resp) {
$nvp = self::buildReq($req);
// file_put_contents('charge_req.txt', $nvp,FILE_APPEND); //写文件记录,用于提交请求的报文
$respString = post(upmp_config::$upmp_trade_url, $nvp);
// file_put_contents('aa.txt', $respString); //写文件记录用于报文信息
return self::verifyResponse($respString, $resp);
}
/**
* 交易查询处理
* @param req 请求要素
* @param resp 应答要素
* @return 是否成功
*/
static function query($req, &$resp) {
$nvp = self::buildReq($req);
// file_put_contents('charge_search.txt', $nvp,FILE_APPEND); //写文件记录,用于提交请求的报文
$respString = post(upmp_config::$upmp_query_url, $nvp);
// file_put_contents('bb.txt', $respString); //写文件记录用于报文信息
return self::verifyResponse($respString, $resp);
}
/**
* 拼接请求字符串
* @param req 请求要素
* @return 请求字符串
*/
static function buildReq($req) {
//除去待签名参数数组中的空值和签名参数
$filteredReq = paraFilter($req);
// 生成签名结果
$signature = buildSignature($filteredReq);
// 签名结果与签名方式加入请求
$filteredReq[upmp_config::SIGNATURE] = $signature;
$filteredReq[upmp_config::SIGN_METHOD] = upmp_config::$sign_method;
return createLinkstring($filteredReq, false, true);
}
/**
* 拼接保留域
* @param req 请求要素
* @return 保留域
*/
static function buildReserved($req) {
$prestr = "{".createLinkstring($req, true, true)."}";
return $prestr;
}
/**
* 应答解析
* @param respString 应答报文
* @param resp 应答要素
* @return 应答是否成功
*/
static function verifyResponse($respString, &$resp) {
if ($respString != ""){
parse_str($respString, $para);
$signIsValid = self::verifySignature($para);
$resp = $para;
if ($signIsValid) {
return true;
}else {
return false;
}
}
}
/**
* 异步通知消息验证
* @param para 异步通知消息
* @return 验证结果
*/
static function verifySignature($para) {
$respSignature = $para[upmp_config::SIGNATURE];
// 除去数组中的空值和签名参数
$filteredReq = paraFilter($para);
$signature = buildSignature($filteredReq);
if ("" != $respSignature && $respSignature==$signature) {
return true;
}else {
return false;
}
}
}
?>
修改成分配给你的参数。至此基本上这个包已经完整了。上面还有一些写文件的操作。用于填写报文。
调用的方法和成功的回调响应的接口
//会员充值到账户余额--使用银联--生成订单(简易版本)
public function actionChargeByUpmp() {
$request = Yii::app()->request;
Yii::import('application.extensions.Upmp.*'); //引入Upmp
$member_id = $request->getParam('mid'); //会员ID
$number = $request->getParam('number'); //会员充值的金额
//加入对会员的安全状态各方面的检测和处理.......
$transaction = Yii::app()->db->beginTransaction(); //使用事务能保证尽可能少的产生脏数据
try{
//先本地生成订单,生成订单ID
$billList = new MemberBillList;
$billList->member_id = $member_id;
$billList->number = $number; //充值金额
$billList->in_out = '1'; //会员支出
$billList->in_out_type = '2'; //会员个人余额充值
$billList->add_time = time();
$billList->status = '2'; //账单状态未完成
$billList->remark = '会员银联充值'; //账单状态未完成
$billList->isNewRecord = true;
if(!$billList->save()){
throw new Exception('生成账单记录失败,'.$this->getModelFirstError($billList), 11);
}
//银联的这个账单号的长度是(8-40),我在文档上只看到了不大于40,没看到最小8.(后来询问才知道)
$orderNumber = date('Ymd').'MCHA'.$billList->id; //本地的账单ID,经过组装后用于提交给银联
//去服务器上生成流水号
$notify_url = 'http://pay.dodobook.net/api/upmp/successByChargeUpmp'; //通知的地址(成功之后每隔几分钟向该地址发送成功的通知)
$bankRt = $this->actionCreateBill($orderNumber,$number,$notify_url); //根据提交的信息去请求并生成流水号
if($bankRt['respCode'] != '00'){ //判断返回的状态
throw new Exception('银行接口返回失败,'.$bankRt['respMsg'], 10);
}
if(!$bankRt['tn']){ //判断流水号
throw new Exception('银行接口流水号未返回', 11);
}
//更新刚才的账单的信息,增加刚才账单和流水号等的对应(完整记录)
$bank_order_id = $bankRt['tn'];
$newBillList = MemberBillList::model()->findByPk($billList->id);
$newBillList->bank_tn_id = $bank_order_id; //银行流水号
$newBillList->bank_order_id = $orderNumber; //银行系统记录的账单号
if(!$newBillList->save()){
throw new Exception('更新流水号失败', 12);
}
$transaction->commit();
$json['id'] = date("YmdHis").'_'.$billList->id; //账单的ID
$json['bank_order_id'] = $bank_order_id; //银行流水号
// $this->_end_api('0','账单记录生成成功,账单状态为未完成.',$json);
}catch(Exception $e){
$transaction->rollback();
// $this->_end_api($e->getCode(),'生成账单记录失败',$e->getMessage());
}
}
//会员充值到账户余额--使用银联--回调方法
public function actionSuccessByChargeUpmp() {
$request = Yii::app()->request;
Yii::import('application.extensions.Upmp.*'); //引入Upmp
测试的时候把返回过来的回调的信息先写文本日志记录
$str = '';
foreach ($_POST as $k => $v) {
$str .= $k .'==='.$v.'<hr>';
}
file_put_contents('charge_upmp.txt', '交易时间'.date('Y-m-d H:i:s').$str,FILE_APPEND);
if($_POST['transStatus'] == '00'){
echo 'success';
}
exit();
/* //正式环境下的账单的处理
if (UpmpService::verifySignature($_POST)){// 服务器签名验证成功
//请在这里加上商户的业务逻辑程序代码
//获取通知返回参数,可参考接口文档中通知参数列表(以下仅供参考)
$transStatus = $_POST['transStatus'];// 交易状态
if ("" != $transStatus && "00"==$transStatus){
// 交易处理成功
$qn = $_POST['qn']; //查询流水号
$orderNumber = $_POST['orderNumber']; //商户订单号
$total_fee = $_POST['settleAmount'] / 100; //清算金额
$billList = MemberBillList::model()->find("bank_order_id = '{$orderNumber}'");
$bill_list_id = $billList->id; //账单记录ID
$memBillList = MemberBillList::model()->findByPk($bill_list_id);
if($memBillList->status == '1'){ //如果已处理过
echo 'success';
$this->_end_api('0','Sucess','Sucess'); //处理成功
}
$transaction = Yii::app()->db->beginTransaction();
try{
//bill_list_id
//total_fee 银联支付的金额
//更新会员账单记录表 member_bill_list
$memBillList = MemberBillList::model()->findByPk($bill_list_id);
$member_id = $memBillList->member_id; //消费的会员ID
$memBill = MemberBill::model()->find("member_id = '{$member_id}'");
$memBillList->status = '1'; //更新状态值为已完成
if(!$memBillList->save()){
$this->_end_api('1','更改会员账单记录表状态失败',$this->getModelFirstError($memBillList));
}
//更新会员账单--.............
$transaction->commit();
echo 'success'; //成功之后告诉银联,避免银联不断的发送信息过来,会尝试24小时,5次
$this->_end_api('0','success','success'); //处理成功
}catch(Exception $e){
$transaction->rollback();
echo "fail";
$this->_end_api('12','银联支付回调处理失败','');
}
}else {
file_put_contents('charge_upmp_err.txt', '失败:交易时间'.date('Y-m-d H:i:s').$str,FILE_APPEND);
}
echo "success";
}else {// 服务器签名验证失败
echo "fail";
$this->_end_api('1','交易失败','');
}
*/
}
//生成账单请求的公共方法
public function actionCreateBill($orderNumber='',$orderAmount='',$notify_url) {
header('Content-Type:text/html;charset=utf-8');
Yii::import('application.extensions.Upmp.*'); //引入Upmp
//需要填入的部分
$req['version'] = upmp_config::$version; // 版本号
$req['charset'] = upmp_config::$charset; // 字符编码
$req['transType'] = "01"; // 交易类型
$req['merId'] = upmp_config::$mer_id; // 商户代码
$req['backEndUrl'] = $notify_url; // 通知URL
$req['frontEndUrl'] = $notify_url; // 前台通知URL(可选)
$req['orderDescription'] = "订单描述"; // 订单描述(可选)
$req['orderTime'] = date("YmdHis"); // 交易开始日期时间yyyyMMddHHmmss
$req['orderTimeout'] = ""; // 订单超时时间yyyyMMddHHmmss(可选)
$req['orderNumber'] = $orderNumber; //订单号(商户根据自己需要生成订单号)
$req['orderAmount'] = intval($orderAmount * 100); // 订单金额(人民币单位为分)
$req['orderCurrency'] = "156"; // 交易币种(可选)--人民币
$req['reqReserved'] = "透传信息"; // 请求方保留域(可选,用于透传商户信息)
// 保留域填充方法
$merReserved['test'] = "test";
$req['merReserved'] = UpmpService::buildReserved($merReserved); // 商户保留域(可选)
$resp = array ();
$validResp = UpmpService::trade($req, $resp);
// 商户的业务逻辑
if ($validResp){ // 服务器应答签名验证成功
return $resp;
}else { // 服务器应答签名验证失败
return $resp;
}
}
备注说明:
1.可以完全按照银联提供的接口和example来使用,有时候会报错,大多数情况下是目录结构和命名规范导致。自己改进。
2.测试报告的报文,都是需要状态为成功的才能通过。即测试的订单推送等都是需要完成的。
3.用户发出的请求报文,可以通过写文件操作记录。收到的应答报文也可以写文件记录。根据他们的示例。
4.银联这边的客服还是很不错的,毕竟加入了一个QQ群,都有专门的人负责及时的沟通和解决。不明白的地方,放心大胆得到问。
5.文档可能有进一步的改进,请到官网去查询相关的资料。此处只是个人的一个demo的记录。
6.欢迎大家交流和指点。
先天下之忧而忧,后天下之乐而乐.经济兴邦,心怀天下,不忘报国之志。
相关阅读
- 通过Google API客户端访问Google Play帐户报告PHP库
- PHP执行文件的压缩和解压缩方法
- 消息中间件MQ与RabbitMQ面试题
- 如何搭建一个拖垮公司的技术架构?
- Yii2中ElasticSearch的使用示例
热门文章
- 通过Google API客户端访问Google Play帐户报告PHP库
- PHP执行文件的压缩和解压缩方法
- 消息中间件MQ与RabbitMQ面试题
- 如何搭建一个拖垮公司的技术架构?
- Yii2中ElasticSearch的使用示例
最新文章
- 通过Google API客户端访问Google Play帐户报告PHP库
- PHP执行文件的压缩和解压缩方法
- 消息中间件MQ与RabbitMQ面试题
- 如何搭建一个拖垮公司的技术架构?
- Yii2中ElasticSearch的使用示例

