|
@@ -19,13 +19,19 @@ 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\HttpException;
|
|
|
|
|
|
/**
|
|
|
* paypal驱动
|
|
@@ -40,9 +46,37 @@ class PayPal extends Driver
|
|
|
// 异步通知的请求参数 (由第三方支付发送)
|
|
|
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'],
|
|
|
+ 'log.LogEnabled' => true,
|
|
|
+ 'log.FileName' => app()->getRootPath() . 'runtime/log/PayPal.log', // 记录日志
|
|
|
+ 'log.LogLevel' => 'debug', // 在live上用info
|
|
|
+ 'cache.enable' => true,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 统一下单API
|
|
|
* @param string $outTradeNo 交易订单号
|
|
@@ -81,24 +115,23 @@ class PayPal extends Driver
|
|
|
->setTransactions(array($transaction))
|
|
|
->setRedirectUrls($redirectUrls);
|
|
|
|
|
|
- $payment->create($apiContext);
|
|
|
+ //$payment->create($apiContext);
|
|
|
|
|
|
try {
|
|
|
- $payment->create($apiContext);
|
|
|
- echo "\n\nRedirect user to approval_url: " . $payment->getApprovalLink() . "\n";
|
|
|
- // 记录日志
|
|
|
- Log::append('PayPal-unify', ['client' => $this->client, 'result' => $this->result]);
|
|
|
- redirect($payment->getApprovalLink());
|
|
|
- // 请求成功
|
|
|
- return true;
|
|
|
+ $this->result = $payment->create($apiContext);
|
|
|
} catch (\PayPal\Exception\PayPalConnectionException $ex) {
|
|
|
// This will print the detailed information on the exception.
|
|
|
//REALLY HELPFUL FOR DEBUGGING
|
|
|
echo $ex->getData();
|
|
|
$this->throwError('支付宝API交易查询失败:' . $ex->getMessage(), true, 'tradeQuery');
|
|
|
-
|
|
|
+ return false;
|
|
|
}
|
|
|
- return false;
|
|
|
+ echo "\n\nRedirect user to approval_url: " . $payment->getApprovalLink() . "\n";
|
|
|
+ // 记录日志
|
|
|
+ Log::append('PayPal-unify', ['client' => $this->client, 'result' => $this->result]);
|
|
|
+ redirect($payment->getApprovalLink());
|
|
|
+ // 请求成功
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -109,58 +142,92 @@ class PayPal extends Driver
|
|
|
*/
|
|
|
public function tradeQuery(string $outTradeNo): ?array
|
|
|
{
|
|
|
-
|
|
|
-
|
|
|
try {
|
|
|
- // 发起API调用
|
|
|
- // https://opendocs.alipay.com/apis/028pxp
|
|
|
- $result = Factory::payment()->common()->query($outTradeNo);
|
|
|
+
|
|
|
+ $result = Order::get($outTradeNo, $this->apiContext);
|
|
|
// 记录日志
|
|
|
- Log::append('Alipay-tradeQuery', ['outTradeNo' => $outTradeNo, 'result' => $result->toMap()]);
|
|
|
+ Log::append('Paypal-tradeQuery', ['outTradeNo' => $outTradeNo, 'result' => json_encode($result)]);
|
|
|
// 处理响应或异常
|
|
|
- $responseChecker = new ResponseChecker();
|
|
|
- if (!$responseChecker->success($result)) {
|
|
|
- $this->throwError($result->msg . "," . $result->subMsg);
|
|
|
- }
|
|
|
+ //$this->throwError($result->msg . "," . $result->subMsg);
|
|
|
// 返回查询成功的结果
|
|
|
- return [
|
|
|
- // 支付状态: true成功 false失败
|
|
|
- 'paySuccess' => $result->tradeStatus === 'TRADE_SUCCESS',
|
|
|
- // 第三方交易流水号
|
|
|
- 'tradeNo' => $result->tradeNo
|
|
|
- ];
|
|
|
+ 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);
|
|
|
+ return $payment::get($payment->getId(), $this->apiContext);
|
|
|
+
|
|
|
+ } 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(): bool
|
|
|
+ public function notify(Request $request, $webHookId): bool
|
|
|
{
|
|
|
+
|
|
|
// 接收表单数据
|
|
|
- $this->notifyParams = request()->filter([])->post();
|
|
|
- // 验证异步请求的参数是否合法
|
|
|
- // https://opendocs.alipay.com/open/270/105902
|
|
|
- $verifyNotify = Factory::payment()->common()->verifyNotify($this->notifyParams);
|
|
|
- // 判断交易单状态必须是支付成功
|
|
|
- $this->notifyResult = $verifyNotify && $this->notifyParams['trade_status'] === 'TRADE_SUCCESS';
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
// 记录日志
|
|
|
- Log::append('Alipay-notify', [
|
|
|
- 'params' => $this->notifyParams,
|
|
|
- 'verifyNotify' => $verifyNotify,
|
|
|
- 'response' => $this->getNotifyResponse(),
|
|
|
- 'result' => $this->notifyResult,
|
|
|
- 'message' => '支付宝异步回调验证' . ($this->notifyResult ? '成功' : '失败')
|
|
|
- ]);
|
|
|
+// Log::append('PayPal-notify', [
|
|
|
+// 'params' => $this->notifyParams,
|
|
|
+// 'verifyNotify' => $verifyNotify,
|
|
|
+// 'response' => $this->getNotifyResponse(),
|
|
|
+// 'result' => $this->notifyResult,
|
|
|
+// 'message' => '支付宝异步回调验证' . ($this->notifyResult ? '成功' : '失败')
|
|
|
+// ]);
|
|
|
return $this->notifyResult;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 支付宝退款API
|
|
|
+ * PAYPAL退款API
|
|
|
* @param string $outTradeNo 第三方交易单号
|
|
|
* @param string $refundAmount 退款金额
|
|
|
* @param array $extra 附加的数据
|
|
@@ -171,22 +238,8 @@ class PayPal extends Driver
|
|
|
{
|
|
|
try {
|
|
|
// 发起API调用
|
|
|
- // https://opendocs.alipay.com/apis/028xqg
|
|
|
$outRequestNo = (string)time();
|
|
|
- $result = Factory::payment()->common()->refund($outTradeNo, $refundAmount, $outRequestNo);
|
|
|
- // 记录日志
|
|
|
- Log::append('Alipay-refund', [
|
|
|
- 'outTradeNo' => $outTradeNo,
|
|
|
- 'refundAmount' => $refundAmount,
|
|
|
- 'result' => $result->toMap()
|
|
|
- ]);
|
|
|
- // 处理响应或异常
|
|
|
- empty($result) && $this->throwError('API无返回结果');
|
|
|
- $responseChecker = new ResponseChecker();
|
|
|
- if (!$responseChecker->success($result)) {
|
|
|
- $this->throwError($result->msg . "," . $result->subMsg);
|
|
|
- }
|
|
|
- // 请求成功
|
|
|
+
|
|
|
return true;
|
|
|
} catch (\Throwable $e) {
|
|
|
$this->throwError('支付宝API退款请求:' . $e->getMessage(), true, 'refund');
|
|
@@ -215,7 +268,8 @@ class PayPal extends Driver
|
|
|
{
|
|
|
return [
|
|
|
// 第三方交易流水号
|
|
|
- 'tradeNo' => $this->notifyParams['trade_no']
|
|
|
+ 'buyerId' => $this->notifyParams['PayerID'],
|
|
|
+ 'paymentId' => $this->notifyParams['paymentId']
|
|
|
];
|
|
|
}
|
|
|
|
|
@@ -228,27 +282,14 @@ class PayPal extends Driver
|
|
|
return $this->notifyResult ? 'success' : 'FAIL';
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 返回统一下单API的结果 (用于前端)
|
|
|
- * @return array
|
|
|
- * @throws BaseException
|
|
|
- */
|
|
|
public function getUnifyResult(): array
|
|
|
{
|
|
|
- if (empty($this->result)) {
|
|
|
- $this->throwError('当前没有unify结果', true, 'getUnifyResult');
|
|
|
+ if (empty($this->result->getApprovalLink())) {
|
|
|
+ $this->throwError('paypal当前没有unify结果', true, 'getUnifyResult');
|
|
|
+ return [];
|
|
|
}
|
|
|
// 整理返回的数据
|
|
|
- $result = ['out_trade_no' => $this->result['out_trade_no']];
|
|
|
- // H5端使用的支付数据
|
|
|
- if ($this->client === ClientEnum::H5) {
|
|
|
- $result['formHtml'] = $this->deleteHtmlTags(['script'], $this->result['body']);
|
|
|
- }
|
|
|
- // APP端使用的支付数据
|
|
|
- if ($this->client === ClientEnum::APP) {
|
|
|
- $result['orderInfo'] = $this->result['body'];
|
|
|
- }
|
|
|
- return $result;
|
|
|
+ return ['approval_link' => $this->result->getApprovalLink(), 'id' => $this->result->getId()];
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -261,7 +302,6 @@ class PayPal extends Driver
|
|
|
{
|
|
|
|
|
|
|
|
|
-
|
|
|
return $this;
|
|
|
}
|
|
|
|