fecshop整合微信支付的一个实现方法(能正常支付成功,但没有经过生产环境的测试不能保证安全性,仅供参考,欢迎指点改正)

技术分享 · alex · 于 6年前 发布 · 4235 次阅读

序言

随着微信越来越流行,自然使用微信支付方式的人也越来越多,电商网站为了方便用户应该支持多种支付方式,我尝试根据微信支付的官方文档和fecshop支付宝的实现方法,初步完成了fecshop中添加微信支付方式,经测试能正常支付成功,但没有经过生产环境的测试,不能保证安全性,仅供参考,欢迎指点改正,微信支付中所使用的帐号,appId,商户帐号等都是测试账号,但支付时用的是真实的钱(微信好像没提供支付宝那种可以用虚拟钱的沙箱帐号),我一般会把商品价格改成0.01元来测试。

具体实现方法 1.在fecshop/common/config/fecshop_local_services/Payment.php中的 paymentConfig.standard 中添加下面的配置:

'wxpay_standard' => [
            				'label'=> '微信支付',
            				// 跳转开始URL
            				'start_url'             => '@homeUrl/payment/wxpay/standard/start',
            				// 支付完成后,跳转的地址。
            				'return_url'            => '@homeUrl/payment/wxpay/standard/review',
            				// 微信发送消息,接收的地址。
            				'ipn_url'               => '@homeUrl/payment/wxpay/standard/ipn',
            				'success_redirect_url'  => '@homeUrl/payment/success',
            		],            		

2.新建微信支付的控制器文件,没有wxpay文件夹就创建 fecshop/vendor/fancyecommerce/fecshop/app/appfront/modules/Payment/controllers/wxpay/StandardController.php 代码如下:

<?php
/**
 * FecShop file.
 *
 * @link http://www.fecshop.com/
 * @copyright Copyright (c) 2016 FecShop Software LLC
 * @license http://www.fecshop.com/license/
 */

namespace fecshop\app\appfront\modules\Payment\controllers\wxpay;

use fecshop\app\appfront\modules\AppfrontController;
use Yii;

/**
 * @author Alex Chang<1692576541@qq.com>
 * @since 1.0
 */
class StandardController extends AppfrontController
{
    
    public $enableCsrfValidation = false;
    /**
     * 在下单页面中选择微信支付方式后,
     * 为微信支付做的准备工作。
     */
    public function actionStart()
    {
        //$AopSdkFile = Yii::getAlias('@fecshop/lib/wxpay/AopSdk.php');
        //require($AopSdkFile);
       // echo '微信支付跳转中...';
        return Yii::$service->payment->wxpay->start();
        
    }
    /**
     *  微信 支付成功后,调用该方法进行支付流程处理
     */
    public function actionReview($out_trade_no, $trade_no)
    {
        $reviewStatus = Yii::$service->payment->wxpay->review($out_trade_no, $trade_no);
        if ($reviewStatus)
        {
        	$successRedirectUrl = Yii::$service->payment->getStandardSuccessRedirectUrl();
        	//return Yii::$service->url->redirect($successRedirectUrl); //由支付页面的ajax跳转成功页面
        }
        else 
        {
        	echo Yii::$service->helper->errors->get('<br/>');
        	return ;
        }
    }
    
    
    /**
     * IPN,微信消息接收部分
     */
    public function actionIpn()
    {
        \Yii::info('wxpay ipn begin', 'fecshop_debug');
       
        $post = Yii::$app->request->post();
	 	if (is_array($post) && !empty($post))
	 	{
	 		\Yii::info('', 'fecshop_debug');
	 		$post = \Yii::$service->helper->htmlEncode($post);
	 		ob_start();
	 		ob_implicit_flush(false);
	 		var_dump($post);
	 		$post_log = ob_get_clean();
	 		\Yii::info($post_log, 'fecshop_debug');
	 		$ipnStatus = Yii::$service->payment->wxpay->receiveIpn($post);
	 		if ($ipnStatus)
	 		{
	 			echo 'success';
	 			return ;
	 		}
	 	}
    }
    /*
    public function actionCancel()
    {
        $innerTransaction = Yii::$app->db->beginTransaction();
		try {
            if(Yii::$service->order->cancel()){
                $innerTransaction->commit();
            }else{
                $innerTransaction->rollBack();
            }
		} catch (Exception $e) {
			$innerTransaction->rollBack();
		}
        return Yii::$service->url->redirectByUrlKey('checkout/onepage');
    }
    */
    
    /**
     * 废弃
     * 这里是第一次为了支付成功而尝试的简便方法,没有进行严格的检查
     * 判断微信支付是否成功,只要一个参数匹配即判断为不成功
     * @param unknown $out_trade_no
     */
    public function actionIs_trade_success_bak()
    {
    	if (empty($out_trade_no))
    	{
    		$out_trade_no =  Yii::$app->request->get('out_trade_no') ? Yii::$app->request->get('out_trade_no') : '0';
    	}
    	$result = Yii::$service->payment->wxpay->queryOrderByOut($out_trade_no);
    	//exit(print_r($result));
    	if ($result['mch_id'] == '1900009851'
    			&& $result['out_trade_no'] == $out_trade_no
    			&& $result['result_code'] == 'SUCCESS'
    			&& $result['trade_state'] == 'SUCCESS'
    			&& $result['trade_type'] == 'NATIVE'
    			&& $result['total_fee'] == '1')
    	{
    		$trade_no = $result['transaction_id'];
    		@$this->actionReview($out_trade_no, $trade_no);//执行交易成功后的处理,把参数$out_trade_no, $trade_no传过去
    		return json_encode([
    				'state'=> 1, 
    				'msg'=> 'success', 
    				'data'=> $result
    		]);
    		
    	}
    	else
    	{
    		//return false .'a';
    		return json_encode([
    				'state'=> -1,
    				'msg'=> 'wait',
    				'data'=> $result
    		]);
    	}
    }
    
    /**
     * 生成扫码支付的二维码
     */
    public function actionQrcode()
    {
    	error_reporting(E_ERROR);
    	$WxPay_example = Yii::getAlias('@fecshop/lib/Wxpay/example');
    	require_once($WxPay_example . '/phpqrcode/phpqrcode.php');
    	$url = urldecode($_GET["data"]);
    	\QRcode::png($url);
    }
    
   /**
     * 判断微信支付是否成功,只要一个参数不匹配即判断为不成功
     * @param unknown $out_trade_no
     */
    public function actionIs_trade_success()
    {
    	if (empty($out_trade_no))
    	{
    		$out_trade_no =  Yii::$app->request->get('out_trade_no') ? Yii::$app->request->get('out_trade_no') : '0';
    	}
    	$result = Yii::$service->payment->wxpay->queryOrderByOut($out_trade_no); //查询微信支付的交易结果
    	$trade_state = $result['trade_state']; //最终的交易状态,必须为SUCCESS才是交易成功
    	$return_code = $result['result_code'];
    	$trade_type = $result['trade_type']; //获取交易方式,这里使用的是扫码支付native
    	$out_trade_no = $result['out_trade_no'];
    	$total_amount = $result['total_fee'];
    	$seller_id = $result['mch_id'];
    	$auth_app_id = $result['appid'];
    	
    	$trade_no = $result['transaction_id'];
    	$res = Yii::$service->payment->wxpay->checkOrder($trade_state, $return_code, $trade_type, $out_trade_no,$total_amount,$seller_id,$auth_app_id);
    	if ($res === true)
    	{
    		@$this->actionReview($out_trade_no, $trade_no); //支付成功调用服务执行订单状态改变,清空购物车和发送邮件操作
    		return json_encode([
    				'state'=> 1,
    				'msg'=> 'success',
    		]);
    	}
    	else 
    	{
    		return json_encode([
    				'state'=> -1,
    				'msg'=> 'wait',
    		]);
    	}
    }
    
}

3.创建微信的service文件:fecshop/vendor/fancyecommerce/fecshop/services/payment/Wxpay.php 代码如下:

<?php
/**
 * FecShop file.
 *
 * @link http://www.fecshop.com/
 * @copyright Copyright (c) 2016 FecShop Software LLC
 * @license http://www.fecshop.com/license/
 */

namespace fecshop\services\payment;

//use fecshop\models\mysqldb\IpnMessage;
use fecshop\services\Service;
use Yii;
use Monolog\Handler\IFTTTHandler;

/**
 * Payment wxpay services.
 * @author Alex Chang<1692576541@qq.com>
 * @since 1.0
 */
class Wxpay extends Service
{
    public $gatewayUrl;
    // 商家 appid
    public $appId;
    // 商家uid
    public $sellerId;
    // 应用私钥
    public $rsaPrivateKey;
    // 微信公钥
    public $wxpayrsaPublicKey;
    public $format;
    public $charset;
    public $signType;
    
    public $devide;
    public $apiVersion = '1.0'; //'1.0';
    
    //protected $_returnUrl;
    //protected $_notifyUrl;
    protected $_AopClient;
    protected $_wxpayRequest;
    protected $_productCode;
    protected $_order;
    //交易创建,等待买家付款
    const WAIT_BUYER_PAY = 'WAIT_BUYER_PAY';
    //未付款交易超时关闭,或支付完成后全额退款
    const TRADE_CLOSED = 'TRADE_CLOSED';
    //交易支付成功
    const TRADE_SUCCESS = 'TRADE_SUCCESS';
    //交易结束,不可退款
    const TRADE_FINISHED = 'TRADE_FINISHED';
    
    protected $_ipnMessageModelName = '\fecshop\models\mysqldb\IpnMessage';
    protected $_ipnMessageModel;
    
    /**
     * 引入 微信支付的SDK文件。
     */
    public function init()
    {
        /* parent::init();
        $AopSdkFile = Yii::getAlias('@fecshop/lib/wxpay/AopSdk.php');
        require($AopSdkFile);
        list($this->_ipnMessageModelName,$this->_ipnMessageModel) = \Yii::mapGet($this->_ipnMessageModelName);   */
    	
    	//require_once "../lib/WxPay.Api.php";
    	//require_once "WxPay.NativePay.php";
    	//require_once 'log.php';
    	parent::init();
    	$WxPay_Api = Yii::getAlias('@fecshop/lib/Wxpay/lib/WxPay.Api.php');
    	$WxPay_NativePay = Yii::getAlias('@fecshop/lib/Wxpay/example/WxPay.NativePay.php');
    	$WxPay_log = Yii::getAlias('@fecshop/lib/Wxpay/example/log.php');
    	require($WxPay_Api);
    	require($WxPay_NativePay);
    	require($WxPay_log);
    	list($this->_ipnMessageModelName,$this->_ipnMessageModel) = \Yii::mapGet($this->_ipnMessageModelName);//把数组中的值一次赋予给list中的变量
    	
    	//$notify = new NativePay();
    }
   
  
    /**
     * 微信 支付成功后,返回网站,调用该函数进行微信订单支付状态查询
     * 如果支付成功,则修改订单状态为支付成功状态。
     * $out_trade_no  内部交易号码
     * $trade_no 微信交易号码
     */
    protected function actionReview($out_trade_no, $trade_no)
    {
        
        if (!empty($out_trade_no) && !empty($trade_no)) {
            $this->paymentSuccess($out_trade_no,$trade_no);
            // 清空购物车
            Yii::$service->cart->clearCartProductAndCoupon();
            
            return true;
        } else {
            Yii::$service->helper->errors->add('wxpay payment fail,resultCode:'.$resultCode);
            
            return false;
        }
        
    }

    /**
     * @property $increment_id | String 订单号
     * @property $sendEmail | boolean 是否发送邮件
     * 订单支付成功后,需要更改订单支付状态等一系列的处理。
     */
    protected function paymentSuccess($increment_id,$trade_no,$sendEmail = true)
    {
        if (!$this->_order) {
            $this->_order = Yii::$service->order->getByIncrementId($increment_id);
            Yii::$service->payment->setPaymentMethod($this->_order['payment_method']);
        }
        // 如果订单状态已经是processing,那么,不需要更改订单状态了。
        if ($this->_order['order_status'] == Yii::$service->order->payment_status_processing){
            
            return true;
        }
        $order = $this->_order;        
        if (isset($order['increment_id']) && $order['increment_id']) {
            // 如果支付成功,则更改订单状态为支付成功
            $order->order_status = Yii::$service->order->payment_status_processing;
            $order->txn_id = $trade_no; // 微信的交易号
            // 更新订单信息
            $order->save();
            // 得到当前的订单信息
            $orderInfo = Yii::$service->order->getOrderInfoByIncrementId($order['increment_id']);
            // 发送新订单邮件
        	Yii::$service->email->order->sendCreateEmail($orderInfo);
            return true;
        }
    }

    
    /**
     * 根据订单,将内容提交给微信。跳转到微信支付页面。
     * 在下单页面点击place order按钮,跳转到微信的时候,执行该函数。
     */
    public function start(){
        // 初始化参数
       // $this->initParam();
        // 根据wap 还是pc ,进行参数初始化
        if($this->devide == 'wap'){
            $this->_productCode     = 'QUICK_WAP_WAY';
        }else if($this->devide == 'pc'){
            $this->_productCode     = 'FAST_INSTANT_TRADE_PAY';
            //$this->_wxpayRequest   = new \wxpayTradePagePayRequest ();
        }else{
            Yii::$service->helper->errors->add('you must config param [devide] in payment wxpay service');
            return;
        }
        
        // 根据订单得到json格式的微信支付参数。
        $trade_info = $this->getStartBizContentAndSetPaymentMethod();
        if(! $trade_info){
            Yii::$service->helper->errors->add('generate wxpay bizContent error');
        }
        // 设置支付成功返回的url 和 支付消息接收url
        // 在调用这个函数之前一定要先设置 Yii::$service->payment->setPaymentMethod($payment_method);
        $returnUrl = Yii::$service->payment->getStandardReturnUrl(); //获取支付配置中的返回url
        $notifyUrl = Yii::$service->payment->getStandardIpnUrl();    ////获取支付配置中的返回ipn url
        /*
        echo $returnUrl;
        echo '#';
        echo $notifyUrl;
        echo '#';
        echo $bizContent;
        exit;
        */
       
       // return $this->_AopClient->pageExecute($this->_wxpayRequest); 
        $WxPay = Yii::getAlias('@fecshop/lib/Wxpay'); //设置别名方便native.php中来包含需要的文件
        $WxPay_example = Yii::getAlias('@fecshop/lib/Wxpay/example');
        $WxPay_native = Yii::getAlias('@fecshop/lib/Wxpay/example/native.php');
        
       // echo "</pre>";
       // print_r($trade_info);
       // exit;
        require_once($WxPay_native);
    }
    
    /**
     * 通过微信接口查询交易信息
     * @param unknown $out_trade_no
     */
    public function queryOrderByOut($out_trade_no)
    {
    	$WxPay = Yii::getAlias('@fecshop/lib/Wxpay'); //设置别名方便native.php中来包含需要的文件
    	$WxPay_example = Yii::getAlias('@fecshop/lib/Wxpay/example');
    	$WxPay_native = Yii::getAlias('@fecshop/lib/Wxpay/example/native.php');
    	require_once($WxPay . '/lib/WxPay.Api.php');
    	require_once($WxPay . '/lib/WxPay.Data.php');
    	require_once($WxPay . '/lib/WxPay.Config.php');
    	require_once($WxPay . '/lib/WxPay.Notify.php');
    	require_once($WxPay . '/lib/WxPay.Exception.php');
    	require_once($WxPay_example . '/WxPay.NativePay.php');
    	require_once($WxPay_example . '/log.php');
    	//$input = new WxPayOrderQuery();
    	$input = new \WxPayOrderQuery(); 
    	$input->SetOut_trade_no($out_trade_no);
    	$result = \WxPayApi::orderQuery($input);
    	//print_r($result);
    
    	return $result;
    
    }
    
    /**
     * 把返回的支付参数方式改成数组以适应微信的api
     */
    protected function getStartBizContentAndSetPaymentMethod(){
    	$currentOrderInfo = Yii::$service->order->getCurrentOrderInfo();
    	if(isset($currentOrderInfo['products']) && is_array($currentOrderInfo['products'])){
    		$subject_arr = [];
    		foreach ($currentOrderInfo['products'] as $product)
    		{
    			$subject_arr[] = $product['name'];
    		}
    
    		if (!empty($subject_arr))
    		{
    			$subject = implode(',', $subject_arr);
    			$increment_id = $currentOrderInfo['increment_id'];
    			$base_grand_total = $currentOrderInfo['base_grand_total'];
    			$total_amount = Yii::$service->page->currency->getCurrencyPrice($base_grand_total, 'CNY');
    			Yii::$service->payment->setPaymentMethod($currentOrderInfo['payment_method']);
    			return [
    					'out_trade_no' => $increment_id,
    					'product_code' => $this->_productCode,
    					'total_amount' => $total_amount,
    					'subject' 	   => $subject,
    			];
    			
    		}
    
    	}
    }
    
    /**
     * 检查订单是否合法
     * 如果每项验证都通过则返回真
     */
    public function checkOrder($trade_state, $return_code, $trade_type, $out_trade_no,$total_amount,$seller_id,$auth_app_id)
    {
    	if ($trade_state != 'SUCCESS'){
    		Yii::$service->helper->errors->add('request trade_state is not equle to SUCCESS');
    		return false;
    	}
    	if ($return_code != 'SUCCESS'){
    		Yii::$service->helper->errors->add('request return_code is not equle to SUCCESS');
    		return false;
    	}
    	if ($trade_type != 'NATIVE'){
    		Yii::$service->helper->errors->add('request trade_type is not equle to NATIVE');
    		return false;
    	}
    	if(!$this->_order){
    		$this->_order = Yii::$service->order->getByIncrementId($out_trade_no);
    		Yii::$service->payment->setPaymentMethod($this->_order['payment_method']);
    	}
    	if(!$this->_order){
    		Yii::$service->helper->errors->add('order increment id:'.$out_trade_no.' is not exist.');
    
    		return false;
    	}
    	$base_grand_total = $this->_order['base_grand_total'];
    	$order_total_amount = Yii::$service->page->currency->getCurrencyPrice($base_grand_total,'CNY');
    	if((string)($order_total_amount * 100) != $total_amount){ //由于微信中是以分为单位所以必须乘以100,二维码页面也已经作了处理,单位都是分,$order_total_amount * 100要转为字符串再比较
    		Yii::$service->helper->errors->add('order increment id:'.$out_trade_no.' , total_amount('.$total_amount.') is not equal to order_total_amount('.$order_total_amount.')');
    		//return ['o' => $order_total_amount * 100, 't' => $total_amount]; //测试时便于观察订单金额和微信实际支付的金额,生产环境要注释掉
    		return false;
    	}
    	if(!$this->sellerId){
    		//return 4;
    		Yii::$service->helper->errors->add('you must config sellerId in alipay payment config file');
    
    		return false;
    	}
    	if($seller_id != $this->sellerId){
    		//return 5;
    		Yii::$service->helper->errors->add('request sellerId('.$seller_id.') is not equle to config sellerId('.$this->sellerId.')');
    
    		return false;
    	}
    	if($auth_app_id != $this->appId){
    		//return 6;
    		Yii::$service->helper->errors->add('request auth_app_id('.$auth_app_id.') is not equle to config appId('.$this->appId.')');
    
    		return false;
    	}
    
    	return true;
    }
    
    
}

4.同第一步一样在fecshop/common/config/fecshop_local_services/Payment.php中,最下面支付宝配置的下面添加微信支付的相关配置如下: 代码如下:

'wxpay' => [ //注意参数要与WxPay.Config中的一致
        		'class'         => 'fecshop\services\payment\Wxpay', //新增,没加之前也能正常运行
        		'appId'         => 'wx426b3015555a46be',
        		'sellerId'      => '1900009851',
        		'rsaPrivateKey' => '8934e7d15453e97507ef794cf7b0519d',
        	],

5.微信支付需要一个页面来负责生成二维码并用ajax不断查询交易是否成功,成功则更改订单状态,清空购物车,发送邮件然后跳转到付款成功页面,这个生成二维码和查询交易的页面,我已弄好,文件路径在fecshop/vendor/fancyecommerce/fecshop/lib/wxpay/example/native.php,为了方便大家测试,我已经把微信支付的sdk包放到百度网盘上,可以下载 链接: https://pan.baidu.com/s/1c2asmEc 密码: 44ga 复制到fecshop/vendor/fancyecommerce/fecshop/lib/文件夹中即可

6.微信sdk文件中也要配置一下跟Payment.php中的账号一致,如果使用百度网盘上的sdk包,测试时暂时不用修改,当然生产环境中要用自己的账户 路径在fecshop/vendor/fancyecommerce/fecshop/lib/wxpay/lib/WxPay.Config.php 代码如下:

const APPID = 'wx426b3015555a46be';
	const MCHID = '1900009851';
	const KEY = '8934e7d15453e97507ef794cf7b0519d';
	const APPSECRET = '7813490da6f1265e4901ffb80afaa36f';

7.现在可以测试支付流程了,大概过程截图如下:

本文由 alex 创作,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。

共收到 9 条回复
Fecmall#16年前 0 个赞

感谢 alex的分享,您加下我的QQ:2358269014

Fecmall#26年前 0 个赞

需要把库包发一下:

@fecshop/lib/Wxpay

alex#36年前 0 个赞

为了方便大家测试,我已经把微信支付的包 @fecshop/lib/Wxpay放到百度网盘上,可以下载 链接: https://pan.baidu.com/s/1c2asmEc 密码: 44ga 复制到fecshop/vendor/fancyecommerce/fecshop/lib/文件夹中即可

Fecmall#46年前 0 个赞

@alex #3楼 另外问个问题,pc端的二维码收款 和 wap端的直接使用微信app支付,代码都有了吗、?

alex#56年前 0 个赞

目前只有pc端的二维码扫码支付

Fecmall#66年前 0 个赞

@alex [#5楼](#comment5) 如果有时间,写一下手机web端的基于微信app的支付,就更好了

支付宝端的我写好了,微信端之前碍于测试账户的障碍,没有写。

alex#76年前 0 个赞

等工作空闲点我会尝试一下看能不能写出来

Fecmall#86年前 0 个赞

@alex #7楼 我今天细看了你的代码,存在问题,你的支付状态不是通过IPN更改订单状态, 而是通过js去获取,如果用户刷出来二维码,然后,用微信刷进行支付,在支付成功之前,关掉浏览器, 然后进行微信支付,fecshop就无法更改订单状态了。

9楼 已删除.
10楼 已删除.
Fecmall#114年前 0 个赞

@wubuyun #10楼 自己开发。

12楼 已删除.
添加回复 (需要登录)
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册
Your Site Analytics