2

据我所知,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 SimulatorMock Webhook,两者都有效
  • 沙盒和实时模式下 测试订单和销售
    1. 进行 API 调用以启动 Paypal 交易,收到重定向 URL
    2. 打开网址,登录,确认付款,使用 Paypal 的 URL 查询重定向回return_url,包括paymentIdPayerID
      • 此时return_url页面只显示返回URL查询,不处理Paypal的返回回调
      • s中没有收到IPNWebhookcallback_url
      • IPN历史页面和Webhook 事件页面显示新事件
    3. 修改代码处理Paypal的返回回调(即调用"/v1/payments/orders/{$this->getId()}/capture"),刷新return_url页面。
      • 此时return_url页面显示抓包状态,交易状态,成功
      • 在s收到了IPNWebhookcallback_url
      • IPN历史页面和Webhook 事件页面确实显示了新事件

那么……Paypal 的 IPN 和 Webhook 都不是后端通知的意思,前端返回处理是必须的吗?但是我看到有人声称“为了避免不可靠的return_url不被用户端触发,我们应该使用IPN”等。我错过了什么,或者做错了什么?

更新 - 包括代码

  1. 我的“支付”按钮向这个 URL 发送一个 POST:(https://*PUB_IP_ADDRESS*/v2/payment/paypal/salesale他们order的相应操作),其中包含这个 JSON 内容:

    {
        "order_id":"test-23-order",
        "amount":"0.01",
        "currency":"CAD",
        "description":"1753 total .01"
    } 
    
  2. 在控制器/存储库中,我使用 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->gatewayOmnipay 的 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);
    }
    ...
    

    wherehandleTransactionResponse($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);
    
  3. 然后 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"
          }
       ]
    }
    
  4. 然后我转到approval_url, 并使用 Paypal 帐户登录以完成销售订单交易的批准。

  5. 现在我们回到上面最初的要点 (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;
        }
    ...
    
4

1 回答 1

0

我注意到您根本没有在您的贝宝按钮中引用贝宝 ipn...

尝试添加 notify_url

我就是这样做的。

$_CONFIG['cart']['test'] = "1"; // 0 (paypal) | 1 (sandebox)
$_CONFIG['cart']['return'] = "http://thx.com";
$_CONFIG['cart']['ipn'] = "http://dev.com/ipn.php";

if (!$_CONFIG['cart']['test']){
    $_CONFIG['cart']['webscr'] = "https://www.paypal.com/cgi-bin/webscr?"; // paypal
    $_CONFIG['cart']['postback'] = "www.paypal.com";
    $_CONFIG['cart']['paypal'] = "commandes@dev.org";
}else{
    $_CONFIG['cart']['webscr'] = "https://www.sandbox.paypal.com/cgi-bin/webscr?"; // sandebox
    $_CONFIG['cart']['postback'] = "www.sandbox.paypal.com";
    $_CONFIG['cart']['paypal'] = "info-facilitator@dev.com";
}

$grand_total = "9.99";

// Bouton Paypal
$paypalURL .= $_CONFIG['cart']['webscr'].'&';
$paypalURL .=  'item_name=ITEM NAME&';
$paypalURL .=  'cmd=_xclick&';
$paypalURL .=  'business='.urlencode($_CONFIG['cart']['paypal']).'&';
$paypalURL .=  'return='.urlencode($_CONFIG['cart']['return']).'&';
$paypalURL .=  'lc=CA&amount='.sprintf("%01.2f", $grand_total).'&';
$paypalURL .=  'currency_code=CAD&';
$paypalURL .=  'button_subtype=products&';
$paypalURL .=  'cn=Ajouter%20des%20instructions%20particuli%c3%a8res%20pour%20le%20vendeur&';
$paypalURL .=  'no_shipping=2&';
$paypalURL .=  'shipping=0&';
$paypalURL .=  'bn=PP%2dBuyNowBF%3abtn_buynowCC_LG%2egif%3aNonHosted&';
$paypalURL .=  'notify_url='.urlencode($_CONFIG['cart']['ipn']);

您可以将 URL 用作按钮或重定向。只需确保验证成功交易支付的金额即可。

您可以在 IPN 代码中做的一件事是在执行邮件警报时触发邮件警报并向您自己发送 $_POST。至少您知道 IPN 何时访问该页面。只要确保将其添加到顶部,以防出现 PHP 错误停止执行。如果在收到邮件后没有处理交易,那么你就知道出了点问题,这不是 Paypal 的错,哈哈

网络网络

<?php

// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
    $value = urlencode(stripslashes($value));
    $req .= "&$key=$value";
    $mail .= "_POST[$key]: $value<br>";
}

// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Host: ".$_CONFIG['cart']['postback']."\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Connection: close\r\n\r\n";
$fp = fsockopen ('ssl://'.$_CONFIG['cart']['postback'], 443, $errno, $errstr, 30);

// Si le fsockopen a fonctionner on poursuit
if ($fp) {

    fputs ($fp, $header . $req);
    while (!feof($fp)) {

        $res = fgets ($fp, 1024);
        if (trim($res)=="VERIFIED") {


        }elseif ($res=="INVALID") {


        }else{


        }

    }

    // On ferme la connexion
    fclose ($fp);
}else{
    echo "404";
}

$subject = "IPN-DEBUG";
$messagez = "Paypal info:<br />$mail<br /><br />$prod_mail";
$de = "you@website.com";
send_mail($subject, $messagez, $de, 'you@website.com');  

function send_mail($subject, $message, $de, $to){

    require_once ('module/phpmailer/MailClass.php');//send_mail

    $send_mail = new FreakMailer();
    $send_mail->Subject = $subject;
    $send_mail->Body = $message;
    $send_mail->isHTML(true);
    $send_mail->From = $de;
    $send_mail->AddAddress($to);

    if(!$send_mail->Send()){return 'false';}else{return 'true';}
    $send_mail->ClearAddresses();
    $send_mail->ClearAttachments();

}
?>
于 2017-07-25T19:25:18.040 回答