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 ?
2
Answers
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: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 whybilling_agreement_id
is not namedsubscrition_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 :)
This is my approach to create and verified Paypal subscription payment.
Firstly follow the Integrate Subscriptions steps from Paypal Developer site.
Client Side
html
You can get data from PayPal using the following snippet:
Javascript
When the subscription is confirmed an onApprove event is fired. Inside the function you can call another function to finalize the subscription process. The function have two object: data and action.
In data object you have a subscriptionID that refers to the unique id of the subscription. You must save this id with the subscription buyer linked to it ( eg: save to database by calling a php file on server using ajax ) .
Server Side Webhooks
In the server side you can get data from PayPal. You have to setup a webhooks call in the developer dashboard for the following action (you can select more or all event if you need ).
BILLING.SUBSCRIPTION.CREATED
,BILLING.SUBSCRIPTION.ACTIVATED
and for the recurring payment madePAYMENT.SALE.COMPLETED
.Keep in mind that: the webhooks simulator doesn’t populate the
billing_agreement_id
, the key that carry the subscriptioID, referred as id in the other Webhooks calls. I suggest to create in the sandbox a subscription with a daily FREQUENCY with one (1) day interval. With this subscription thePAYMENT.SALE.COMPLETED
will be fired immediately. The key to find inPAYMENT.SALE.COMPLETED
call isbilling_agreement_id
.Verify Paypal webhook notification
You also have to verify the authenticity of the notification:
The transmission id, the transmission date, the webhook id and a CRC over the HTTP body. The first two can be found in the header of the request, the webhook id in the developer backend (of course, that id will never change), the CRC is calculated like shown below.
The certificate’s location is in the header, too, so we load it and extract the private key.
Last thing to watch out for: The name of the algorithm provided by Paypal (again in a header field) is not exactly the same as understood by PHP. Paypal calls it "sha256WithRSA" but openssl_verify will expect "sha256WithRSAEncryption". You can read more about verification precess here