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); } }