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":"sb-wi93p1551674#personal.example.com","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
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:
V2 capture get: https://developer.paypal.com/docs/api/payments/v2/#captures_get
Older V1 sale get: https://developer.paypal.com/docs/api/payments/v1/#sale_get
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.
Is there an API for retrieving the current balance of my PayPal account? Is there a way I can securely query my PayPal account and retrieve the total balance in my account?
I want to do this to create a "we have x out of y for our monthly quota" type things.
The Transaction Search API has a method to check balances.
To use it, first enable the Transaction Search permission in your REST App settings.
If you requested an API access token in the 9 hours prior to enabling that permission, either terminate that access token or wait up to 9 hours for it to expire from cache.
I was planning to use this endpoint to check and see if a sale has completed, using the sale's payment id: /v1/payments/payment/{payment_id} (https://developer.paypal.com/docs/api/payments/#payment_get)
But I just noticed this sentence in the description of this endpoint:
Shows details for a payment, by ID, that has yet to complete.
I've spent days reading the docs and I don't know what the bolded part means. I would like to be able to use the payment id and this endpoint to check the status of a sale days, months, even years after the user has completed their purchase, but the bolded part of this sentence seems to tell me I won't be able to do that.
Does this mean that after the sale has status = completed I can no longer use this endpoint to get the payment/sale details? Does the payment get deleted from paypal's system, and only the sale remains?
Thanks!
I am not trying to justify why the wording is the way it is which needs to be updated but below is really what's happening.
When you do POST /v1/payments/ it creates a payment resource typically identified by an id PAY-xxxx
this typically goes in 3 statuses
created. The transaction was successfully created.
approved. The payer approved the transaction.
failed. The transaction request failed.
when you do a POST /v1/payments/PAY-XXXX/execute (presuming you did an intent=sale) this results in the creation of a new resource called "sale" which can be seen in the related_resources when you examine the response.
Does this mean that after the sale has status = completed I can no longer use this endpoint to get the payment/sale details?
you can if you notice even though the sale status=completed the payment status is still "approved" (sale is a related_resource within payment). so you can always lookup a PAY-xxxx and then see what are the related resources and their corresponding status at this point the status of PAY-xxxx is less consequential.
Does the payment get deleted from paypal's system, and only the sale remains?
you can still query by the PAY-xxxx so it would suggest that it doesn't get deleted in anyways.
If a customer doesn't pay right away, can I store the token in a database and present it to them again at a later date (for example, 2 days later), so they can checkout again if they didn't complete it or didn't do it?
How to Create One-Time Payments Using Express Checkout
If the SetExpressCheckout request is successful, PayPal returns a token string in the Token response field. The default lifetime of this token is 3 hours
Hth...
I'm using the express checkout API through the ActiveMerchant gem in a Ruby on Rails app. The whole authorize and capture flow works just fine when is done within 3 hours. But after that my token expires and I lose the transaction. Even if the authorize and capture documentation says that the authorization is valid for 3 days (at least according to https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/howto_admin_authcapture).
So, how can I capture the transaction after the token has expired?
You shouldn't have to be passing your token. You should be following the flow below.
Make your SetExpressCheckout API call and set payment action to authorization (A)
Get token back
Redirect buyer over to PayPal with to token to login and agree to payment
Buyer gets redirected back to your site with token and payer id returned
You can then execute your GetExpressCheckoutDetails API call using the token.(this step is optional)
Then you perform the DoExpressCheckoutPayment API where you pass over the token and set the payment action to authorization (A)
Then then completes the Express Checkout authorization.
Now you would go back a day or two later and perform your DoCapture API where you send over the transaction id that was returned from your DoExpressCheckoutPayment API earlier. You don't send over the token again here. Once you complete the DoCapture, the funds should then show in your account.