银联支付笔记

636 阅读5分钟

银联支付相关笔记

银联开放平台:
	https://open.unionpay.com

商家中心:
	https://merchant.unionpay.com

ping++ 聚合支付(看着不错):
	https://www.pingxx.com/

参考文章:
	// 该文件非常不错,注册、测试整个流程都有,一步一步都有图文介绍
	https://blog.csdn.net/zyw_java/article/details/78167481

关于银联支付要求的前、后台通知链接,要求是外网地址,我们自己可以搭建 ngrok,或使用网上免费或付费产品,搭建 ngork 也很简单,查看笔记

总结下: 整个支付测试还是很简单的。

官网下载下来 PHP-SDK,然后部署到本地,访问各个页面即可测试接口。

需要注意的几点:
	1.sdk/acp_sdk.ini 配置文件里的:前、后台地址,证书文件,以及日志目录,需要替换成我们自己的本地路径

	2.接口的商户id号,需要替换成我们测试参数中的 "测试商户号"

	3.我测试过程中,一直报错:
		10报文格式错误[5500030]

		网上也找不到错误问题,有一篇文章提到了,好像是时区的问题。
		查看了下,本地因为新安装的 php,时区还真不对,修改 php 配置文件:
			php --ini		// 找到 php 配置文件
			vim /usr/local/etc/php/7.1/php.ini
			date.timezone = "Asia/Shanghai"		// 设置时区

测试完成了,项目代码中得实现整个银联支付,自己可能也能实现,但是不太优雅,在 composer 找有没有银联支付的包,找到一个: lokielse/omnipay-unionpay

github 上查找 payment 相关包: github.com/topics/paym…

发现 laravel 学院,也有该包的一个教程: laravelacademy.org/post/1492.h… laravelacademy.org/post/1475.h… packagist.org/packages/ig…

这里顺便过一下 omnipay-unionpay 笔记: github 地址: github.com/lokielse/om…

	Omnipay 是一个独立于框架、多网关的支付类库,支持 PHP 7.1+。Omnipay-unionpay 为 Omnipay 实现了对银联支付的支持。

Ominipay 地址:
	https://github.com/omnipay/omnipay

安装:
	composer require lokielse/omnipay-unionpay dev-master

基本用法:
	Ominipay-unionpay 提供了以下支付网关接口:
		Union_Wtz (Union No Redirect Payment) 银联无跳转支付(alpha)
		Union_Express (Union Express Payment) 银联全产品网关(PC,APP,WAP支付)
		Union_LegacyMobile (Union Legacy Mobile Payment) 银联老网关(APP)
		Union_LegacyQuickPay (Union Legacy QuickPay Payment) 银联老网关(PC)

使用:
	测试参数可以在银联商户平台查看:
		https://open.unionpay.com/ajweb/account/testPara

准备:
	如何获取 PrivateKey, PublicKey, Cert ID:
		1.准备好 cert.pfx、密码、verify_sign_acp.cer

		2.获取私钥
			openssl pkcs12 -in cert.pfx  -nocerts -nodes | openssl rsa -out private_key.pem

		3.公钥就是 verify_sign_acp.cer	

		4.获取 Cert ID
			openssl pkcs12 -in cert.pfx -clcerts -nokeys | openssl x509 -serial -noout // 得到 16 进制数
			visit https://lokielse.github.io/hex2dec // 将 16 进制数转换为 10 进制数

接口:
	Consume
		$gateway = Omnipay::create('UnionPay_Express');
		$gateway->setMerId($config['merId']);
		$gateway->setCertId($config['certId']);
		$gateway->setPrivateKey($config['privateKey']); // path or content
		$gateway->setReturnUrl($config['returnUrl']);
		$gateway->setNotifyUrl($config['notifyUrl']);

		$order = [
		    'orderId'   => date('YmdHis'), //Your order ID
		    'txnTime'   => date('YmdHis'), //Should be format 'YmdHis'
		    'orderDesc' => 'My order title', //Order Title
		    'txnAmt'    => '100', //Order Total Fee
		];

		//For PC/Wap
		$response = $gateway->purchase($order)->send();
		$response->getRedirectHtml();

		//For APP
		$response = $gateway->createOrder($order)->send();
		$response->getTradeNo();

	Return/Notify
		$gateway = Omnipay::create('UnionPay_Express');
		$gateway->setMerId($config['merId']);
		$gateway->setPublicKey($config['publicKey']); // path or content

		$response = $gateway->completePurchase(['request_params'=>$_REQUEST])->send();

		if ($response->isPaid()) {
		    //pay success
		}else{
		    //pay fail
		}

	Query Order Status
		$response = $gateway->query([
		    'orderId' => '20150815121214', //Your site trade no, not union tn.
		    'txnTime' => '20150815121214', //Order trade time
		    'txnAmt'  => '200', //Order total fee
		])->send();

		var_dump($response->isSuccessful());
		var_dump($response->getData());

	Consume Undo
		$response = $gateway->consumeUndo([
		    'orderId' => '20150815121214', //Your site trade no, not union tn.
		    'txnTime' => date('YmdHis'), //Regenerate a new time
		    'txnAmt'  => '200', //Order total fee
		    'queryId' => 'xxxxxxxxx', //Order total fee
		])->send();

		var_dump($response->isSuccessful());
		var_dump($response->getData());

	Refund
		// 注意:
		1. 银联退款时,必须加上 queryId, 
		2. 作为商户生成的订单号orderId与退款时的订单号是不一样的。也就意味着退款时的订单号必须重新生成。
		3. txnAmt 这个参数银联是精确到分的。直接返回元为单位的值,将会出现报错信息。
		// get the queryId first
		$response = $gateway->query([
		    'orderId' => '20150815121214', //Your site trade no, not union tn.
		    'txnTime' => '20150815121214', //Order trade time
		    'txnAmt'  => 200 * 100, //Order total fee; notice that: you should multiply the txnAmt by 100 with the Unionpay gateway. Such as 200 * 100;
		])->send();
		$queryId = ($response->getData())['queryId'];
		$response = $gateway->refund([
		    'orderId' => '20150815121215', //Your site trade no, not union tn. notice: this orderId must not be the same with the order's created orderId.
		    'txnTime' => date('YmdHis'), //Order trade time
		    'txnAmt'  => 200 * 100, //Order total fee; notice that: you should multiply the txnAmt by 100 with the Unionpay gateway. Such as 200 * 100;
		    'queryId' => $queryId
		])->send();

		var_dump($response->isSuccessful());
		var_dump($response->getData());

	File Transfer
		$response = $gateway->fileTransfer([
		    'txnTime'    => '20150815121214', //Order trade time
		    'settleDate' => '0119', //Settle Date
		    'fileType'   => '00', //File Type
		])->send();

		var_dump($response->isSuccessful());
		var_dump($response->getData());

laravel 还有一个 Ominipay 的封装包,是将 Ominipay 集成到 Laravel,并提供了一个简单的配置。 ignited/laravel-omnipay

github地址:
	https://github.com/ignited/laravel-omnipay

安装:
	composer require ignited/laravel-omnipay

	config/app.php 添加 provider 和 facade
		'providers' => [
			Ignited\LaravelOmnipay\LaravelOmnipayServiceProvider::class
		]

		'Omnipay' => Ignited\LaravelOmnipay\Facades\OmnipayFacade::class

	发布配置文件:
		php artisan vendor:publish --provider="Ignited\LaravelOmnipay\LaravelOmnipayServiceProvider" --tag=config

配置:
	config/laravel-ominipay.php

		return [

			// 支付使用的默认网关
			'default' => 'unionpay',

			// 各个支付网关
			'gateways' => [

		        // PayPal支付
				'paypal' => [
					'driver'  => 'PayPal_Express',
					'options' => [
						'solutionType'   => '',
						'landingPage'    => '',
						'headerImageUrl' => ''
					]
				],
			]
		];

使用:
	1.支付

        // 订单
        $order = array(
            'orderId' => '订单号',
            'txnTime' => date('YmdHis'),
            'title'   => '测试订单',
            'txnAmt'  => '订单价格',
        );

        // 调用支付(purchase - 查看 omnipay-unionpay 用法)
        $response = Omnipay::purchase($order)->send();

        // 触发银联支付跳转
        $response->redirect();

    2.同步/异步通知

        // 调用支付完成
        $response = Omnipay::completePurchase(['request_params' => $_REQUEST])->send();

        // 支付成功
        if($response->isPaid()){

        // 支付失败
        }else{

        }

        /*
        	注意,这里有个小坑:
        		我在测试支付回调时,异步签名认证成功!但是同步签名认证失败!

        	排查了半天,觉得程序没啥问题,后来在 github 上提问了作者:
        		https://github.com/lokielse/omnipay-unionpay/issues/27

        		将 $_REQUEST 改为 $_POST 即可($_REQUEST 可能包含了的内容)
         */

    3.切换其他网关
    	Omnipay::setGateway('unionpay');

    4.实例化网关
    	$gateway = Omnipay::gateway('paypal');
    	$gateway = Omnipay::gateway('unionpay');

总结下项目步骤: 1.安装 laravel-omnipay composer require ignited/laravel-omnipay

	config/app.php 添加 provider 和 facade
		'providers' => [
			Ignited\LaravelOmnipay\LaravelOmnipayServiceProvider::class
		]

		'Omnipay' => Ignited\LaravelOmnipay\Facades\OmnipayFacade::class

	发布配置文件:
		php artisan vendor:publish --provider="Ignited\LaravelOmnipay\LaravelOmnipayServiceProvider" --tag=config

2.安装 ominipay-unionpay
	composer require lokielse/omnipay-unionpay dev-master

3.配置银联支付
	1>获取私钥、公钥、证书ID 配置
		1)准备好 证书(cert.pfx)、证书密码(000000)、verify_sign_acp.cer

		2)获取私钥
			openssl pkcs12 -in cert.pfx  -nocerts -nodes | openssl rsa -out private_key.pem

		3)获取公钥
			公钥就是 verify_sign_acp.cer

		4)获取证书ID
			openssl pkcs12 -in cert.pfx -clcerts -nokeys | openssl x509 -serial -noout 		// 得到16进制

			访问:https://lokielse.github.io/hex2dec,将16进制转换为10进制

	2>创建银联支付证书目录:
		mkdir -p config/unionpay-cart/test/ 		// 测试环境证书配置目录
			private_key.pem 			// 私钥
			verify_sign_acp.cer 		// 公钥
			cert.pfx 					// 证书

		mkdir -p config/unionpay-cart/production/	// 生产环境证书配置目录

	3>配置 config/laravel-ominipay.php,银联支付:

		return [

			// 支付使用的默认网关
			'default' => 'unionpay',

			// 各个支付网关
			'gateways' => [

		        // PayPal支付
				'paypal' => [
					'driver'  => 'PayPal_Express',
					'options' => [
						'solutionType'   => '',
						'landingPage'    => '',
						'headerImageUrl' => ''
					]
				],

		        // 银联支付
		        'unionpay' => [
		            'driver' => 'UnionPay_Express',
		            'options' => [
		                'merId' => 'xxx',
		                'certId' => 'xxx',
		                'privateKey' => 'private_key.pem',
		                'publicKey' => 'verify_sign_acp.cer',
		                'certPath' => '700000000000001_acp.pfx',
		                'certPassword' => '000000',
		                'certDir' => 'xxx',
		                'returnUrl' => url('payment/unionpay/return'),
		                'notifyUrl' => url('payment/unionpay/notify'),
		            ]
		        ],
			]
		];

4.调用
	路由:
		web/wap端支付
		    Route::get('payment/unionpay/web-pay', 'PaymentUnionpayController@webPay');
		    Route::get('payment/unionpay/wap-pay', 'PaymentUnionpayController@wapPay');
		同步/异步通知
		    // 注意:银联的同步通知也是 POST 提交
		    Route::post('payment/unionpay/return', 'PaymentUnionpayController@return');
		    Route::post('payment/unionpay/notify', 'PaymentUnionpayController@notify');
		/*
			这里注意下 "同步通知":
				不同于一般的同步通知,是 'get' 请求,银联的是 'post' 请求
		 */

	中间件:
		VerifyCsrfToken 也得配置 '同步/异步通知' 排除 csrf token 认证

		    protected $except = [
		    	...
		        'payment/unionpay/return',      // 同步通知(银联的同步通知,也是 post 提交)
		        'payment/unionpay/notify',      // 异步通知
		    ];

	控制器:
	    public function webPay()
	    &&
	    public function wapPay()
	    {
	        $order = array(
	            'orderId' => '订单号',
	            'txnTime' => date('YmdHis'),
	            'title'   => '测试订单',
	            'txnAmt'  => '100',
	        );

	        $response = Omnipay::purchase($order)->send();
	        $response->redirect();
	    }


	    public function return()
	    &&
	    public function notify()
	    {

	        // 调用支付完成
	        $response = Omnipay::completePurchase(['request_params' => $_REQUEST])->send();

	        // 支付成功
	        if($response->isPaid()){

	        // 支付失败
	        }else{

	        }
	    }

5.其他
	1>目前 ominipay-unionpay 只支持 5.0.0 版本,想要使用 5.0.1 版本,需要我们自己来扩展

	2>看的几篇教程、还有官方文档,以及银联自身的 5.0.1 SDK,看的相当混乱,包括银联支付里该设置哪些配置,都很乱。
		需要我们来查看 ominipay-unionpay 源码,才能大概了解到底是怎么使用!