You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
12 KiB
358 lines
12 KiB
<?php
|
|
|
|
|
|
namespace backend\modules\payment\logic;
|
|
|
|
|
|
use api\logic\Helper;
|
|
use backend\modules\shop\models\ars\PaymentLog;
|
|
use backend\modules\shop\models\ars\RefundLog;
|
|
use backend\modules\shop\models\ars\WxPayConfig;
|
|
use EasyWeChat\Factory;
|
|
use yii\db\Exception;
|
|
use yii\helpers\Json;
|
|
use yii\web\BadRequestHttpException;
|
|
use yii\web\NotFoundHttpException;
|
|
use Yii;
|
|
|
|
class WxPaymentManager
|
|
{
|
|
/*支付类型*/
|
|
const PAY_TYPE_WEB = 1;
|
|
const PAY_TYPE_MINI_PROGRAM = 2;
|
|
/*支付状态*/
|
|
const STATUS_PAYMENT_WAITING = 0;
|
|
const STATUS_PAYMENT_SUCCESS = 1;
|
|
/*退款状态*/
|
|
const STATUS_REFUND_WAIT = 0; //退款待审核
|
|
const STATUS_REFUND_CONFIRM = 1; //退款待确认
|
|
const STATUS_REFUND_SUCCESS = 2; //退款成功
|
|
const STATUS_REFUND_PORTION = 3; //部分退款
|
|
/*发起支付方式*/
|
|
const TRADE_TYPE_JS_API = 'JSAPI';
|
|
|
|
public $appId;
|
|
public $mchId;
|
|
public $key;
|
|
public $certPath;
|
|
public $keyPath;
|
|
public $notifyUrl;
|
|
public $tradeType;
|
|
public $payType;
|
|
public $app;
|
|
public $order;
|
|
|
|
/**
|
|
* @param $orderId
|
|
* @param $orderAmount
|
|
* @param $paymentAmount
|
|
* @param $notifyUrl
|
|
* @param $payType
|
|
* @return mixed
|
|
* @throws BadRequestHttpException
|
|
* @throws Exception
|
|
* 微信统一下单
|
|
*/
|
|
public function wxPayment($orderId, $orderAmount, $paymentAmount, $notifyUrl, $payType)
|
|
{
|
|
if (empty($orderId) || empty($orderAmount) ||empty($paymentAmount) || empty($notifyUrl) || empty($payType)) {
|
|
throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
|
|
}
|
|
$this->tradeType = self::TRADE_TYPE_JS_API;
|
|
$this->payType = $payType;
|
|
$this->notifyUrl = $notifyUrl;
|
|
|
|
$unifyParams = $this->applyPaymentData($orderId, $orderAmount, $paymentAmount, $notifyUrl);
|
|
$result = $this->unify($unifyParams);
|
|
if ($result['return_code'] == 'FAIL') {
|
|
throw new BadRequestHttpException($result['return_msg']);
|
|
}
|
|
if ($result['return_code'] == 'SUCCESS' && $result['return_msg'] == 'OK' && $result['result_code'] == 'FAIL') {
|
|
throw new BadRequestHttpException($result['err_code_des']);
|
|
}
|
|
return Json::decode($this->getBridgeConfig($result['prepay_id']), true);
|
|
}
|
|
|
|
/**
|
|
* @param $prepayId
|
|
* @return array|string
|
|
*/
|
|
private function getBridgeConfig($prepayId)
|
|
{
|
|
$this->initObject();
|
|
return $this->app->jssdk->bridgeConfig($prepayId);
|
|
}
|
|
|
|
/**
|
|
* @param string $orderId
|
|
* @param int $orderAmount
|
|
* @param int $paymentAmount
|
|
* @param string $notifyUrl
|
|
* @return array
|
|
* @throws BadRequestHttpException
|
|
* @throws Exception
|
|
* 生成支付参数
|
|
*/
|
|
private function applyPaymentData($orderId, $orderAmount, $paymentAmount, $notifyUrl)
|
|
{
|
|
$this->savePaymentLog($orderId, $orderAmount, $paymentAmount, $notifyUrl);
|
|
|
|
$params = [
|
|
'body' => '订单支付',
|
|
'out_trade_no' => $orderId,
|
|
'total_fee' => $paymentAmount,
|
|
'openid' => Yii::$app->user->identity->wx_openid,
|
|
];
|
|
return $params;
|
|
}
|
|
|
|
/**
|
|
* @param string $orderId
|
|
* @param int $orderAmount
|
|
* @param int $paymentAmount
|
|
* @param string $notifyUrl
|
|
* @throws BadRequestHttpException
|
|
* @throws Exception
|
|
* 保存支付信息
|
|
*/
|
|
private function savePaymentLog($orderId, $orderAmount, $paymentAmount, $notifyUrl)
|
|
{
|
|
$paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
|
|
if (!$paymentLog) {
|
|
$paymentLog = new PaymentLog();
|
|
} elseif (!empty($paymentLog->wx_payment_id)) {
|
|
throw new BadRequestHttpException('此订单号也有支付信息,不能重复');
|
|
}
|
|
$paymentLog->order_id = $orderId;
|
|
$paymentLog->order_amount = $orderAmount;
|
|
$paymentLog->payment_amount = $paymentAmount;
|
|
$paymentLog->notify_url = $this->notifyUrl;
|
|
$paymentLog->type = $this->payType;
|
|
$paymentLog->status = self::STATUS_PAYMENT_WAITING;
|
|
if (!$paymentLog->save()) {
|
|
throw new Exception(Helper::errorMessageStr($paymentLog->errors));
|
|
}
|
|
}
|
|
|
|
protected function getApp()
|
|
{
|
|
$this->initObject();
|
|
$config = [
|
|
'app_id' => $this->appId,
|
|
'mch_id' => $this->mchId,
|
|
'key' => $this->key,
|
|
'cert_path' => $this->certPath,
|
|
'key_path' => $this->keyPath,
|
|
'notify_url' => $this->notifyUrl,
|
|
'trade_type' => $this->tradeType,
|
|
'sandbox' => true, // 设置为 false 或注释则关闭沙箱模式
|
|
];
|
|
$this->app = Factory::payment($config);
|
|
//判断当前是否为沙箱模式:
|
|
$this->app->inSandbox();
|
|
}
|
|
|
|
/**
|
|
* @var WxPayConfig $wxPayConfig
|
|
*/
|
|
private function initObject()
|
|
{
|
|
$path = Yii::getAlias('@backend');
|
|
$wxPayConfig = WxPayConfig::find()->one();
|
|
switch ($this->payType) {
|
|
case self::PAY_TYPE_WEB:
|
|
// $wxConfig = WxConfig::find()->one();
|
|
// $this->appId = trim($wxConfig->appid);
|
|
break;
|
|
case self::PAY_TYPE_MINI_PROGRAM:
|
|
// $miniProgramConfig = MiniProgramConfig::find()->one();
|
|
// $this->appId = trim($miniProgramConfig->appid);
|
|
break;
|
|
}
|
|
$this->mchId = trim($wxPayConfig->mch_id);
|
|
$this->key = trim($wxPayConfig->key);
|
|
$this->certPath = trim($path . $wxPayConfig->cert_path);
|
|
$this->keyPath = trim($path . $wxPayConfig->key_path);
|
|
$this->notifyUrl = Yii::$app->request->hostInfo . '/wx-payment/notify';
|
|
|
|
$this->mchId = '1395812402';
|
|
$this->key = '51CF5EE3B2E35B9843E17E6099325A65';
|
|
}
|
|
|
|
/**
|
|
* @param $unifyParams
|
|
* @return mixed
|
|
* 统一下单
|
|
*/
|
|
private function unify($unifyParams)
|
|
{
|
|
$unifyParams['trade_type'] = $this->tradeType;
|
|
$this->getApp();
|
|
return $this->app->order->unify($unifyParams);
|
|
}
|
|
|
|
/**
|
|
* @param $data
|
|
* @return bool
|
|
* 支付成功回调验证签名和支付金额
|
|
*/
|
|
public function checkSign($data)
|
|
{
|
|
$this->initObject();
|
|
$notifySign = $data['sign'];
|
|
unset($data['sign']);
|
|
$sign = $this->_sign($data);
|
|
if ($notifySign == $sign) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $arr
|
|
* @return string
|
|
* 微信签名方法
|
|
*/
|
|
private function _sign($arr)
|
|
{
|
|
$arr = array_filter($arr);
|
|
ksort($arr);
|
|
$arr['key'] = $this->key;
|
|
$queryString = http_build_query($arr);
|
|
$queryString = urldecode($queryString);
|
|
return strtoupper(md5($queryString));
|
|
}
|
|
|
|
|
|
/*************************************** Refund ************************************************/
|
|
/**
|
|
* @param string $orderId
|
|
* @param int $refundAmount
|
|
* @param string $refundAccount
|
|
* @param int $reason
|
|
* @return RefundLog
|
|
* @throws BadRequestHttpException
|
|
* @throws Exception
|
|
* @throws NotFoundHttpException
|
|
* 申请退款
|
|
*/
|
|
public function applyRefund($orderId, $refundAmount, $refundAccount, $reason)
|
|
{
|
|
if (empty($orderId) || empty($refundAmount) || empty($refundAccount) || empty($reason)) {
|
|
throw new BadRequestHttpException(Helper::REQUEST_BAD_PARAMS);
|
|
}
|
|
$paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
|
|
if (empty($paymentLog)) {
|
|
throw new NotFoundHttpException('订单支付信息未找到');
|
|
}
|
|
if (RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT])) {
|
|
throw new BadRequestHttpException('此订单存在等待审核的退款申请');
|
|
}
|
|
$refundedAmount = RefundLog::find()
|
|
->where(['order_id' => $orderId, 'status' => self::STATUS_PAYMENT_SUCCESS])
|
|
->sum('refund_amount') ?? 0;
|
|
|
|
$refundLog = new RefundLog();
|
|
$refundLog->order_id = $orderId;
|
|
$refundLog->wx_refund_id = Helper::timeRandomNum(3, 'R');
|
|
$refundLog->reason = $reason;
|
|
$refundLog->order_amount = $paymentLog->payment_amount;
|
|
$refundLog->refund_amount = $refundAmount;
|
|
$refundLog->refunded_amount = $refundedAmount;
|
|
$refundLog->type = $paymentLog->type;
|
|
$refundLog->status = self::STATUS_REFUND_WAIT;
|
|
$refundLog->refund_account = $refundAccount;
|
|
$refundLog->applyed_at = time();
|
|
if (!$refundLog->save()) {
|
|
throw new Exception(Helper::errorMessageStr($refundLog->errors));
|
|
}
|
|
return $refundLog;
|
|
}
|
|
|
|
/**
|
|
* @param string $orderId
|
|
* @param int $refundAmount
|
|
* @param int $operatorId
|
|
* @return array|mixed
|
|
* @throws BadRequestHttpException
|
|
* @throws NotFoundHttpException
|
|
*/
|
|
public function refund($orderId, $refundAmount, $operatorId)
|
|
{
|
|
$paymentLog = PaymentLog::findOne(['order_id' => $orderId]);
|
|
if (empty($paymentLog)) {
|
|
throw new NotFoundHttpException('订单支付信息未找到');
|
|
}
|
|
$refundLog = RefundLog::findOne(['order_id' => $orderId, 'status' => self::STATUS_REFUND_WAIT]);
|
|
if (empty($refundLog)) {
|
|
throw new NotFoundHttpException('订单退款信息未找到');
|
|
}
|
|
|
|
$this->executeRefund($paymentLog, $refundLog, $refundAmount);
|
|
return $this->updateRefundInfo($paymentLog, $refundLog, $operatorId);
|
|
}
|
|
|
|
/**
|
|
* @param PaymentLog $paymentLog
|
|
* @param RefundLog $refundLog
|
|
* @param $refundAmount
|
|
* @throws BadRequestHttpException
|
|
*/
|
|
private function executeRefund($paymentLog, $refundLog, $refundAmount)
|
|
{
|
|
/*参数分别为:微信订单号、商户退款单号、订单金额、退款金额、其他参数*/
|
|
$this->getApp();
|
|
$config = [
|
|
'refund_desc' => '退款'
|
|
];
|
|
$result = $this->app->refund->byTransactionId(
|
|
$paymentLog->wx_payment_id,
|
|
$refundLog->wx_refund_id,
|
|
$paymentLog->payment_amount,
|
|
$refundAmount,
|
|
$config
|
|
);
|
|
Yii::info($result, 'refund_log');
|
|
|
|
if ($result['return_code'] == 'FAIL' || $result['return_msg'] != 'OK' || $result['result_code'] == 'FAIL') {
|
|
throw new BadRequestHttpException($result['return_msg']);
|
|
}
|
|
if ($result['err_code_des']) {
|
|
throw new BadRequestHttpException($result['err_code_des']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param PaymentLog $paymentLog
|
|
* @param RefundLog $refundLog
|
|
* @param int $operatorId
|
|
* @return array
|
|
*/
|
|
private function updateRefundInfo($paymentLog, $refundLog, $operatorId)
|
|
{
|
|
$tra = Yii::$app->db->beginTransaction();
|
|
try {
|
|
$paymentLog->wx_refund_id = $refundLog->wx_refund_id;
|
|
$paymentLog->refund_account = $refundLog->refund_account;
|
|
$paymentLog->status = $refundLog->refund_amount < $paymentLog->payment_amount ? self::STATUS_REFUND_SUCCESS : self::STATUS_REFUND_PORTION;
|
|
if (!$paymentLog->save()) {
|
|
throw new Exception(Helper::errorMessageStr($paymentLog->errors));
|
|
}
|
|
|
|
$refundLog->operator_id = $operatorId;
|
|
$refundLog->status = $refundLog->refund_amount < $paymentLog->payment_amount ? self::STATUS_REFUND_SUCCESS : self::STATUS_REFUND_PORTION;
|
|
$refundLog->finished_at = time();
|
|
if (!$refundLog->save()) {
|
|
throw new Exception(Helper::errorMessageStr($refundLog->errors));
|
|
}
|
|
|
|
$tra->commit();
|
|
return ['status' => true];
|
|
} catch (Exception $e) {
|
|
$tra->rollBack();
|
|
return ['status' => false, 'info' => $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
}
|