序言
随着微信越来越流行,自然使用微信支付方式的人也越来越多,电商网站为了方便用户应该支持多种支付方式,我尝试根据微信支付的官方文档和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 中国大陆许可协议 进行许可。
可自由转载、引用,但需署名作者且注明文章出处。