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.
277 lines
9.9 KiB
277 lines
9.9 KiB
<?php
|
|
|
|
namespace App\Service\v3\Implementations;
|
|
|
|
use App\Commons\Log;
|
|
use App\Constants\v3\ErrorCode;
|
|
use App\Constants\v3\LogLabel;
|
|
use App\Constants\v3\OrderState;
|
|
use App\Constants\v3\OrderType;
|
|
use App\Constants\v3\Payment;
|
|
use App\Exception\ErrorCodeException;
|
|
use App\Model\v3\OrderMain;
|
|
use App\Model\v3\User;
|
|
use App\Service\v3\Interfaces\GoodsActivityServiceInterface;
|
|
use App\Service\v3\Interfaces\PaymentServiceInterface;
|
|
use App\Service\v3\Interfaces\SmsSendServiceInterface;
|
|
use EasyWeChat\Factory;
|
|
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
|
|
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
|
|
use EasyWeChat\Kernel\Support\Collection;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use Hyperf\DbConnection\Db;
|
|
use Hyperf\Guzzle\CoroutineHandler;
|
|
use Hyperf\Di\Annotation\Inject;
|
|
use Hyperf\Redis\Redis;
|
|
use Hyperf\Utils\ApplicationContext;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use function AlibabaCloud\Client\json;
|
|
|
|
class PaymentService implements PaymentServiceInterface
|
|
{
|
|
/**
|
|
* @Inject
|
|
* @var Log
|
|
*/
|
|
protected $log;
|
|
|
|
/**
|
|
* @Inject
|
|
* @var SmsSendServiceInterface
|
|
*/
|
|
protected $smsAliSendService;
|
|
|
|
public function do($globalOrderId, $money, $userId, $notifyUrl)
|
|
{
|
|
|
|
try {
|
|
|
|
$config = config('wxpay');
|
|
$app = Factory::payment($config);
|
|
$app['guzzle_handler'] = CoroutineHandler::class;
|
|
|
|
// 待支付的,未超时(15min,900sec)的订单
|
|
$orderMain = OrderMain::query()
|
|
->where(['state' => OrderState::UNPAID, 'global_order_id' => $globalOrderId, 'user_id' => $userId])
|
|
->where('created_at', '>=', (time()-900))
|
|
->first();
|
|
if (empty($orderMain)) {
|
|
throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
|
|
}
|
|
|
|
$payMoney = bcmul((string)$orderMain->money, 100, 0);
|
|
if (env('APP_ENV') != 'prod' && $orderMain->type == OrderType::ONLINE) {
|
|
// $payMoney = 1;
|
|
}
|
|
|
|
$user = User::select('openid')->find($userId);
|
|
$result = $app->order->unify([
|
|
'body' => '懒族生活',
|
|
'out_trade_no' => $orderMain->global_order_id,
|
|
'total_fee' => $payMoney,
|
|
'notify_url' => $notifyUrl,
|
|
'trade_type' => 'JSAPI',
|
|
'openid' => $user['openid'],
|
|
]);
|
|
|
|
if ($result['return_code'] == 'FAIL') {
|
|
throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[支付失败]'.$result['return_msg']);
|
|
}
|
|
|
|
// 返回支付参数给前端
|
|
$parameters = [
|
|
'appId' => $result['appid'],
|
|
'timeStamp' => '' . time() . '',
|
|
'nonceStr' => uniqid(),
|
|
'package' => 'prepay_id=' . $result['prepay_id'],
|
|
'signType' => 'MD5'
|
|
];
|
|
|
|
$parameters['paySign'] = $this->signture($parameters, $config['key']);
|
|
$parameters['order_main_id'] = $orderMain->global_order_id;
|
|
|
|
return $parameters;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->log->event(LogLabel::ORDER_PAYMENT_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
|
|
throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[稍后重试]');
|
|
}
|
|
}
|
|
|
|
public function check()
|
|
{
|
|
// TODO: Implement check() method.
|
|
}
|
|
|
|
/**
|
|
* 退款的整单,用户申请的退款
|
|
* @param $globalOrderId
|
|
* @param $userId
|
|
* @return array|bool|Collection|object|ResponseInterface|string
|
|
*/
|
|
public function undo($globalOrderId, $userId)
|
|
{
|
|
|
|
try{
|
|
|
|
$config = config('wxpay');
|
|
$app = Factory::payment($config);
|
|
$app['guzzle_handler'] = CoroutineHandler::class;
|
|
|
|
// 已支付的,未退款的,使用微信支付的订单
|
|
$orderMain = OrderMain::query()
|
|
->whereIn('state', [OrderState::PAID, OrderState::DELIVERY, OrderState::COMPLETED, OrderState::EVALUATED, OrderState::REFUNDING])
|
|
->where(['global_order_id' => $globalOrderId, 'user_id' => $userId, 'pay_type' => Payment::WECHAT,'refund_time'=>0])
|
|
->first();
|
|
if (empty($orderMain)) {
|
|
throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]'.$globalOrderId);
|
|
}
|
|
|
|
$result = $app->refund->byOutTradeNumber(
|
|
$orderMain->global_order_id,
|
|
$orderMain->global_order_id,
|
|
bcmul($orderMain->money, 100, 0),
|
|
bcmul($orderMain->money, 100, 0),
|
|
[
|
|
'refund_desc' => '订单退款',
|
|
'notify_url' => config('wechat.notify_url.refund'),
|
|
]
|
|
);
|
|
|
|
if ($result['return_code'] == 'SUCCESS' && isset($result['result_code']) && $result['result_code'] == "SUCCESS") {
|
|
return true;
|
|
} else {
|
|
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL,$result['err_code_des']);
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->log->event(LogLabel::ORDER_REFUND_LOG, ['payment_do_exception_msg' => $e->getMessage()]);
|
|
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 企业付款到微信钱包
|
|
* @param $money
|
|
* @param $tradeNo
|
|
* @param $openId
|
|
* @param $userName
|
|
* @param string $desc
|
|
* @param string $checkName
|
|
* @return bool
|
|
* @throws GuzzleException
|
|
* @throws InvalidConfigException
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function payToWx($money, $tradeNo, $openId, $userName, $desc = '', $checkName = 'NO_CHECK')
|
|
{
|
|
$config = config('wxpay');
|
|
$app = Factory::payment($config);
|
|
$app['guzzle_handler'] = CoroutineHandler::class;
|
|
|
|
// 特殊原因如资金不够等,发短信给老板或者老总
|
|
$redis = ApplicationContext::getContainer()->get(Redis::class);
|
|
$refuseReasonForSpecialKey = 'send_withdraw_refuse_reson_'.date('Ymd');
|
|
|
|
$result = $app->transfer->toBalance([
|
|
'partner_trade_no' => $tradeNo, // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号)
|
|
'openid' => $openId,
|
|
'check_name' => $checkName, // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名
|
|
're_user_name' => $userName, // 如果 check_name 设置为FORCE_CHECK,则必填用户真实姓名
|
|
'amount' => $money, // 企业付款金额,单位为分
|
|
'desc' => $desc, // 企业付款操作说明信息
|
|
]);
|
|
|
|
if ($result['return_code'] != 'SUCCESS') {
|
|
$this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
|
|
'exception_payToWx' => $result['return_msg'],
|
|
'result' => json_encode($result),
|
|
'desc' => $desc
|
|
]);
|
|
throw new ErrorCodeException(ErrorCode::WITHDRAW_PAYMENT_FAIL);
|
|
}
|
|
|
|
if ($result['result_code'] != 'SUCCESS') {
|
|
|
|
// 如果是商户余额不足等原因,要发送短信给老总
|
|
$arr = ['NOTENOUGH','AMOUNT_LIMIT','NO_AUTH'];
|
|
if (in_array($result['err_code'], $arr)) {
|
|
|
|
if (!$redis->exists($refuseReasonForSpecialKey)) { // 当天如果没发过就发
|
|
$res = $this->smsAliSendService->doWithdrawFail($desc, $money, $result['err_code'], $result['err_code_des']);
|
|
if (isset($res['result']['Code']) && $res['result']['Code'] == 'OK') {
|
|
$redis->set($refuseReasonForSpecialKey, $result['err_code'].'/'.$result['err_code_des']);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->log->event(LogLabel::PAY_TO_WX_FAIL_LOG, [
|
|
'exception_payToWx' => $result['err_code_des'],
|
|
'result' => json_encode($result),
|
|
'desc' => $desc
|
|
]);
|
|
|
|
$arr = ['NAME_MISMATCH','V2_ACCOUNT_SIMPLE_BAN', 'SENDNUM_LIMIT'];
|
|
$msg = '';
|
|
if (in_array($result['err_code'], $arr)) {
|
|
$msg = $result['err_code_des'];
|
|
}
|
|
|
|
// if ($result['err_code'] == 'SENDNUM_LIMIT') {
|
|
// throw new ErrorCodeException(ErrorCode::PAYMENT_SEND_NUM_LIMIT);
|
|
// } elseif ($result['err_code'] == 'V2_ACCOUNT_SIMPLE_BAN') {
|
|
// throw new ErrorCodeException(ErrorCode::PAYMENT_V2_ACCOUNT_SIMPLE_BAN);
|
|
// } elseif ($result['err_code'] == 'NAME_MISMATCH') {
|
|
// throw new ErrorCodeException(ErrorCode::PAYMENT_NAME_MISMATCH);
|
|
// } elseif ($result['err_code'] == 'AMOUNT_LIMIT') {
|
|
// throw new ErrorCodeException(ErrorCode::PAYMENT_AMOUNT_LIMIT);
|
|
// }
|
|
|
|
throw new ErrorCodeException(ErrorCode::WITHDRAW_PAYMENT_FAIL);
|
|
}
|
|
|
|
// 一旦有提现成功的,说明没有特殊原因影响,可能临时解除了,比如临时充钱了,去除标志防止再次缺钱的发生
|
|
$redis->del($refuseReasonForSpecialKey);
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* 支付参数加签
|
|
* @param $parameters
|
|
* @param $key
|
|
* @return string
|
|
*/
|
|
private function signture($parameters, $key)
|
|
{
|
|
// 按字典序排序参数
|
|
ksort($parameters);
|
|
|
|
// http_query
|
|
$queryParams = $this->http_query($parameters);
|
|
|
|
// 加入KEY
|
|
$queryParams = $queryParams . "&key=" . $key;
|
|
|
|
// MD5加密
|
|
$queryParams = md5($queryParams);
|
|
|
|
// 字符转为大写
|
|
return strtoupper($queryParams);
|
|
}
|
|
|
|
/**
|
|
* 参数转为http query字串
|
|
* @param $parameters
|
|
* @return string
|
|
*/
|
|
private function http_query($parameters) {
|
|
|
|
$http_query = [];
|
|
foreach ($parameters as $key => $value) {
|
|
$http_query[] = $key.'='.$value;
|
|
}
|
|
|
|
return implode('&', $http_query);
|
|
}
|
|
}
|