Commit c8f37931 authored by 汪波's avatar 汪波

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	src/frontend/views/my-member/index.php
parents abf506c3 8affcf00
<?php
/**
* Created by PhpStorm.
* User: pader
* Date: 2016/11/3
* Time: 14:26
*/
namespace common\libs;
require_once __DIR__.'/weixin/libs/WxPay.Data.php';
require_once __DIR__.'/weixin/libs/WxPay.Api.php';
class WxPayHelper
{
/**
* 企业支付
*
* @param string $openId 要支付到的微信用户OpenId
* @param string $outTradeNo 本地订单号
* @param float $amount 支付金额,单位元,最多精确到小数点两位
* @param string $clientIp 客户端IP
* @param string $desc 订单简介
* @param string $checkName 是否检查昵称
* @param string $reUserName 要检查的昵称
* @return array
*/
public static function transfers($openId, $outTradeNo, $amount, $clientIp, $desc, $checkName='NO_CHECK', $reUserName='') {
$amount = bcmul($amount, 100, 0); //微信内为分的计算方式,所以需要转换
$transfer = new \WxPayTransfers();
$transfer->setOpenId($openId);
$transfer->setAmount($amount);
$transfer->setCheckName($checkName);
$transfer->setMchBillNo($outTradeNo);
$transfer->setDesc($desc);
$transfer->setReuserName($reUserName);
$transfer->SetSpbill_create_ip($clientIp);
$result = \WxPayApi::transfers($transfer);
is_array($result) && $result['out_trade_no'] = $outTradeNo;
return $result;
}
}
\ No newline at end of file
......@@ -12,6 +12,42 @@ require_once "WxPay.Data.php";
*/
class WxPayApi
{
/**
* 付款接口
*
* @param \WxPayTransfers $inputObj
* @param int $timeOut
* @return array
*/
public static function transfers($inputObj, $timeOut=6) {
$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
$inputObj->SetAppid(WxPayConfig::APPID, 'appid');
$inputObj->SetMch_id(WxPayConfig::MCHID);
$inputObj->SetNonce_str(self::getNonceStr());
if (!$inputObj->IsSpbill_create_ipSet()) {
$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);
}
$inputObj->SetSign();
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();
$response = self::postXmlCurl($xml, $url, true, $timeOut);
$_ENV['last_post_xml'] = $xml;
$result = new WxPayResults();
$result->FromXml($response);
$return = $result->GetValues();
self::reportCostTime($url, $startTimeStamp, $return);
return $return;
}
/**
*
* 统一下单,WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
......@@ -534,6 +570,7 @@ class WxPayApi
curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
}
curl_setopt($ch,CURLOPT_URL, $url);
//curl_setopt($ch,CURLOPT_VERBOSE,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
//设置header
......@@ -545,9 +582,10 @@ class WxPayApi
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
curl_setopt($ch,CURLOPT_SSLCERT, realpath(__DIR__.'/'.WxPayConfig::SSLCERT_PATH));
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
curl_setopt($ch,CURLOPT_SSLKEY, realpath(__DIR__.'/'.WxPayConfig::SSLKEY_PATH));
curl_setopt($ch,CURLOPT_SSLCERTPASSWD, WxPayConfig::MCHID);
}
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
......
......@@ -34,7 +34,7 @@ class WxPayConfig
* API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
* @var path
*/
const SSLCERT_PATH = '../cert/apiclient_cert.pem';
const SSLCERT_PATH = '../cert/apiclient_cert.p12';
const SSLKEY_PATH = '../cert/apiclient_key.pem';
//=======【curl代理设置】===================================
......
This diff is collapsed.
......@@ -165,4 +165,13 @@ class Income extends \yii\db\ActiveRecord
return $query->asArray()->all();
}
public static function updateSettleOk($uid, $minId, $maxId)
{
self::updateAll(['status'=>self::STATUS_IS_SETTLED], 'user_id=:uid AND id BETWEEN :min AND :max', [
':uid' => $uid,
':min' => $minId,
':max' => $maxId
]);
}
}
......@@ -112,7 +112,7 @@ class Member extends \yii\db\ActiveRecord
*/
public function getUser()
{
return $this->hasOne(User::className(), ['id'=>'member_id'])->select('id,username,telephone,role,avatar');
return $this->hasOne(User::className(), ['id'=>'member_id'])->select('id,username,telephone,role,avatar,created_at');
}
/**
......
......@@ -92,5 +92,16 @@ class SettleAccountWeixin extends ActiveRecord
{
return $size == self::HEAD_SIZE_ORG ? $headImgUrl : substr_replace($headImgUrl, $size, -1);
}
/**
* 获取绑定微信的基本信息
*
* @param int $uid
* @return array
*/
public static function getBaseInfo($uid)
{
return self::find()->select('user_id, open_id, nickname, headimgurl')->where(['user_id'=>$uid])->asArray()->one();
}
}
<?php
namespace common\models;
use yii\base\InvalidCallException;
use yii\behaviors\AttributeBehavior;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
......@@ -12,7 +13,7 @@ use yii\db\ActiveRecord;
* @property integer $user_id
* @property string $type
* @property string $out_trade_no
* @property string $trade_no
* @property string $payment_no
* @property string $amount
* @property integer $income_id_min
* @property integer $income_id_max
......@@ -50,12 +51,12 @@ class SettleOrders extends ActiveRecord
return [
[['user_id', 'amount', 'income_id_min', 'income_id_max'], 'required'],
[['user_id', 'income_id_min', 'income_id_max', 'status', 'created_at', 'updated_at'], 'integer'],
[['type', 'out_trade_no', 'trade_no'], 'string'],
[['type', 'out_trade_no', 'payment_no'], 'string'],
[['type'], 'default', 'value'=>self::TYPE_WEIXIN],
[['amount'], 'number'],
[['out_trade_no', 'trade_no'], 'string', 'max' => 50],
[['out_trade_no', 'payment_no'], 'string', 'max' => 50],
[['out_trade_no'], 'unique'],
[['trade_no'], 'default', 'value'=>''],
[['payment_no'], 'default', 'value'=>''],
[['status'], 'default', 'value'=>self::STATUS_UNPAID]
];
}
......@@ -70,7 +71,7 @@ class SettleOrders extends ActiveRecord
'user_id' => 'User ID',
'type' => 'Type',
'out_trade_no' => 'Out Trade No',
'trade_no' => 'Trade No',
'payment_no' => 'Payment No',
'amount' => 'Amount',
'income_id_min' => 'Income Id Min',
'income_id_max' => 'Income Id Max',
......@@ -120,6 +121,12 @@ class SettleOrders extends ActiveRecord
return $this->out_trade_no;
}
/**
* 生成本地订单
*
* @param int $userId
* @return string
*/
public static function generateOutTradeNo($userId)
{
$userSeg = sprintf('%08d', $userId);
......@@ -149,4 +156,32 @@ class SettleOrders extends ActiveRecord
return self::find()->where(['status'=>self::STATUS_UNPAID])->asArray()->all();
}
/**
* 更新支付单为成功状态
*
* @param int $id
* @param string $paymentNo
* @return int
*/
public static function updateStatusPaid($id, $paymentNo)
{
return self::updateAll(['payment_no'=>$paymentNo, 'status'=>self::STATUS_PAIED], ['id'=>$id]);
}
/**
* 更新指定订单状态
*
* @param int $id
* @param int $status
* @return int
*/
public static function updateStatus($id, $status)
{
if ($status == self::STATUS_PAIED) {
throw new InvalidCallException('Unable to update status paid, use updateStatusPaid() instated.');
}
return self::updateAll(['status'=>$status], ['id'=>$id]);
}
}
......@@ -163,5 +163,17 @@ class UploadPools extends ActiveRecord
return false;
}
/**
* 返回不在使用的文件列表
*
* @param int $limit
* @return UploadPools[]
*/
public static function fetchUnusedFiles($limit=500)
{
return self::find()->select('uuid,skey')->where('`status` IN (0,2) AND upload_time<:expire', [':expire'=>time()-86400])
->limit($limit)->all();
}
}
\ No newline at end of file
......@@ -18,7 +18,8 @@
"qiniu/php-sdk": "^7.0",
"light/yii2-xmlparser": "*",
"2amigos/yii2-qrcode-helper": "*",
"mobiledetect/mobiledetectlib": "^2.8"
"mobiledetect/mobiledetectlib": "^2.8",
"hellogerard/jobby": "^3.2"
},
"require-dev": {
"yiisoft/yii2-codeception": "*",
......
This diff is collapsed.
......@@ -17,6 +17,7 @@ return [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning', 'info'],
'logVars' => []
],
],
],
......
<?php
/**
* Created by PhpStorm.
* User: pader
* Date: 2016/11/3
* Time: 17:57
*/
namespace console\controllers;
use common\models\UploadPools;
use Qiniu\Storage\BucketManager;
use yii\console\Controller;
class MaintainController extends Controller
{
/**
* 清理长期未被认领或已指定要删除的文件
*
* 建议每隔2-5个小时运行一次
*
* @throws \Exception
*/
public function actionClearUploadPool()
{
/* @var $qiniu \common\components\Qiniu */
$qiniu = \Yii::$app->qiniu;
$auth = $qiniu->getAuthInstance();
$bucketMgr = new BucketManager($auth);
$list = UploadPools::fetchUnusedFiles();
$success = $failed = 0;
foreach ($list as $file) {
list($storage, $key) = explode(':', $file->skey);
$bucket = $qiniu->getStorageConfig($storage, 'bucket');
/* @var $err \Qiniu\Http\Error */
$err = $bucketMgr->delete($bucket, $key);
if ($err == null || $err->getResponse()->statusCode == 612) {
$file->delete();
++$success;
} else {
++$failed;
\Yii::warning('Delete unused file failed: '.$err->message()."\n{uuid={$file->uuid}, skey={$file->skey}");
}
}
$info = "Deleted unused files finish, success=$success, failed=$failed.";
\Yii::info($info);
echo $info."\n";
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: xiabin
* Date: 2016/11/4
* Time: 下午1:45
*/
namespace console\controllers;
use Jobby\Jobby;
use Yii;
use yii\console\Controller;
class TaskController extends Controller
{
public function actionIndex()
{
$jobby = new Jobby();
$yiiCommand = $_SERVER['SCRIPT_FILENAME'];
$output = Yii::getAlias('@runtime') . "/logs/jobs-" . date('Y-m-d') . ".log";
if(YII_DEBUG){
$time = date('Y-m-d H:i:s');
$jobby->add('CommandExample', [
// Run a shell commands
'command' => "echo $time",
// Ordinary crontab schedule format is supported.
// This schedule runs every hour.
// You could also insert DateTime string in the format of Y-m-d H:i:s.
'schedule' => '*/5 * * * *',
// Stdout and stderr is sent to the specified file
'output' => $output,
// You can turn off a job by setting 'enabled' to false
'enabled' => TRUE,
]);
}
//minute hour mday month wday [year]
$jobby->add('WithdrawSettle', [
'command' => "$yiiCommand withdraw/settle",
'schedule' => '30 8 * * 1-5', //每周一到周五上午8:30
'output' => $output,
'enabled' => true
]);
$jobby->add('ClearUploadPool', [
'command' => "$yiiCommand maintain/clear-upload-pool",
'schedule' => '15 */2 * * *', //每隔两小时的第15分钟
'output' => $output,
'enabled' => true
]);
$jobby->run();
}
}
\ No newline at end of file
......@@ -9,10 +9,12 @@
namespace console\controllers;
use common\libs\Utils;
use common\libs\WxPayHelper;
use common\models\Income;
use common\models\Orders;
use common\models\SettleAccountWeixin;
use common\models\SettleOrders;
use common\models\User;
use Yii;
use yii\console\Controller;
/**
......@@ -22,6 +24,11 @@ use yii\console\Controller;
class WithdrawController extends Controller
{
/**
* 结算今日之前用户的收入
*
* 建议每次凌晨或上午运行
*/
public function actionSettle()
{
$this->actionCreateSettleOrders();
......@@ -39,6 +46,11 @@ class WithdrawController extends Controller
$count = 0;
foreach ($incomeData as $row) {
//最低金额1元起付
if ($row['amount'] < 1) {
continue;
}
$order = new SettleOrders();
$order->load($row);
......@@ -46,12 +58,12 @@ class WithdrawController extends Controller
$sameOrder = SettleOrders::findSameOrder($order->user_id, $order->income_id_min, $order->income_id_max);
if ($sameOrder) {
\Yii::warning("Create settle order canceled because found same order: {id={$sameOrder['id']}, out_trade_no={$sameOrder['out_trade_no']}}.");
Yii::warning("Create settle order canceled because found same order: {id={$sameOrder['id']}, out_trade_no={$sameOrder['out_trade_no']}}.");
continue;
}
if (!$order->save()) {
\Yii::error('Create settle order failed:' . Utils::fetchErrorFirst($order->errors));
Yii::error('Create settle order failed:' . Utils::fetchErrorFirst($order->errors));
continue;
}
......@@ -60,16 +72,67 @@ class WithdrawController extends Controller
$info = "Create settle orders finish, count=$count.";
\Yii::info($info);
Yii::info($info);
echo $info."\n";
}
/**
* @return void
*/
public function actionPaySettleOrders()
{
$unpaidOrders = SettleOrders::getAllUnpaidOrders();
if (!$unpaidOrders) {
$info = "No unpaid settle orders.";
Yii::info($info);
echo $info."\n";
return;
}
$success = $failed = 0;
foreach ($unpaidOrders as $order) {
//先锁定
SettleOrders::updateStatus($order['id'], SettleOrders::STATUS_ONPAY);
$account = SettleAccountWeixin::getBaseInfo($order['user_id']);
if (!$account) {
++$failed;
Yii::warning("Pay settle order ignore because no found weixin account: {id={$order['id']}, user_id={$order['user_id']}}.");
continue;
}
//获取用户IP
$user = User::find()->select('ip')->where(['id'=>$order['user_id']])->asArray()->one();
//发起付款
$result = WxPayHelper::transfers($account['open_id'], $order['out_trade_no'], $order['amount'], $user['ip'], '收入结算-'.$order['id']);
if (!isset($result['return_code']) || !isset($result['result_code'])) {
//接口调用失败恢复为未支付状态
SettleOrders::updateStatus($order['id'], SettleOrders::STATUS_UNPAID);
++$failed;
$json = json_encode($result, JSON_UNESCAPED_UNICODE);
Yii::error("Pay settle order failed: {id={$order['id']}, user_id={$order['user_id']}, out_trade_no={$order['out_trade_no']}}\n$json");
} elseif ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
//支付成功
SettleOrders::updateStatusPaid($order['id'], $result['payment_no']);
Income::updateSettleOk($order['user_id'], $order['income_id_min'], $order['income_id_max']);
++$success;
} else {
SettleOrders::updateStatus($order['id'], SettleOrders::STATUS_FAILED);
++$failed;
Yii::error("Pay settle order failed: {$result['return_msg']}\n{id={$order['id']}, user_id={$order['user_id']}, out_trade_no={$order['out_trade_no']}}");
}
}
print_r($unpaidOrders);
$info = "Pay settle orders finish, success=$success, failed=$failed";
Yii::info($info);
echo $info."\n";
}
}
\ No newline at end of file
......@@ -13,7 +13,7 @@ class m161020_033521_settle_orders_init extends Migration
'user_id' => $this->integer(10)->unsigned()->notNull(),
'`type` enum(\'weixin\',\'alipay\') NOT NULL COMMENT \'结算类型\'',
'out_trade_no' => $this->string(50)->comment('本地订单编号'),
'trade_no' => $this->string(50)->comment('支付平台订单编号'),
'payment_no' => $this->string(50)->comment('支付平台单号'),
'amount' => $this->decimal(8, 2)->unsigned()->comment('结算金额'),
'income_id_min' => $this->integer(10)->unsigned()->comment('结算的收记录范围最小id'),
'income_id_max' => $this->integer(10)->unsigned()->comment('结算的收记录范围最大id'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment