银联在线支付商户UPMP接口的使用和说明
- 28718
- 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的使用示例