据我所知,IPN/Webhook 是后端通知,旨在帮助商家端系统在前端返回 URL 无法使用时可靠地接收交易更新。
然而,这根本不是我在开发过程中所经历的。除非处理了前端返回,否则 IPN 或 Webhook 都不会触发。这违背了“后端通知”的全部目的。
我很想被证明是错误的,但我已经使用 Order/Auth/Sale 测试了 Omnipay/PaypalSDK 的排列,并在 Sandbox/Live 环境中等待 Webhook/IPN,但没有一个有效。
这是我的设置:
- Laravel 上的 PHP,带有 Omnipay 和 Paypal SDK
- 正确转发端口的 Docker,因此可以在 LAN 外访问 HTTP 和 HTTPS
- 在 Paypal 的 REST 应用中启用 Webhook;在业务资料中启用 IPN
- 在沙盒模式下测试交易(销售、订单、捕获),一切正常
- 测试了 Paypal 的IPN Simulator和Mock Webhook,两者都有效
- 在沙盒和实时模式下
测试订单和销售
- 进行 API 调用以启动 Paypal 交易,收到重定向 URL
- 打开网址,登录,确认付款,使用 Paypal 的 URL 查询重定向回
return_url
,包括paymentId
和PayerID
- 此时
return_url
页面只显示返回URL查询,不处理Paypal的返回回调。 - s中没有收到IPN和Webhook
callback_url
- IPN历史页面和Webhook 事件页面不显示新事件
- 此时
- 修改代码处理Paypal的返回回调(即调用
"/v1/payments/orders/{$this->getId()}/capture"
),刷新return_url
页面。- 此时
return_url
页面显示抓包状态,交易状态,成功 - 在s中收到了IPN和Webhook
callback_url
- IPN历史页面和Webhook 事件页面确实显示了新事件
- 此时
那么……Paypal 的 IPN 和 Webhook 都不是后端通知的意思,前端返回处理是必须的吗?但是我看到有人声称“为了避免不可靠的return_url不被用户端触发,我们应该使用IPN”等。我错过了什么,或者做错了什么?
更新 - 包括代码
我的“支付”按钮向这个 URL 发送一个 POST:(
https://*PUB_IP_ADDRESS*/v2/payment/paypal/sale
或sale
他们order
的相应操作),其中包含这个 JSON 内容:{ "order_id":"test-23-order", "amount":"0.01", "currency":"CAD", "description":"1753 total .01" }
在控制器/存储库中,我使用 Omnipay 和 PaypalSDK 进行了测试。
一个。使用 Omnipay,这是完成的:
// PaymentController.php ... $result = $this->gateway ->setOrderId($request->get('order_id')) ->setAmount($request->get('amount')) ->setCurrency($request->get('currency')) ->setDescription($request->get('description')) ->initPayment(); ...
$this->gateway
Omnipay 的 Paypal 网关在哪里。然后:// OmnipayRepository.php ... public function initPayment(){ if (is_null($this->payment_intent)){ throw ExceptionMapping(40000, "You need to set payment intent first with setIntent()"); } $payment_params = $this->getPaymentParameters(); switch ($this->payment_intent){ case self::INTENT_PREAUTH: $payment_action = $this->gateway->authorize($payment_params); break; case self::INTENT_SALE: $payment_action = $this->gateway->purchase($payment_params); break; default: throw ExceptionMapping(40000, "{$this->payment_intent} is an invalid payment intent for this action"); } $response = $payment_action->send(); return $this->handleTransactionResponse($response); } ...
where
handleTransactionResponse($response)
获取返回的 JSON 并保存到模型。湾。使用贝宝 SDK:
// PaymentController.php ... $paypal = new PaypalRepository(); $payment = $paypal->initPayment( $request->get('amount'), $request->get('currency'), $request->get('description'), URL::to("/").config("paypal.options.returnUrl"), URL::to("/").config("paypal.options.cancelUrl"), 'order', $request->get('order_id') ); if ($payment['error']) { echo $payment['error']."ERR_PP"; } else { $approvalUrl = $payment['payment']->getApprovalLink(); echo $approvalUrl; exit; } ...
接着
// PaypalRepository.php::initPayment() ... $payment = null; $payer = new Payer(); $payer->setPaymentMethod("paypal"); $item1 = new Item(); $item1->setName($descr) ->setDescription($descr) ->setCurrency($currency) ->setQuantity(1) ->setSku($sku)// Similar to `item_number` in Classic API ->setPrice($total); $itemList = new ItemList(); $itemList->setItems(array($item1)); $details = new Details(); $details->setShipping(0); $details->setTax(0); $details->setSubtotal($total); $amount = new Amount(); $amount->setCurrency($currency); $amount->setTotal($total); $amount->setDetails($details); $transaction = new Transaction(); $transaction->setAmount($amount); $transaction->setItemList($itemList); $transaction->setDescription($descr); $transaction->setInvoiceNumber(uniqid()); $redirectUrls = new RedirectUrls(); $redirectUrls->setReturnUrl($returnUrl); $redirectUrls->setCancelUrl($cancelUrl); $payment = new Payment(); $payment->setIntent($intent); $payment->setPayer($payer); $payment->setRedirectUrls($redirectUrls); $payment->setTransactions(array($transaction)); $payment->create($this->apiContext);
然后 Paypal 将使用以下 JSON 响应:
{ "id":"PAY-4L424927Y2109544NLF2TY2A", "intent":"sale", "state":"created", "payer":{ "payment_method":"paypal" }, "transactions":[ { "amount":{ "total":"0.01", "currency":"CAD" }, "description":"1716 total .01", "related_resources":[ ] } ], "create_time":"2017-07-24T00:16:40Z", "links":[ { "href":"https:\/\/api.sandbox.paypal.com\/v1\/payments\/payment\/PAY-4L424927Y2109544NLF2TY2A", "rel":"self", "method":"GET" }, { "href":"https:\/\/www.sandbox.paypal.com\/cgi-bin\/webscr?cmd=_express-checkout&token=EC-0GN55647HU250615H", "rel":"approval_url", "method":"REDIRECT" }, { "href":"https:\/\/api.sandbox.paypal.com\/v1\/payments\/payment\/PAY-4L424927Y2109544NLF2TY2A\/execute", "rel":"execute", "method":"POST" } ] }
然后我转到
approval_url
, 并使用 Paypal 帐户登录以完成销售或订单交易的批准。现在我们回到上面最初的要点 (3) 和 (4),Paypal 不会触发任何 IPN 或 Webhook 事件,除非我通过处理数据
return_url
来完成交易,在代码中是这样的:一个。全支付:
// PaymentRepository.php, WIP code for callback_return ... public function callback_return(Request $request) { $paymentId = $_GET['paymentId']; $payerId = $_GET['PayerID']; // Once the transaction has been approved, we need to complete it. $transaction = $this->gateway->completePurchase(array( 'payer_id' => $payerId, 'transactionReference' => $paymentId, )); $response = $transaction->send(); if ($response->isSuccessful()) { // The customer has successfully paid. echo "PAID"; } else { echo "ERROR"; // There was an error returned by completePurchase(). You should // check the error code and message from PayPal, which may be something // like "card declined", etc. } } ...
湾。贝宝 SDK:
// paypal_return.php, code snippet on completing transaction ... $paypal = new paypal(); try { $payment = $paypal->executePaypalIntent(get2('paymentId'), get2('PayerID')); } catch (\Exception $e) { $payment = false; } ...