簡體   English   中英

如何通過 webhook 在 PayPal API 中確認每月訂閱的計費?

[英]How to confirm billing of a monthly subscription in PayPal API via webhook?

我正在嘗試實現PayPal 訂閱流程,其中用戶單擊我通過儀表板創建的PayPal 訂閱按鈕

在后端,我監聽訂閱計費成功時觸發的PAYMENT.SALE.COMPLETED webhook。 不幸的是,webhook 沒有向我發送太多信息,因此我可以在我的數據庫中檢索與剛剛計費的訂閱相關聯的用戶和項目。
這將允許我安全地向該用戶顯示私人內容

以下是 payPal 發送的webhook 內容(見長見諒):

const response = {
        id: 'WH-4W487015EX264720U-32N35125TV248784B',
        event_version: '1.0',
        create_time: '2021-04-26T08:24:41.436Z',
        resource_type: 'sale',
        event_type: 'PAYMENT.SALE.COMPLETED',
        summary: 'Payment completed for EUR 6.9 EUR',
        resource: {
            billing_agreement_id: 'I-T2HP99MJTS1T',
            amount: {
                total: '6.90',
                currency: 'EUR',
                details: {
                    subtotal: '6.90'
                }
            },
            payment_mode: 'INSTANT_TRANSFER',
            update_time: '2021-04-26T08:23:59Z',
            create_time: '2021-04-26T08:23:59Z',
            protection_eligibility_type: 'ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE',
            transaction_fee: {
                currency: 'EUR',
                value: '0.48'
            },
            protection_eligibility: 'ELIGIBLE',
            links: [
                {
                    method: 'GET',
                    rel: 'self',
                    href: 'https://api.sandbox.paypal.com/v1/payments/sale/6R7481343K8159132'
                },
                {
                    method: 'POST',
                    rel: 'refund',
                    href: 'https://api.sandbox.paypal.com/v1/payments/sale/6R7481343K8159132/refund'
                }
            ],
            id: '6R7481343K8159132',
            state: 'completed',
            invoice_number: ''
        },
        links: [
            {
                href: 'https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4W487015EX264720U-32N35125TV248784B',
                rel: 'self',
                method: 'GET'
            },
            {
                href: 'https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4W487015EX264720U-32N35125TV248784B/resend',
                rel: 'resend',
                method: 'POST'
            }
        ],
    }

我試圖GET /v1/payments/sale/:id但它沒有給我帶來太多信息。

我還檢查了有關該主題的其他堆棧溢出線程,但沒有任何幫助。 我也不想使用前端 SDK 中提供的成功回調,因為它們不如 webhook 安全(連接可以在觸發回調之前關閉, 請參閱此 gitlab 問題

我如何知道用戶已為其訂閱付費?

我們終於找到了一種解決方法,讓我們的后端檢索買家和商品。

前端

在訂閱按鈕代碼上,經過多次嘗試/錯誤后,我們注意到createSubscription方法接受承諾,並且我們可以使用它在付款繼續之前將 subscriptionId 發送到后端:

paypal.Buttons({
    style: {...},
    createSubscription: function (data, actions) {
        return actions.subscription.create({
            /* Creates the subscription */
            plan_id: 'P-26J60279VA924454WMCBPBSA',
        }).then(subscriptionId => { // subscriptionId == I-9DH5L3A3JAEB
            return new Promise((res, rej) => {
                // here we send the subscriptionId to the back-end
                // and create a pending subscription
                const body = {subscriptionId, userId, itemId};
                apiCall('POST', '/subscription', body,() => {
                    // allow to return subscriptionId to paypal
                    resolve(subscriptionId); 
                })
            });
        });
    },
    onApprove: function (data, actions) {
       // this function was of NO USE
       // it is not safe to call your backend here
       // as connexion can close and paypal doesn't
       // wait after this function to capture payment
       // thus leading to orphaned subscriptions 
       // (paid but not linked to your backend)
    },
}).render('#paypal-button');

后端(webhook 處理程序)

后端等待確認 webhook,其中webhookResponse.resource.billing_agreement_id是訂閱 ID ,並允許驗證先前創建的訂閱。 我不完全明白為什么billing_agreement_id沒有命名為subscrition_id ...

如果不清楚,請告訴我。 我讓它作為答案,直到有更好的方法來做到這一點:)

這是我創建和驗證 Paypal 訂閱付款的方法。

首先按照 Paypal 開發人員站點中的集成訂閱步驟進行操作。

客戶端

html

<script src="https://www.paypal.com/sdk/js?client-id=<YOUR CLIENT ID>&vault=true&intent=subscription"></script>
<div id="paypal-button-container"></div>

您可以使用以下代碼段從 PayPal 獲取數據:

Javascript

           paypal.Buttons({
                createSubscription: function( data, actions ) {
                    return actions.subscription.create({
                        'plan_id': '<YOUR SUBSCRIPTION PLAN>' // Creates the subscription
                    });
                },
                onApprove: function( data, actions ) {
                    finalize( data, actions );
                }
            }).render( '#paypal-button-container' ); // Renders the PayPal button

            const finalize = async ( data, actions ) => {
                const rawResponse = await fetch( '/api/paypal-subscription.php', {
                    method: 'POST',
                    headers: {
                        'Accept': 'application/json',
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ data: data, actions: actions })
                });
                const results = await rawResponse.json();
                if ( results.error == 0 ){
                    console.log( "payment created" );
                } else {
                    console.log( 'Errore creazione agenzia' );
                };
            };

確認訂閱后,會觸發 onApprove 事件。 在 function 內部,您可以調用另一個 function 來完成訂閱過程。 function 有兩個 object:數據和動作。

在數據 object 中,您有一個訂閱 ID,它引用訂閱的唯一 ID。 您必須將此 ID 與鏈接到它的訂閱買家一起保存(例如:通過使用 ajax 在服務器上調用 php 文件保存到數據庫)。

服務器端 Webhook

在服務器端,您可以從 PayPal 獲取數據。 您必須在開發人員儀表板中設置 webhook 調用以執行以下操作(如果需要,您可以 select 更多或所有事件)。 BILLING.SUBSCRIPTION.CREATEDBILLING.SUBSCRIPTION.ACTIVATED和定期付款PAYMENT.SALE.COMPLETED

<?php
$data = json_decode( file_get_contents( "php://input" ), true );
$data = $data['resource'];

if ( !array_key_exists( 'billing_agreement_id', $data ) ) {
    // Not a payment for a billing agreement
    // handle single payments or:
    die();
};
?>

請記住:webhooks 模擬器不會填充billing_agreement_id ,即攜帶 subscriptioID 的鍵,在其他 Webhooks 調用中稱為 id。 我建議在沙盒中創建一個訂閱,該訂閱具有每日 FREQUENCY 和一 (1) 天的間隔。 通過此訂閱, PAYMENT.SALE.COMPLETED將立即被觸發。 PAYMENT.SALE.COMPLETED調用中找到的關鍵是billing_agreement_id

驗證 Paypal webhook 通知

您還必須驗證通知的真實性:

<php
// get request headers
$headers = apache_request_headers();

// get http payload
$body = file_get_contents( 'php://input' );

// compose signature string: The third part is the ID of the webhook ITSELF(!),
// NOT the ID of the webhook event sent. You find the ID of the webhook
// in Paypal's developer backend where you have created the webhook
$data =
    $headers['Paypal-Transmission-Id'] . '|' .
    $headers['Paypal-Transmission-Time'] . '|' .
    '<WEBHOOK ID FROM THE DEVELOPER DASHBOARD>' . '|' . crc32( $body );


// load certificate and extract public key
$pubKey = openssl_pkey_get_public( file_get_contents( $headers['Paypal-Cert-Url'] ) );
$key = openssl_pkey_get_details( $pubKey )['key'];

// verify data against provided signature 
$result = openssl_verify(
    $data,
    base64_decode( $headers['Paypal-Transmission-Sig'] ),
    $key, 'sha256WithRSAEncryption'
);


if ( $result == 1 ) {
    // webhook notification is verified
} elseif ( $result == 0 ) {
    // webhook notification is NOT verified
} else {
    // there was an error verifying this
};
?>

傳輸 id、傳輸日期、webhook id 和 HTTP 主體上的 CRC。 前兩個可以在請求的 header 中找到,開發者后端的 webhook id(當然,那個 id 永遠不會改變),CRC 計算如下所示。

證書的位置也在 header 中,所以我們加載它並提取私鑰。

最后需要注意的是:Paypal(同樣在 header 字段中)提供的算法名稱與 PHP 所理解的不完全相同。 Paypal 將其稱為“sha256WithRSA”,但 openssl_verify 將期望“sha256WithRSAEncryption”。 您可以在此處閱讀有關驗證過程的更多信息

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM