PayPal可以在不处理来自return_url的数据的情况下触发IPN / Webhook吗?

时间:2017-07-24 20:32:39

标签: php callback paypal-ipn paypal-rest-sdk paypal-webhooks

据我所知,IPN / Webhook是后端通知,并且假设当前端return-url无法使用时,帮助商家方系统可靠地接收交易更新。

但是,这完全不是我在开发过程中遇到的情况。除非处理前端返回,否则IPN或Webhook都不会触发。这违背了“后端通知”的全部目的。

我很想被证明是错的,但我已经使用Order / Auth / Sale测试了Omnipay / PaypalSDK的排列,并在Sandbox / Live环境中等待Webhook / IPN,但都没有。

以下是我的设置:

  • 使用Omnipay和Paypal SDK的Laravel上的PHP
  • 正确转发端口的Docker,因此可以在LAN外部访问HTTP和HTTPS
  • 在Paypal的REST应用程序中启用了Webhook;在业务配置文件中启用IPN
  • 沙盒模式下经过测试的交易(销售,订单,捕获),全部工作
  • 经过测试的Paypal的 IPN模拟器模拟Webhook ,均有效
  • 经过测试订单&销售 Sandbox&实时模式
    1. 发起API调用以启动Paypal交易,收到重定向网址
    2. 已打开网址,已登录,已确认付款,已重定向回return_url,其中包含来自Paypal的网址查询,其中包括paymentIdPayerID
      • 目前,return_url页面仅显示网址查询,但不会处理Paypal的返回回调
      • 没有callback_url
      • 中接收 IPN Webhook
      • IPN Hisotry 页面和 Webhook活动页面显示新活动
    3. 修改代码以处理Paypal的返回回调(即名为"/v1/payments/orders/{$this->getId()}/capture"),并刷新return_url页面。
      • 此时,return_url页面显示捕获状态和事务状态,成功
      • callback_url
      • 中接收 IPN Webhook
      • IPN Hisotry 页面和 Webhook活动页面执行显示新活动

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

更新 - 包含代码

  1. 我的“付费”按钮会向此网址发送一个POST:https://*PUB_IP_ADDRESS*/v2/payment/paypal/salesaleorder,以了解相应的操作)以及此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->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);
    }
    ...
    

    其中handleTransactionResponse($response)获取返回的JSON并保存到模型。

    湾使用Paypal 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帐户登录以完成对 sale 订单交易的批准。

  5. 现在我们回到上面的原始要点(3)和(4),其中Paypal不会触发任何IPN或Webhook事件,除非我通过return_url处理数据以完成交易,在代码中,这是:

    一个。 Omnipay:

    // 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.
        }
    }
    ...
    

    湾Paypal 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;
        }
    ...
    

1 个答案:

答案 0 :(得分:0)

我注意到你没有在paypal按钮中引用paypal 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的错误lol

IPN

<?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();

}
?>