Search code examples
paypaltransactionscapture

How to get Order ID of a subscription payment from a Transaction from paypal API


I have a problem connecting subscription payments in paypal with their respective orders. In a nutshell, the initial payment for the subscription is made on an order. Once the user pays, the order is approved and I get this information from the paypal API:

{"id":"2M9235603X788581X","intent":"CAPTURE","status":"APPROVED","payment_source":{"paypal":{"email_address":"email_address","account_id":"8XHXZUT5Y3CVS","name":{"given_name":"John","surname":"Doe"},"address":{"country_code":"US"}}},"purchase_units":[],"payer":{"name":{"given_name":"John","surname":"Doe"},"email_address":"email_address","payer_id":"8XHXZUT5Y3CVS","address":{"country_code":"US"}},"create_time":"2023-02-13T17:24:46Z","links":[{"href":"https:\/\/api.sandbox.paypal.com\/v2\/checkout\/orders\/2M9235603X788581X","rel":"self","method":"GET"},{"href":"https:\/\/api.sandbox.paypal.com\/v2\/checkout\/orders\/2M9235603X788581X","rel":"update","method":"PATCH"},{"href":"https:\/\/api.sandbox.paypal.com\/v2\/checkout\/orders\/2M9235603X788581X\/capture","rel":"capture","method":"POST"}]}

Right of the bat the data is incomplete, missing crucial information such as fees and transaction details. So next best thing is to get the data on the webhook.

When the payment goes through an event is triggered "PAYMENT.SALE.COMPLETED", which sends this info to my server:

{"id":"WH-6SE66006R98946535-7F814879YL577135N","event_version":"1.0","create_time":"2023-02-13T17:25:09.906Z","resource_type":"sale","event_type":"PAYMENT.SALE.COMPLETED","summary":"Payment completed for EUR 39.37 EUR","resource":{"billing_agreement_id":"I-0HN4N0KTWLMP","amount":{"total":"39.37","currency":"EUR","details":{"subtotal":"39.37"}},"payment_mode":"INSTANT_TRANSFER","update_time":"2023-02-13T17:25:03Z","create_time":"2023-02-13T17:25:03Z","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","transaction_fee":{"currency":"EUR","value":"1.76"},"protection_eligibility":"ELIGIBLE","links":[{"method":"GET","rel":"self","href":"https://api.sandbox.paypal.com/v1/payments/sale/036421861N8145017"},{"method":"POST","rel":"refund","href":"https://api.sandbox.paypal.com/v1/payments/sale/036421861N8145017/refund"}],"id":"036421861N8145017","state":"completed","invoice_number":""},"links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-6SE66006R98946535-7F814879YL577135N","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-6SE66006R98946535-7F814879YL577135N/resend","rel":"resend","method":"POST"}]}

From the billing agreement ID (which is the subscription ID) I can get the transactions, based on a time period:

{"transactions":[{"status":"COMPLETED","id":"036421861N8145017","amount_with_breakdown":{"gross_amount":{"currency_code":"EUR","value":"39.37"},"fee_amount":{"currency_code":"EUR","value":"1.76"},"net_amount":{"currency_code":"EUR","value":"37.61"}},"payer_name":{"given_name":"John","surname":"Doe"},"payer_email":"[email protected]","time":"2023-02-13T17:25:03.000Z"}],"links":[{"href":"https://api.sandbox.paypal.com/v1/billing/subscriptions/I-0HN4N0KTWLMP/transactions?start_time=2023-02-01T07%3A50%3A20.940Z&end_time=2023-02-28T07%3A50%3A20.940Z","rel":"SELF","method":"GET"}]}

Now why it requires start and end date even though the filter is the agreement ID, I couldnt tell you, however here I can see the fees and since the event "PAYMENT.SALE.COMPLETED" has been triggered I can be sure that the payment has went through.

All thats left is for me to connect the transaction with the order. But how? There is no direct connection between the order and the transaction, no ID specified and nothing shown in the approved order. How can I connect the transaction ID with the Order ID so I can have a proper confirmation and extract the fees?

I`ve tried direct capture, pulling the data from the API and setting up webhook for all payment and subscription events, yet nothing I've seen provides the required information.

SOLUTION:

So for any future developer that stumbels on this problem here is my advice and solution. Make sure that you conform to the Paypal API and create reference records with their Subscription ID and transaction ID.

Now the tricky part is getting the transaction right away, so the user doesnt have to wait to long for a confirmation. The way I`ve done this is after the subscription is created and the order approved, I send the relevant data to the server and using the Subscription ID I keep sending requests to get all transactions for it using this endpoint: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_transactions

I do this request with a while(){} cycle, for 5 max attempts with 2 seconds of sleep period between requests, since the transaction is not shown for the subscription right away.

After I get it I create the reference record and if the subscription transaction has the proper status, I save it as finished.

On the webhook, when receiving the "PAYMENT.SALE.COMPLETED" event, you need to check if the transaction reference is already created and if not, create it. This will server you for any future payments (since its a subscription) Hope this helps


Solution

  • In your example, the subscription ID is I-0HN4N0KTWLMP and the transaction ID of the first payment (sale/capture) is 036421861N8145017. (I don't know where the order ID 2M9235603X788581X is coming from; PayPal subscriptions do not use Order IDs, those are for one-time payments)

    With the subscription ID you can get its details with a simple GET call: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_get , no date ranges are needed.

    For details on the actual payment, such as fees, either of the payments API versions may work with that ID:


    Listening for the webhook event PAYMENT.SALE.COMPLETED is the correct general solution for receiving a server-side notification of both the initial and future payments.

    If you need your own "user" or similar ID for reconciliation, set custom_id when creating the subscription.