本文链接(包含源码示例)若需查看,请点击文章左下角的阅读原文。
起因
前段时间因为项目中需要实现支付宝手机网站支付功能,所以写下这篇文章以作记录,不足之处,欢迎指教。
后端框架:Laravel 5.5
业务功能
适用于商家在移动端网页应用中集成支付宝支付功能。商家在网页中调用支付宝提供的网页支付接口调起支付宝客户端内的支付模块,商家网页会跳转到支付宝中完成支付,支付完后跳回到商家网页内,最后展示支付结果。若无法唤起支付宝客户端,则在一定的时间后会自动进入网页支付流程。
一. 创建应用
链接:支付宝蚂蚁金服开放平台
注意:
需拥有实名认证的支付宝账户。
企业或个体工商户可申请
需要有真实有效的营业执照,切网站必须通过ICP备案
进入蚂蚁金服开放平台->开发者中心->网页&移动应用。按需求创建应用,在这里我创建的是网页/移动类应用。
创建完成后提交审核,大部分应用需要签约后才能使用,签约需要营业执照。
二. 配置应用环境
配置完成后,可提交审核,开发者点击提交审核后,预计会有一个工作日的审核时间。应用上线成功后,状态变为以上线,该状态下的应用能够调用生产环境的接口。
三. 接口调用配置
目前laravel中集成alipay SDK的支付接口很丰富。常用的有下面几种:
OmniPay-laravel:github OmniPay-laravel链接
latrell/alipay:github latrell/alipay链接
...
因为项目的需要,在这里我采用的是alipay的原生SDK包。
首先下载PHP版本的Demo:支付宝手机网站支付PHP demo
从index.php中可以看出该demo支持以下功能
手机网站2.0支付(接口名:alipay.trade.wap.pay)
手机网站2.0订单查询 (接口名:alipay.trade.query)
手机网站2.0订单退款 (接口名:alipay.trade.refund)
手机网站2.0订单退款查询(接口名:alipay.trade.fastpay.refund.query)
手机网站2.0账单下载(接口名:alipay.data.dataservice.bill.downloadurl.query)
其中config.php是配置文件:
<?php
$config = array (
//应用ID,您的APPID。
'app_id' => "",
//商户私钥,您的原始格式RSA私钥
'merchant_private_key' => "",
//异步通知地址
'notify_url' => "",
//http://工程公网访问地址/alipay.trade.wap.pay-PHP-UTF-8/notify_url.php
//同步跳转
'return_url' => "",
//http://mitsein.com/alipay.trade.wap.pay-PHP-UTF-8/return_url.php
// jk.mrwangqi.com
//编码格式
'charset' => "UTF-8",
//签名方式
'sign_type'=>"RSA2",
//支付宝网关
'gatewayUrl' => "https://openapi.alipay.com/gateway.do",
//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
'alipay_public_key' => "",
);
配置完成后,修改demo权限
sudo chmod -R 777 alipayDemo
访问demo下的index.php
这样子这个demo就可以运行了。
具体开发
现在下载SDK:支付宝手机网站支付PHP SDK
一. 引入SDK包
在laravel中引入SDK包的步骤:
在app/新建libs文件夹,将SDK包放在该目录下
2. 找到根目录下的composer.json文件,添加如下配置:
"autoload": {
"classmap": [
"database",
"app/libs/alipay" //这里是自定义包的文件位置,我将我项目中的该SDK包命名为alipay
],
"psr-4": {
"App\\": "app/"
}
},
3. 执行以下命令
composer dump-autoload //当在包中加入新的类,需要更新autoloader
二. 移动/新建文件
在alipay目录下新建wappay目录,在wappay目录下新建buildermodel和service两个目录。将上面demo目录下的wappay/buildermodel/AlipayTradeWapPayContentBuilder.php和wappay/service/AlipayTradeService.php两个文件分别复制到自己项目SDK包中新建的wappay中的相应目录下。
AlipayTradeWapPayContentBuilder.php是alipay demo对支付宝手机网站支付接口业务参数的封装。AlipayTradeService.php是alipay demo对支付宝手机网站支付接口业务功能的封装。
在SDK目录下新建log.txt。作为支付宝支付日志存放文件
三. 设置/引入命名空间
对AlipayTradeWapPayContentBuilder.php和AlipayTradeService.php设置命名空间,我设置的是
namespace App\libs\alipay\wappay\buildermodel;
namespace App\libs\alipay\wappay\buildermodel;
对alipay/aop/request/AlipayTradeWapPayRequest.php和alipay/aop/AopClient.php设置命名空间,我设置的是:
namespace App\libs\alipay\aop\request;
namespace App\libs\alipay\aop;
在AlipayTradeWapPayContentBuilder.php中引入上面两个命名空间:
use App\libs\alipay\aop\request\AlipayTradeWapPayRequest;
use App\libs\alipay\aop\AopClient;
将AlipayTradeService.php中的下面代码注释:
// require_once dirname ( __FILE__ ).DIRECTORY_SEPARATOR.'./../../AopSdk.php';
// require dirname ( __FILE__ ).DIRECTORY_SEPARATOR.'./../../config.php';
四. 配置config(alipay.php)
在上面中alipay的demo中是有一个config.php文件作为配置文件的,这里我们不需要这个文件,我们利用laravel的特性,在laravel项目目录下的config目录新建一个alipay.php:
return [
//应用ID,您的APPID。
'app_id' => "",
//商户私钥,您的原始格式RSA私钥
'merchant_private_key' => "",
//异步通知地址
'notify_url' => "",
//http://工程公网访问地址/alipay.trade.wap.pay-PHP-UTF-8/notify_url.php
//同步跳转
'return_url' => "",
//http://mitsein.com/alipay.trade.wap.pay-PHP-UTF-8/return_url.php
// jk.mrwangqi.com
//编码格式
'charset' => "UTF-8",
//签名方式
'sign_type'=>"RSA2",
//支付宝网关
'gatewayUrl' => "https://openapi.alipay.com/gateway.do",
//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
'alipay_public_key' => "",
];
五. 对应config修改函数
在alipay.php中进行配置支付接口所需参数。下面我们修改alipay/wappay/service/AlipayTradeService.php:
class AlipayTradeService {
//支付宝网关地址
public $gateway_url = "https://openapi.alipay.com/gateway.do";
//支付宝公钥
public $alipay_public_key;
//商户私钥
public $private_key;
//应用id
public $appid;
//编码格式
public $charset = "UTF-8";
public $token = NULL;
//返回数据格式
public $format = "json";
//签名方式
public $signtype = "RSA";
function __construct(){
$this->gateway_url = config('alipay.gatewayUrl'); //获得config文件夹下的alipay.php中的gatewayUrl参数,下同。
$this->appid = config('alipay.app_id');
$this->private_key = config('alipay.merchant_private_key');
$this->alipay_public_key = config('alipay.alipay_public_key');
$this->charset = config('alipay.charset');
$this->signtype= config('alipay.sign_type');
if(empty($this->appid)||trim($this->appid)==""){
throw new Exception("appid should not be NULL!");
}
if(empty($this->private_key)||trim($this->private_key)==""){
throw new Exception("private_key should not be NULL!");
}
if(empty($this->alipay_public_key)||trim($this->alipay_public_key)==""){
throw new Exception("alipay_public_key should not be NULL!");
}
if(empty($this->charset)||trim($this->charset)==""){
throw new Exception("charset should not be NULL!");
}
if(empty($this->gateway_url)||trim($this->gateway_url)==""){
throw new Exception("gateway_url should not be NULL!");
}
}
function AlipayWapPayService($alipay_config) {
$this->__construct($alipay_config);
}
/**
* alipay.trade.wap.pay
* @param $builder 业务参数,使用buildmodel中的对象生成。
* @param $return_url 同步跳转地址,公网可访问
* @param $notify_url 异步通知地址,公网可以访问
* @return $response 支付宝返回的信息
*/
function wapPay($builder,$return_url,$notify_url) {
$biz_content=$builder->getBizContent();
//打印业务参数
$this->writeLog($biz_content);
$request = new AlipayTradeWapPayRequest();
$request->setNotifyUrl($notify_url);
$request->setReturnUrl($return_url);
$request->setBizContent ( $biz_content );
// 首先调用支付api
$response = $this->aopclientRequestExecute ($request,true);
// $response = $response->alipay_trade_wap_pay_response;
return $response;
}
function aopclientRequestExecute($request,$ispage=false) {
$aop = new AopClient ();
$aop->gatewayUrl = $this->gateway_url;
$aop->appId = $this->appid;
$aop->rsaPrivateKey = $this->private_key;
$aop->alipayrsaPublicKey = $this->alipay_public_key;
$aop->apiVersion ="1.0";
$aop->postCharset = $this->charset;
$aop->format= $this->format;
$aop->signType=$this->signtype;
// 开启页面信息输出
$aop->debugInfo=true;
if($ispage)
{
$result = $aop->pageExecute($request,"post");
echo $result;
}
else
{
$result = $aop->Execute($request);
}
//打开后,将报文写入log文件
$this->writeLog("response: ".var_export($result,true));
return $result;
}
//请确保项目文件有可写权限,不然打印不了日志。
function writeLog($text) {
// $text=iconv("GBK", "UTF-8//IGNORE", $text);
//$text = characet ( $text );
file_put_contents ( dirname ( __FILE__ ).DIRECTORY_SEPARATOR."./../../log.txt", date ( "Y-m-d H:i:s" ) . " " . $text . "\r\n", FILE_APPEND );
}
}
?>
其他接口暂时用不到,所以在这里我将其隐去。
六. 新建控制器(AlipayController)
php artisan make:controller AlipayController
因为需要实现手机网站支付,所以需要定义支付接口:
<?php
namespace App\Http\Controllers\User\Alipay;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\libs\alipay\wappay\buildermodel\AlipayTradeWapPayContentBuilder;
use App\libs\alipay\wappay\service\AlipayTradeService;
class AlipayWapController extends Controller {
/**
*支付接口
*/
public function alipayWapPay(Request $request) {
$out_trade_no = getTradeNOString(); //公共方法生成唯一订单号
$subject = 'test'; //数据仅供测试,下同
$total_amount = 0.01;
$body = 'test test!';
$timeout_express="1m";
$payRequestBuilder = new AlipayTradeWapPayContentBuilder();
$payRequestBuilder->setBody($body);
$payRequestBuilder->setSubject($subject);
$payRequestBuilder->setOutTradeNo($out_trade_no);
$payRequestBuilder->setTotalAmount($total_amount);
$payRequestBuilder->setTimeExpress($timeout_express);
$payResponse = new AlipayTradeService();
$result=$payResponse->wapPay($payRequestBuilder,config('alipay.return_url'),config('alipay.notify_url'));
}
/**
*支付同步回调接口,在config/alipay.php的return_url参数进行配置
*/
public function alipayReturn() {
}
/**
*支付异步回调接口,在config/alipay.php的notify_url参数进行配置
*/
public function alipayNotify() {
}
}
七. 定义路由
定义支付路由及同步和异步回调路由
Route::group(['prefix' => 'alipay'],function() {
Route::get('wappay','AlipayWapController@alipayWapPay');
Route::get('return','AlipayWapController@alipayReturn');
Route::get('notify','AlipayWapController@alipayNotify');
});
要注意的一点是同步路由是GET形式调用,而异步路由是POST形式调用,在调用支付接口的时候会出现CSRF错误,现在最简单的方法是利用laravel的中间件避免CSRF,在app/Http/Middleware/VerifyCsrfToken.php中增加路由
protected $except = [
//
'alipay/pay',
'alipay/return',
'alipay/notify'
];
八. 修改冲突
这时就可以通过定义路由进行调用支付接口,但是在调用时会报下面这个错误:
Cannot redeclare Encrypt() (previously declared in .../vendor/laravel/lumen-framework/src/helpers.php:126)
//或:
Cannot redeclare Decrypt() (previously declared in .../vendor/laravel/lumen-framework/src/helpers.php:126)
这是因为Laravel 5使用Alipay SDK时,Laravel内带的加密解密函数Encrypt()/Decrypt()函数和Alipay SDK中的加密解密函数Encrypt()/Decrypt()函数命名冲突
解决方法:只需修改Alipay SDK中定义的函数名称,修改引用的函数名称。
修改步骤:
在Alipay SDK中,一共有需要修改三个文件的内容:
aop/AopEncrypt.php
aop/AopClient.php
lotusphp_runtime/Cookie/Cookie.php
在文件中查找encrypt/decrypt替换为alipayEncrypt/alipayDecrypt即可。
注:如果服务器是在Linux下,可能会报一个没有权限的错误,这是因为我们之前在SDK包中新建了一个log.txt,在alipay/wappay/service/AlipayTradeService.php中的writeLog()函数中向该文件写入支付日志时没有写入权限,给它个权限就好了。
结束
到此,在Laravel中支付宝手机网站支付功能就实现了,不足之处,欢迎请教。