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.
TL;DR- How do I know if I really did get paid, via the PayPal NVP API?
Longer version
I'm writing code to automagically process and classify transactions from our PP business account. I have code that calls NVP methods TransactionSearch and GetTransactionDetails and fills a few database tables with the results that are returned. This happens every day for the previous 24 hours' worth of transactions. So far, so good.
I began from the assumption that the same information available via manual CSV export via the PayPal web interface would be available via the API, but I'm now discovering that either this is not the case, or there is another API call I should be making to get the rest of the information I need.
In particular, GetTransactionDetails doesn't seem to return the equivalent of CSV field "Reference Txn ID" and, in a few cases, I can't see how to connect related transactions.
So, my question is how can I obtain the reference transaction ID, or how do I otherwise connect these associated transactions?
Example 1
This is (probably) the most common sort of situation where the reference transaction ID is needed.
Depending on the buyer's funding source, you may either see a purchase complete instantly or, if the transaction must be cleared, complete across three separate transactions:
The original payment, detail of which gives buyer's name, email, address, invoice number, item ID, gross, fee and net amounts etc.
followed immediately by a debit of the net amount
followed, in a few days, by a credit of the net amount.
It is crucial to match the three transactions because you don't know whether the payment will succeed or be declined (and therefore whether you've actually got the money) unless you do.
In the data I have available to me at the moment, there is only one such example of this situation, and calls to GetTransactionDetails for the settlement transactions fail completely ("The transaction could not be loaded") — possibly because, in that one instance, I know that the payment was made with a credit card by somebody who doesn't have a PayPal account.
This might be an anomaly, or it might be the common case. I have seen this triplet of transactions quite frequently in other PayPal accounts, but not this one, so I can't be sure.
Example 2
A payment in a foreign currency will typically generate three transactions, rather like in Example 1:
The original payment (eg in €), the detail of which gives the sender's name and email address etc;
this is then followed by an immediate debit of the (probably net) amount in €
and then an immediate credit in USD.
From the point of view of the NVP API, GetTransactionDetails usually returns values labelled SETTLEAMT and SETTLEAMTCURRENCYCODE for the original payment. No further processing is required.
However, a recent such transaction (for reasons that even PayPal could not adequately explain) did not auto-convert, and the GetTransactionDetails call lacks the settlement values even after the conversion was performed manually.
The transactions are all there — but I cannot see any programmatic way to associate them because an attempt to call GetTransactionDetails on currency conversion transactions returns the error "You can not get the details for this type of transaction".
—
Any advice or experience relating to automatic processing of PayPal transactions? Is there any way to duplicate, programmatically, the same data included in the CSV export?
If necessary, I'd be happy to process the CSV export if there were some way to generate it programmatically, but I don't even know of a way to do that (short of a Selenium-driven solution full of pitfalls).
GetTransactionDetails will include a PARENTTRANSACTIONID if there is a related transaction. For example, a refund transaction would have a parent transaction ID.
Good morning, I'm working with DoExpressCheckout's Payment Status response for an InstantPaymentOnly Sale.
https://developer.paypal.com/docs/classic/api/merchant/DoExpressCheckoutPayment_API_Operation_NVP/
By PAYMENTINFO_n_PAYMENTSTATUS all possible responses are shown, Completed-Funds-Held is the latest addition.
I'm providing a service that expires and must be used by a certain time. If the payments final status is not known by the time the service must start, its looks like we're going to have to collect payment (again) in person and refund the online payment. Needless to say, that doesn't sound good.
Q. Could Completed-Funds-Held result in funds never being deposited to the main balance?
The documentation for that particular status lists two possible response codes that go along with Completed-Funds-Held: newsellerpaymenthold (not applicable in my case) and paymenthold which vaguely states "A hold is placed on the merchant's transaction for a reason not listed."
Basically is Completed-Funds-Held a "successful" transaction where the purchaser can receive his product without risk of the seller never receiving payment?
EDIT: Is Completed-Funds-Held a final status if the payment is successful? Or will a Completed IPN request come if and when the hold is settled?
Those payments will eventually become available. What you should do is setup an IPN solution so that you can automatically update your system when a payment hold is released. The same would be true for things like e-checks where the payment status is simply pending and then clears a few days later.
I'm having trouble getting to the bottom of a bug that seems to stem from our system not having received an IPN callback for a held transaction.
Here is the transaction history as shown on the Paypal website:
And here are the callbacks that registered on our system:
On Jul 25 we received only one callback, for the payment, but none for the temporary hold that's showing on the Paypal site. This is leading to incorrect balances, as it should balance out to zero in this example, but in our database it ends up giving the customer $200 credit.
From the site and docs I don't find any mention of callbacks for held transactions, and it's not on the list of callbacks I can test as far as I can see:
Canceled_Reversal
Completed
Denied
Expired
Failed
In-Progress
Partially_Refunded
Pending
Processed
Refunded
Reversed
Voided
I'm unsure whether our system just missed a callback, or whether there is none to be expected and we should be doing something else. What is the expected sequence of events in the case where a transaction is put on "Temporary Hold"? Any help would be appreciated!
You must have missed a Reversed callback which took the funds out.
If the case was decided in your favour you will then get a Canceled_Reversal transaction which gives you the funds back again.
At the end of the IPN docs it says the following:
Important: After you have authenticated an IPN message (that is, received a VERIFIED response from PayPal), you must perform these important checks before you can assume that the IPN is both legitimate and has not already been processed:
1.Check that the payment_status is Completed.
2.If the payment_status is Completed, check the txn_id against the previous PayPal transaction that you processed to ensure the IPN message is not a duplicate.
3.Check that the receiver_email is an email address registered in your PayPal account.
4.Check that the price (carried in mc_gross) and the currency (carried in mc_currency) are correct for the item (carried in item_name or item_number).
What are you supposed to do if one of these checks fails? Are we supposed to call a PayPal API somehow to 'reject' the transaction'?
Most shops would mark an order / transaction that fails these checks as "Possible Fraud" or something along these lines.
The order status should raise awareness with the employee working the orders - a closer look should show which check has failed and further actions can be initiated.
This may range from refunding the transactions to contacting PayPal for possible fraud attempts. In any case, the order should be checked and not just treated as any other "Completed" order.
Sample scenario:
You're using unencrypted Website Payments Standard, HTML Forms to receive payments.
A fraudulent buyer finds out and starts modifying the price of an item through editing the submitted form
You receive the order as PAID and don't check the value of the order within your shop against the transaction
An employee doesn't spot this and an item worth 200$ is sent out for a payment of 0.01$