|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace App\Service;
|
|
|
|
|
|
|
|
use App\Exception\BusinessException;
|
|
|
|
use App\Helper\Efps\Api;
|
|
|
|
use App\Helper\Efps\Result;
|
|
|
|
use App\Helper\Log;
|
|
|
|
use App\Helper\StringHelper;
|
|
|
|
use App\Model\App;
|
|
|
|
use App\Model\BankCard;
|
|
|
|
use App\Model\Order;
|
|
|
|
use App\Model\PrePayLog;
|
|
|
|
use App\Model\RefundOrder;
|
|
|
|
use App\Model\User;
|
|
|
|
use App\Request\BindCardConfirmRequest;
|
|
|
|
use App\Request\BindCardRequest;
|
|
|
|
use App\Request\PaymentQueryRequest;
|
|
|
|
use App\Request\PaymentRequest;
|
|
|
|
use App\Request\ProtocolPayConfirmRequest;
|
|
|
|
use App\Request\ProtocolPayPreRequest;
|
|
|
|
use App\Request\RefundQueryRequest;
|
|
|
|
use App\Request\RefundRequest;
|
|
|
|
use App\Request\UnBindCardRequest;
|
|
|
|
|
|
|
|
class PaymentService extends AbstractService
|
|
|
|
{
|
|
|
|
public function generateMemberId($appKey, $cardNo) {
|
|
|
|
return md5($appKey . '-' . $cardNo);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function generateMemberIdNew($appId, $outMemberId) {
|
|
|
|
return md5($appId . '-' . $outMemberId);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function createOrder(App $app, array $params, $user) {
|
|
|
|
$order = new Order();
|
|
|
|
$order->app_id = $app->app_id;
|
|
|
|
$order->order_no = $params['outTradeNo'];
|
|
|
|
$order->member_id = $user->member_id;
|
|
|
|
$order->out_member_id = $params['outMemberId'] ?? '';
|
|
|
|
$order->out_order_no = $params['outOrderNo'] ?? '';
|
|
|
|
$order->amount = $params['payAmount'] ?? 0;
|
|
|
|
$order->notify_url = $params['notifyUrl'] ?? '';
|
|
|
|
$order->order_info = json_encode($params['orderInfo'] ?? [], JSON_UNESCAPED_UNICODE);
|
|
|
|
return $order;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function handlePayResult(Result $result, Order $order) {
|
|
|
|
if ($result->get('payState') === '00') {
|
|
|
|
$order->status = Order::STATUS_PAYED;
|
|
|
|
$order->fee = $result->get('procedureFee', 0);
|
|
|
|
$order->pay_order_no = $result->get('transactionNo', '');
|
|
|
|
$order->channel_order_no = $result->get('channelOrder', '');
|
|
|
|
$order->payed_at = date('Y-m-d H:i:s');
|
|
|
|
$order->save();
|
|
|
|
} elseif ($result->get('payState') === '01') {
|
|
|
|
$order->status = Order::STATUS_FAILED;
|
|
|
|
$order->error_code = '01';
|
|
|
|
$order->error_msg = '支付失败';
|
|
|
|
$order->payed_at = date('Y-m-d H:i:s');
|
|
|
|
$order->save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function handleRefundResult(Result $result, RefundOrder $order) {
|
|
|
|
if ($result->get('refundState') === '00') {
|
|
|
|
$order->status = RefundOrder::STATUS_REFUND_SUCCESS;
|
|
|
|
$order->fee = $result->get('procedureFee', 0);
|
|
|
|
$order->refunded_at = date('Y-m-d H:i:s');
|
|
|
|
$order->save();
|
|
|
|
} elseif ($result->get('refundState') === '01') {
|
|
|
|
$order->status = RefundOrder::STATUS_REFUND_FAILED;
|
|
|
|
$order->error_code = '01';
|
|
|
|
$order->error_msg = '处理失败';
|
|
|
|
$order->refunded_at = date('Y-m-d H:i:s');
|
|
|
|
$order->save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function protocolPayPreRequest($params)
|
|
|
|
{
|
|
|
|
$req = new ProtocolPayPreRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$data = $req->getData();
|
|
|
|
|
|
|
|
if ($data['payAmount'] > 300000) {
|
|
|
|
throw new BusinessException('超出限额');
|
|
|
|
}
|
|
|
|
|
|
|
|
$order = Order::where('app_id', $app->app_id)->where('out_order_no', $data['outOrderNo'])->first();
|
|
|
|
if ($order) {
|
|
|
|
throw new BusinessException('订单重复');
|
|
|
|
}
|
|
|
|
|
|
|
|
$data['outTradeNo'] = StringHelper::generateOrderNo();
|
|
|
|
$result = Api::protocolPayPre($data);
|
|
|
|
|
|
|
|
$user = User::where('app_id', $app->app_id)->where('out_member_id', $data['outMemberId'])->first();
|
|
|
|
if (!$user) {
|
|
|
|
Log::error('UserUndefiend: ', $data);
|
|
|
|
throw new BusinessException('用户不存在');
|
|
|
|
}
|
|
|
|
$order = $this->createOrder($app, $data, $user);
|
|
|
|
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
$order->status = Order::STATUS_APPLY_FAIL;
|
|
|
|
$order->save();
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
if (is_null($result->get('payResult'))) {
|
|
|
|
$order->token = $result->get('token', '');
|
|
|
|
$order->protocol = $result->get('protocol', '');
|
|
|
|
$order->status = Order::STATUS_WAIT_PAY;
|
|
|
|
$order->save();
|
|
|
|
} else {
|
|
|
|
$bankCard = BankCard::where('app_id', $app->app_id)
|
|
|
|
->where('out_member_id', $data['outMemberId'])
|
|
|
|
->where('sms_no', $data['smsNo'])
|
|
|
|
->where('status', BankCard::STATUS_WAIT_CONFIRM)
|
|
|
|
->first();
|
|
|
|
|
|
|
|
if ($bankCard) {
|
|
|
|
$bankCard->status = BankCard::STATUS_ACTIVE;
|
|
|
|
$bankCard->protocol = $result->get('protocol', '');
|
|
|
|
$bankCard->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
$order->protocol = $result->get('protocol', '');
|
|
|
|
$order->status = Order::STATUS_WAIT_PAY;
|
|
|
|
$order->save();
|
|
|
|
}
|
|
|
|
return $result->getData();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function protocolPayConfirm($params)
|
|
|
|
{
|
|
|
|
$req = new ProtocolPayConfirmRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$data = $req->getData();
|
|
|
|
$order = Order::where('app_id', $app->app_id)
|
|
|
|
->where('token', $data['token'])
|
|
|
|
->where('protocol', $data['protocol'])
|
|
|
|
->first();
|
|
|
|
if (!$order) {
|
|
|
|
throw new BusinessException('订单不存在');
|
|
|
|
}
|
|
|
|
$result = Api::protocolPayConfirm($req->getData());
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
$order->status = Order::STATUS_FAILED;
|
|
|
|
$order->error_code = $result->getCode();
|
|
|
|
$order->error_msg = $result->getMessage();
|
|
|
|
$order->payed_at = date('Y-m-d H:i:s');
|
|
|
|
$order->save();
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
// 00 01 03
|
|
|
|
$this->handlePayResult($result, $order);
|
|
|
|
return $result->getData();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function bindCard($params) {
|
|
|
|
$req = new BindCardRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$reqData = $req->getData();
|
|
|
|
|
|
|
|
$memberId = $this->generateMemberIdNew($app->app_id, $reqData['outMemberId']);
|
|
|
|
$reqData['memberId'] = $memberId;
|
|
|
|
|
|
|
|
$bankCard = BankCard::where('app_id', $app->app_id)
|
|
|
|
->where('out_member_id', $reqData['outMemberId'])
|
|
|
|
->where('bank_card_no', $reqData['bankCardNo'])
|
|
|
|
->where('status', BankCard::STATUS_ACTIVE)
|
|
|
|
->first();
|
|
|
|
if ($bankCard) {
|
|
|
|
throw new BusinessException('该卡已绑定');
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = Api::bindCard($reqData);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
$message = $result->getMessage();
|
|
|
|
throw new BusinessException($message == '支付渠道未配置' ? '暂不支持该银行卡' : $message);
|
|
|
|
}
|
|
|
|
|
|
|
|
$user = User::where('out_member_id', $reqData['outMemberId'])->where('app_id', $app->app_id)->first();
|
|
|
|
if (!$user) {
|
|
|
|
$user = new User();
|
|
|
|
$user->member_id = $memberId;
|
|
|
|
$user->out_member_id = $reqData['outMemberId'];
|
|
|
|
$user->app_id = $app->app_id;
|
|
|
|
$user->real_name = $reqData['userName'];
|
|
|
|
$user->card_no = $reqData['certificatesNo'];
|
|
|
|
$user->mobile = $reqData['phoneNum'];
|
|
|
|
$user->save();
|
|
|
|
}
|
|
|
|
if ($user && empty($user->out_member_id)) {
|
|
|
|
$user->out_member_id = $reqData['outMemberId'];
|
|
|
|
$user->save();
|
|
|
|
}
|
|
|
|
$bankCard = new BankCard();
|
|
|
|
$bankCard->member_id = $memberId;
|
|
|
|
$bankCard->out_member_id = $reqData['outMemberId'];
|
|
|
|
$bankCard->app_id = $app->app_id;
|
|
|
|
$bankCard->real_name = $reqData['userName'];
|
|
|
|
$bankCard->card_no = $reqData['certificatesNo'];
|
|
|
|
$bankCard->mobile = $reqData['phoneNum'];
|
|
|
|
$bankCard->sms_no = $result->get('smsNo', '');
|
|
|
|
$bankCard->bank_card_no = $reqData['bankCardNo'];
|
|
|
|
$bankCard->bank_card_type = $reqData['bankCardType'];
|
|
|
|
$bankCard->protocol = $result->get('protocol', '');
|
|
|
|
$bankCard->expired = $reqData['expired'] ?? '';
|
|
|
|
$bankCard->cvn = $reqData['cvn'] ?? '';
|
|
|
|
$bankCard->status = BankCard::STATUS_WAIT_CONFIRM;
|
|
|
|
$bankCard->save();
|
|
|
|
return array_merge($result->getData(), ['memberId' => $memberId]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function bindCardConfirm($params)
|
|
|
|
{
|
|
|
|
$req = new BindCardConfirmRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$reqData = $req->getData();
|
|
|
|
|
|
|
|
$bankCard = BankCard::where('app_id', $app->app_id)
|
|
|
|
->where('out_member_id', $reqData['outMemberId'])
|
|
|
|
->where('sms_no', $reqData['smsNo'])
|
|
|
|
->where('status', BankCard::STATUS_WAIT_CONFIRM)
|
|
|
|
->first();
|
|
|
|
if (!$bankCard) {
|
|
|
|
throw new BusinessException('绑卡申请不存在');
|
|
|
|
}
|
|
|
|
|
|
|
|
$reqData['memberId'] = $bankCard->member_id;
|
|
|
|
$result = Api::bindCardConfirm($reqData);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
$bankCard->status = BankCard::STATUS_ACTIVE;
|
|
|
|
$bankCard->protocol = $result->get('protocol', '');
|
|
|
|
$bankCard->save();
|
|
|
|
|
|
|
|
return $result->getData();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function refundQuery($params)
|
|
|
|
{
|
|
|
|
$req = new RefundQueryRequest($params);
|
|
|
|
$data = $req->getData();
|
|
|
|
$result = Api::refundQuery($data);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
$order = RefundOrder::where('out_refund_order_no', $result->get('outRefundNo'))
|
|
|
|
->where('status', RefundOrder::STATUS_APPLY_SUCCESS)
|
|
|
|
->first();
|
|
|
|
if ($order) {
|
|
|
|
$this->handleRefundResult($result, $order);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result->getData([
|
|
|
|
'outRefundNo',
|
|
|
|
'transactionNo',
|
|
|
|
'amount',
|
|
|
|
'refundAmount',
|
|
|
|
'refundState'
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function paymentQuery($params)
|
|
|
|
{
|
|
|
|
$req = new PaymentQueryRequest($params);
|
|
|
|
$data = $req->getData();
|
|
|
|
$result = Api::paymentQuery($data);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
$order = Order::where('out_order_no', $result->get('outTradeNo'))
|
|
|
|
->where('status', Order::STATUS_WAIT_PAY)
|
|
|
|
->first();
|
|
|
|
if ($order) {
|
|
|
|
$this->handlePayResult($result, $order);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result->getData([
|
|
|
|
'outTradeNo',
|
|
|
|
'transactionNo',
|
|
|
|
'payState',
|
|
|
|
'procedureFee',
|
|
|
|
'amount'
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function unbindCard($params)
|
|
|
|
{
|
|
|
|
$req = new UnBindCardRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$reqData = $req->getData();
|
|
|
|
$bankCard = BankCard::where('app_id', $app->app_id)
|
|
|
|
->where('out_member_id', $reqData['outMemberId'])
|
|
|
|
->where('protocol', $reqData['protocol'])
|
|
|
|
->where('status', BankCard::STATUS_ACTIVE)
|
|
|
|
->first();
|
|
|
|
if (!$bankCard) {
|
|
|
|
throw new BusinessException('绑卡不存在');
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = Api::unBindCard($reqData);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
$bankCard->status = BankCard::STATUS_UNBIND;
|
|
|
|
$bankCard->save();
|
|
|
|
|
|
|
|
return $result->getData();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function refund($params)
|
|
|
|
{
|
|
|
|
$req = new RefundRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$data = $req->getData();
|
|
|
|
|
|
|
|
$refundOrder = new RefundOrder();
|
|
|
|
$refundOrder->app_id = $app->app_id;
|
|
|
|
$refundOrder->out_order_no = $data['outTradeNo'] ?: '';
|
|
|
|
$refundOrder->out_refund_order_no = $data['outRefundNo'] ?: '';
|
|
|
|
$refundOrder->order_amount = $data['amount'] ?: '';
|
|
|
|
$refundOrder->refund_amount = $data['refundAmount'] ?: '';
|
|
|
|
$refundOrder->remark = $data['remark'] ?: '';
|
|
|
|
$refundOrder->notify_url = $data['notifyUrl'] ?: '';
|
|
|
|
|
|
|
|
$result = Api::refund($data);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
$refundOrder->status = RefundOrder::STATUS_APPLY_FAILED;
|
|
|
|
$refundOrder->error_code = $result->getCode();
|
|
|
|
$refundOrder->error_msg = $result->getMessage();
|
|
|
|
$refundOrder->save();
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
$refundOrder->status = RefundOrder::STATUS_APPLY_SUCCESS;
|
|
|
|
$refundOrder->save();
|
|
|
|
|
|
|
|
return $result->getData();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function payment($params)
|
|
|
|
{
|
|
|
|
$req = new PaymentRequest($params);
|
|
|
|
$app = $req->getApp();
|
|
|
|
$reqData = $req->getData();
|
|
|
|
|
|
|
|
if (!$reqData['outOrderNo']) {
|
|
|
|
throw new BusinessException('订单号错误');
|
|
|
|
}
|
|
|
|
if (!$reqData['outMemberId']) {
|
|
|
|
throw new BusinessException('用户ID不能为空');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($reqData['amount'])) {
|
|
|
|
if (!is_numeric($reqData['amount'])) {
|
|
|
|
throw new BusinessException('请输入金额');
|
|
|
|
}
|
|
|
|
if ($reqData['amount'] < 100 || $reqData['amount'] > 300000) {
|
|
|
|
Log::error('reqData:', $reqData);
|
|
|
|
throw new BusinessException('充值金额必须为100~300000');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$reqData['notifyUrl']) {
|
|
|
|
throw new BusinessException('通知地址不能为空');
|
|
|
|
}
|
|
|
|
|
|
|
|
$log = PrePayLog::where('app_id', $app->app_id)->where('out_order_no', $reqData['outOrderNo'])->first();
|
|
|
|
$order = Order::where('app_id', $app->app_id)->where('out_order_no', $reqData['outOrderNo'])->first();
|
|
|
|
if ($log || $order) {
|
|
|
|
throw new BusinessException('订单重复');
|
|
|
|
}
|
|
|
|
|
|
|
|
$token = md5(microtime(true) . json_encode($reqData) . $app->app_id . $app->app_key);
|
|
|
|
|
|
|
|
$log = new PrePayLog();
|
|
|
|
$log->app_id = $app->app_id;
|
|
|
|
$log->out_order_no = $reqData['outOrderNo'];
|
|
|
|
$log->amount = $reqData['amount'] ?: 0;
|
|
|
|
$log->out_member_id = $reqData['outMemberId'];
|
|
|
|
$log->notify_url = $reqData['notifyUrl'];
|
|
|
|
$log->redirect_url = $reqData['redirectUrl'] ?? '';
|
|
|
|
$log->token = $token;
|
|
|
|
$log->save();
|
|
|
|
|
|
|
|
return ['payUrl' => env('WEB_HOST') . '/payment.html?token=' . $token];
|
|
|
|
}
|
|
|
|
|
|
|
|
public function query($params)
|
|
|
|
{
|
|
|
|
$req = new PaymentQueryRequest($params);
|
|
|
|
$data = $req->getData();
|
|
|
|
$app = $req->getApp();
|
|
|
|
|
|
|
|
if (empty($data['outOrderNo'])) {
|
|
|
|
throw new BusinessException('订单号不能为空');
|
|
|
|
}
|
|
|
|
|
|
|
|
$order = Order::where('app_id', $app->app_id)->where('out_order_no', $data['outOrderNo'])->first();
|
|
|
|
if (!$order) {
|
|
|
|
throw new BusinessException('订单不存在');
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = Api::paymentQuery(['outTradeNo' => $order->order_no]);
|
|
|
|
if (!$result->isSuccess()) {
|
|
|
|
throw new BusinessException($result->getMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->handlePayResult($result, $order);
|
|
|
|
|
|
|
|
return [
|
|
|
|
'outOrderNo' => $data['outOrderNo'],
|
|
|
|
'orderNo' => $order->order_no,
|
|
|
|
'payState' => $result->get('payState'),
|
|
|
|
'amount' => $result->get('amount'),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|