terminal = $terminal; $this->config = WeChatConfigService::getPayConfigByTerminal($terminal); $this->app = new Application($this->config); if ($userId !== null) { $this->auth = UserAuth::where(['user_id' => $userId, 'terminal' => $terminal])->findOrEmpty(); } } /** * @notes 发起微信支付统一下单 * @param $from * @param $order * @return array|false|string * @author 段誉 * @date 2021/8/4 15:05 */ public function pay($from, $order) { try { switch ($this->terminal) { case UserTerminalEnum::WECHAT_MMP: $config = WeChatConfigService::getMnpConfig(); $result = $this->jsapiPay($from, $order, $config['app_id']); break; case UserTerminalEnum::WECHAT_OA: $config = WeChatConfigService::getOaConfig(); $result = $this->jsapiPay($from, $order, $config['app_id']); break; case UserTerminalEnum::IOS: case UserTerminalEnum::ANDROID: $config = WeChatConfigService::getOpConfig(); $result = $this->appPay($from, $order, $config['app_id']); break; case UserTerminalEnum::H5: $config = WeChatConfigService::getOaConfig(); $result = $this->mwebPay($from, $order, $config['app_id']); break; case UserTerminalEnum::PC: $config = WeChatConfigService::getOaConfig(); $result = $this->nativePay($from, $order, $config['app_id']); break; default: throw new \Exception('支付方式错误'); } return [ 'config' => $result, 'pay_way' => PayEnum::WECHAT_PAY ]; } catch (\Exception $e) { $this->setError($e->getMessage()); return false; } } /** * @notes jsapiPay * @param $from * @param $order * @param $appId * @return mixed * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/2/28 12:12 */ public function jsapiPay($from, $order, $appId) { $response = $this->app->getClient()->postJson("v3/pay/transactions/jsapi", [ "appid" => $appId, "mchid" => $this->config['mch_id'], "description" => $this->payDesc($from), "out_trade_no" => $order['pay_sn'], "notify_url" => $this->config['notify_url'], "amount" => [ "total" => intval($order['order_amount'] * 100), ], "payer" => [ "openid" => $this->auth['openid'] ], 'attach' => $from ]); $result = $response->toArray(false); $this->checkResultFail($result); return $this->getPrepayConfig($result['prepay_id'], $appId); } /** * @notes 网站native * @param $from * @param $order * @param $appId * @return mixed * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/2/28 12:12 */ public function nativePay($from, $order, $appId) { $response = $this->app->getClient()->postJson('v3/pay/transactions/native', [ 'appid' => $appId, 'mchid' => $this->config['mch_id'], 'description' => $this->payDesc($from), 'out_trade_no' => $order['pay_sn'], 'notify_url' => $this->config['notify_url'], 'amount' => [ 'total' => intval($order['order_amount'] * 100), ], 'attach' => $from ]); $result = $response->toArray(false); $this->checkResultFail($result); return $result['code_url']; } /** * @notes appPay * @param $from * @param $order * @param $appId * @return mixed * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/2/28 12:12 */ public function appPay($from, $order, $appId) { $response = $this->app->getClient()->postJson('v3/pay/transactions/app', [ 'appid' => $appId, 'mchid' => $this->config['mch_id'], 'description' => $this->payDesc($from), 'out_trade_no' => $order['pay_sn'], 'notify_url' => $this->config['notify_url'], 'amount' => [ 'total' => intval($order['order_amount'] * 100), ], 'attach' => $from ]); $result = $response->toArray(false); $this->checkResultFail($result); return $this->configForPayment($result['prepay_id'], $appId); } /** * @notes h5 * @param $from * @param $order * @param $appId * @param $redirectUrl * @return mixed * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/2/28 12:13 */ public function mwebPay($from, $order, $appId) { $response = $this->app->getClient()->postJson('v3/pay/transactions/h5', [ 'appid' => $appId, 'mchid' => $this->config['mch_id'], 'description' => $this->payDesc($from), 'out_trade_no' => $order['pay_sn'], 'notify_url' => $this->config['notify_url'], 'amount' => [ 'total' => intval($order['order_amount'] * 100), ], 'attach' => $from, 'scene_info' => [ 'payer_client_ip' => request()->ip(), 'h5_info' => [ 'type' => 'Wap', ] ] ]); $result = $response->toArray(false); $this->checkResultFail($result); $domain = request()->domain(); if (!empty(env('project.test_web_domain')) && env('APP_DEBUG')) { $domain = env('project.test_web_domain'); } $redirectUrl = $domain . '/mobile'. $order['redirect_url'] .'?id=' . $order['id'] . '&from='. $from . '&checkPay=true'; return $result['h5_url'] . '&redirect_url=' . urlencode($redirectUrl); } /** * @notes 退款 * @param array $refundData * @return mixed * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/2/28 16:53 */ public function refund(array $refundData) { $response = $this->app->getClient()->postJson('v3/refund/domestic/refunds', [ 'transaction_id' => $refundData['transaction_id'], 'out_refund_no' => $refundData['refund_sn'], 'amount' => [ 'refund' => intval($refundData['refund_amount'] * 100), 'total' => intval($refundData['total_amount'] * 100), 'currency' => 'CNY', ] ]); $result = $response->toArray(false); $this->checkResultFail($result); return $result; } /** * @notes 查询退款 * @param $refundSn * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/3/1 11:16 */ public function queryRefund($refundSn) { $response = $this->app->getClient()->get("v3/refund/domestic/refunds/{$refundSn}"); return $response->toArray(false); } /** * @notes 支付描述 * @param $from * @return string * @author 段誉 * @date 2023/2/27 17:54 */ public function payDesc($from) { $desc = [ 'order' => '商品', 'recharge' => '充值', ]; return $desc[$from] ?? '商品'; } /** * @notes 捕获错误 * @param $result * @throws \Exception * @author 段誉 * @date 2023/2/28 12:09 */ public function checkResultFail($result) { if (!empty($result['code']) || !empty($result['message'])) { throw new \Exception('微信:'. $result['code'] . '-' . $result['message']); } } /** * @notes 预支付配置 * @param $prepayId * @param $appId * @return mixed[] * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException * @author 段誉 * @date 2023/2/28 17:38 */ public function getPrepayConfig($prepayId, $appId) { return $this->app->getUtils()->buildBridgeConfig($prepayId, $appId); } /** * @notes 支付回调 * @return \Psr\Http\Message\ResponseInterface * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException * @throws \ReflectionException * @throws \Throwable * @author 段誉 * @date 2023/2/28 14:20 */ public function notify() { $server = $this->app->getServer(); // 支付通知 $server->handlePaid(function (Message $message) { $data = ['trade_state' => $message['trade_state'] ?? '', 'out_trade_no' => $message['out_trade_no'] ?? '', 'transaction_id' => $message['transaction_id'] ?? '', 'attach' => $message['attach'] ?? '']; Log::info('wechat pay notify: ' . var_export($data, true)); if ($message['trade_state'] === 'SUCCESS') { $extra['transaction_id'] = $message['transaction_id']; $attach = $message['attach']; $message['out_trade_no'] = mb_substr($message['out_trade_no'], 0, 18); switch ($attach) { case 'recharge': $order = RechargeOrder::where(['sn' => $message['out_trade_no']])->findOrEmpty(); Log::info('wechat pay notify: ' . var_export($order, true)); if($order->isEmpty() || $order->pay_status == PayEnum::ISPAID) { return true; } PayNotifyLogic::handle('recharge', $message['out_trade_no'], $extra); break; } } return true; }); // 退款通知 $server->handleRefunded(function (Message $message) { return true; }); return $server->serve(); } public function configForPayment($prepayId, $appId) { $config = [ 'appId' => $appId, 'timeStamp' => strval(time()), 'nonceStr' => uniqid(), 'package' => "prepay_id=$prepayId", 'signType' => 'RSA', ]; $message = $config['appId'] . "\n" . $config['timeStamp'] . "\n" . $config['nonceStr'] . "\n" . $prepayId . "\n"; openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); $config['paySign'] = $sign; $config['timestamp'] = $config['timeStamp']; $config['partnerid'] = $this->config['mch_id']; unset($config['timeStamp']); return $config; } protected function getPrivateKey() { if (!file_exists($this->config['private_key'])) { throw new \InvalidArgumentException( "SSL certificate not found: {$this->config['private_key']}" ); } return openssl_pkey_get_private(file_get_contents($this->config['private_key'])); } }