123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- <?php
- // +----------------------------------------------------------------------
- // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2017~2024 https://www.yiovo.com All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
- // +----------------------------------------------------------------------
- // | Author: 萤火科技 <admin@yiovo.com>
- // +----------------------------------------------------------------------
- declare (strict_types=1);
- namespace app\common\library\paypal;
- use Alipay\EasySDK\Kernel\Config;
- use Alipay\EasySDK\Kernel\Factory;
- use Alipay\EasySDK\Kernel\Util\ResponseChecker;
- use app\common\enum\Client as ClientEnum;
- use app\common\library\Log;
- use app\common\library\payment\gateway\Driver;
- use cores\exception\BaseException;
- use cores\Request;
- use PayPal\Api\Amount;
- use PayPal\Api\Order;
- use PayPal\Api\Payer;
- use PayPal\Api\Payment;
- use PayPal\Api\PaymentExecution;
- use PayPal\Api\RedirectUrls;
- use PayPal\Api\Transaction;
- use PayPal\Api\VerifyWebhookSignature;
- use PayPal\Auth\OAuthTokenCredential;
- use PayPal\Exception\PayPalConnectionException;
- use PayPal\Rest\ApiContext;
- use think\Exception;
- use think\exception\HttpException;
- /**
- * paypal驱动
- * Class PayPal
- * @package app\common\library\payment\gateway\driver
- */
- class PayPal
- {
- // 统一下单API的返回结果
- private $result;
- // 异步通知的请求参数 (由第三方支付发送)
- private $notifyParams;
- protected $config;
- protected $notifyWebHookId;// 3NP026061E6858914
- // 异步通知的验证结果
- private $notifyResult;
- public $apiContext;
- public function __construct($config)
- {
- // 秘钥配置
- $this->config = $config;
- $this->notifyWebHookId = $this->config['web_hook_id'];
- $this->apiContext = new ApiContext(
- new OAuthTokenCredential(
- $this->config['client_id'],
- $this->config['secret']
- )
- );
- $this->apiContext->setConfig([
- 'mode' => $this->config['mode'],//sandbox, live
- 'log.LogEnabled' => true,
- 'log.FileName' => app()->getRootPath() . 'runtime/log/PayPal.log', // 记录日志
- 'log.LogLevel' => 'debug', // 在live上用info
- 'cache.enable' => true,
- ]);
- }
- /**
- * 统一下单API
- * @param string $outTradeNo 交易订单号
- * @param string $totalFee 实际付款金额
- * @param array $extra 附加的数据 (需要携带H5端支付成功后跳转的url)
- * @return bool|array
- * @throws BaseException
- */
- public function unify(string $outTradeNo, string $totalFee, array $extra = [], $currency = 'USD')
- {
- $apiContext = new ApiContext(
- new OAuthTokenCredential(
- $this->config['client_id'], // ClientID
- $this->config['secret'] // ClientSecret
- )
- );
- // After Step 2
- $payer = new Payer();
- $payer->setPaymentMethod('paypal');
- $amount = new Amount();
- $amount->setTotal($totalFee);
- $amount->setCurrency($currency);
- $transaction = new Transaction();
- $transaction->setAmount($amount);
- $redirectUrls = new RedirectUrls();
- //live
- //$return_url = config('app.app_host') . $this->config['return_url'] . $outTradeNo;
- //$cancel_Url = config('app.app_host').$this->config['cancel_url'];
- //sandbox
- $return_url = 'https://lar.lmm.gold/api/index/index';
- $cancel_url = 'https://lar.lmm.gold/store/index.html';
- $redirectUrls->setReturnUrl($return_url)
- ->setCancelUrl($cancel_url);
- $payment = new Payment();
- $payment->setIntent('sale')
- ->setPayer($payer)
- ->setTransactions(array($transaction))
- ->setRedirectUrls($redirectUrls);
- $this->result = $payment->create($apiContext);// This will print the detailed information on the exception.
- //REALLY HELPFUL FOR DEBUGGING
- //echo "\n\nRedirect user to approval_url: " . $payment->getApprovalLink() . "\n";
- return ['approval_link' => $payment->getApprovalLink()];
- }
- /**
- * 交易查询 (主动查询订单支付状态)
- * @param string $outTradeNo 交易订单号
- * @return array|null
- * @throws BaseException
- */
- public function tradeQuery(string $outTradeNo): ?array
- {
- try {
- $payment = Payment::get($outTradeNo, $this->apiContext);
- // 记录日志
- Log::append('Paypal-tradeQuery', ['outTradeNo' => $outTradeNo, 'result' => json_encode($result)]);
- // 处理响应或异常
- //$this->throwError($result->msg . "," . $result->subMsg);
- // 返回查询成功的结果
- return $result->toArray();
- } catch (\Throwable $e) {
- $this->throwError('支付宝API交易查询失败:' . $e->getMessage(), true, 'tradeQuery');
- }
- return null;
- }
- public function executePayment($paymentId)
- {
- try {
- $payment = Payment::get($paymentId, $this->apiContext);
- $execution = new PaymentExecution();
- $execution->setPayerId($payment->getPayer()->getPayerInfo()->getPayerId());
- // 执行付款
- $payment->execute($execution, $this->apiContext);
- $payment::get($payment->getId(), $this->apiContext);
- $transactions = $payment->getTransactions();
- \think\facade\Log::error('$transactions::' . json_encode($transactions));
- if ($payment->getState() == 'approved' && $payment->getId() == $paymentId) {
- //related_resources->sale->id
- return true;
- }
- return false;
- } catch (\Exception $e) {
- \think\facade\Log::error('executePayment', ['paymentId' => $paymentId, 'errMsg' => $e->getMessage()]);
- $this->throwError('执行失败:' . $e->getMessage(), true, 'tradeQuery');
- return false;
- }
- }
- /**
- * 支付成功后的异步通知
- * @return bool
- */
- public function notify(Request $request, $webHookId): bool
- {
- // 接收表单数据
- try {
- $headers = $request->header();
- $headers = array_change_key_case($headers, CASE_UPPER);
- $content = $request->getContent();
- \think\facade\Log::error('notify::' . json_encode($headers));
- // 如果是laravel,这里获请求头的方法可能要变,现在是$headers['PAYPAL-AUTH-ALGO'],去到laravel的话可能要$headers['PAYPAL-AUTH-ALGO'][0],到时试试就知道了,实在不行打日志看看数据结构再确定如何获取
- $signatureVerification = new VerifyWebhookSignature();
- $signatureVerification->setAuthAlgo($headers['PAYPAL-AUTH-ALGO']);
- $signatureVerification->setTransmissionId($headers['PAYPAL-TRANSMISSION-ID']);
- $signatureVerification->setCertUrl($headers['PAYPAL-CERT-URL']);
- $signatureVerification->setWebhookId($webHookId ?: $this->notifyWebHookId);
- $signatureVerification->setTransmissionSig($headers['PAYPAL-TRANSMISSION-SIG']);
- $signatureVerification->setTransmissionTime($headers['PAYPAL-TRANSMISSION-TIME']);
- $signatureVerification->setRequestBody($content);
- $result = clone $signatureVerification;
- $output = $signatureVerification->post($this->apiContext);
- \think\facade\Log::error('notify' . json_encode($output));
- if ($output->getVerificationStatus() == 'SUCCESS') {
- return true;
- }
- throw new HttpException(400, 'Verify Failed.');
- } catch (HttpException $exception) {
- \think\facade\Log::error('PayPal Notification Verify Failed' . $exception->getMessage());
- return false;
- }
- }
- /**
- * PAYPAL退款API
- * @param string $outTradeNo 第三方交易单号
- * @param string $refundAmount 退款金额
- * @param array $extra 附加的数据
- * @return bool
- * @throws BaseException
- */
- public function refund(string $outTradeNo, string $refundAmount, array $extra = []): bool
- {
- try {
- // 发起API调用
- $outRequestNo = (string)time();
- return true;
- } catch (\Throwable $e) {
- $this->throwError('支付宝API退款请求:' . $e->getMessage(), true, 'refund');
- }
- return false;
- }
- /**
- * 单笔转账接口
- * @param string $outTradeNo 交易订单号
- * @param string $totalFee 实际付款金额
- * @param array $extra 附加的数据 (ALIPAY_LOGON_ID支付宝登录号,支持邮箱和手机号格式; name参与方真实姓名)
- * @return bool
- */
- public function transfers(string $outTradeNo, string $totalFee, array $extra = []): bool
- {
- return false;
- }
- /**
- * 获取异步回调的请求参数
- * @return array
- */
- public function getNotifyParams(): array
- {
- return [
- // 第三方交易流水号
- 'buyerId' => $this->notifyParams['PayerID'],
- 'paymentId' => $this->notifyParams['paymentId']
- ];
- }
- /**
- * 返回异步通知结果的输出内容
- * @return string
- */
- public function getNotifyResponse(): string
- {
- return $this->notifyResult ? 'success' : 'FAIL';
- }
- public function getUnifyResult(): array
- {
- if (empty($this->result->getApprovalLink())) {
- $this->throwError('paypal当前没有unify结果', true, 'getUnifyResult');
- return [];
- }
- // 整理返回的数据
- return ['approval_link' => $this->result->getApprovalLink(), 'id' => $this->result->getId()];
- }
- /**
- * 设置支付宝配置信息(全局只需设置一次)
- * @param array $options 支付宝配置信息
- * @param string $client 下单客户端
- * @return null
- */
- public function setOptions(array $options, string $client)
- {
- return $this;
- }
- /**
- * 输出错误信息
- * @param string $errMessage 错误信息
- * @param bool $isLog 是否记录日志
- * @param string $action 当前的操作
- * @throws BaseException
- */
- private function throwError(string $errMessage, bool $isLog = false, string $action = '')
- {
- $this->error = $errMessage;
- $isLog && Log::append("Alipay-{$action}", ['errMessage' => $errMessage]);
- throwError($errMessage);
- }
- /**
- * 获取和验证下单接口所需的附加数据
- * @param array $extra
- * @return array
- * @throws BaseException
- */
- private function extraAsUnify(array $extra): array
- {
- if (!array_key_exists('returnUrl', $extra)) {
- $this->throwError('returnUrl参数不存在');
- }
- return $extra;
- }
- /**
- * 异步回调地址
- * @return string
- */
- private function notifyUrl(): string
- {
- // 例如:https://www.xxxx.com/alipayNotice.php
- return base_url() . 'alipayNotice.php';
- }
- }
|