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