PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Tuesday, November 22, 2022

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

 November 22, 2022     javascript, node.js, paypal, paypal-rest-sdk, webhooks     No comments   

Issue

I am trying to implement a PayPal subscription flow where user click on a PayPal subscription button that I have created via the dashboard.

In the back-end, I listen to the PAYMENT.SALE.COMPLETED webhook that is triggered when a subscription billing is successful. Unfortunately the webhook doesn't send me much infos so that I can retrieve the user and item in my DB linked to the just billed subscription.
This would allow me to securely show private content to that user.

Here is the webhook content sent by payPal (sorry for the length):

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'
            }
        ],
    }

I have tried to GET the /v1/payments/sale/:id but it didn't bring me much informations.

I have also checked other stack overflow threads on the subject but it wasn't of any help. I also don't want to use success callbacks provided in the front-end SDK because they are not as secure as a webhook (connection can close before triggering the callback see this gitlab issue)

How can I be aware that a user was billed for his subscription ?


Solution

We finally found a workaround to make our back-end retrieve the buyer and the item.

Front-end

On the subscription button code, we noticed after a lot of trial/errors that the createSubscription method accept promises and that we could use it to send the subscriptionId the the back-end before the payment continues:

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');

Back-end (webhook handler)

The back-end wait for the confirmation webhook where webhookResponse.resource.billing_agreement_id is the subscription id and allow to validate the previously created subscription. I don't exactly understand why billing_agreement_id is not named subscrition_id...

Let me know if it's not clear enougth. I let that as an answer until there is a better way to do it :)



Answered By - TOPKAT
Answer Checked By - Timothy Miller (PHPFixing Admin)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing