26
votes

I always assumed that txn_id sent with IPN message is unique. PayPal guidelines seem to support this idea - https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro

Avoid duplicate IPN messages. Check that you have not already processed the transaction identified by the transaction ID returned in the IPN message. You may need to store transaction IDs returned by IPN messages in a file or database so that you can check for duplicates. If the transaction ID sent by PayPal is a duplicate, you should not process it again.

However I found that PayPal's eCheck payment IPN is sent twice with the same transaction ID. Once during initial payment with payment_status as "Pending" and again after couple days when eCheck is actually processes with payment_status as "Completed".

I want to store both transactions, but still would like to avoid storing duplicates. There is another field in IPN called ipn_track_id and it's different for both transactions, but I can't find documentation for it, except this vague description:

Internal; only for use by MTS and DTS

Anyone else is using ipn_track_id to uniquely identify IPN messages?

5
logically it is one transaction hence the identical txn_id. Adding the payment_status to the (primary) key would solve that, wouldn't it? The doc should probably read: a txn_id for the same status should not be processed again.rene
That makes sense, thank you for the suggestion.arnaslu
Worth noting that you should only process the transaction where payment_status="Completed" according to PayPals IPN guidelines.steve cook
@steve I'm relying on payment_status="Completed" wherein my BIN btn is removed and the transaction enters my DB. Hope this is a good method.Ben Racicot
I have the feeling the uniqueness of the txn_id should be checked only when payment_status='Completed'. Because no documentation mentioned the uniqueness of the combination of payment_status and txn_id, your program may break if two 'pending's with the same txn_id hit your db. As I said, this is my instinct, Paypal should make it more clear in the documentations.Qian Chen

5 Answers

36
votes

ipn_track_id shouldn't be used; mainly because this is for internal use only as stated, and because it's unique for every IPN message.
The txn_id is unique for each transaction, not each IPN message.

What this means is; one transaction can have multiple IPN messages. eCheck, for example, where it will go in a 'Pending' state by default, and 'Complete' once the eCheck has cleared.
But you may also see reversals, canceled reversals, cases opened and refunds against the same txn_id.

Pseudo code:

If not empty txn_id and txn_type = web_accept and payment_status = Completed  
    // New payment received; completed. May have been a transaction which was pending earlier.
    Update database set payment_status = Completed and txn_id = $_POST['txn_id']  

If not empty txn_id and txn_type = web_accept and payment_status = Pending  
    // New payment received; completed  
    Update database set payment_status = Pending and payment_reason = $_POST['pending_reason'] and txn_id = $_POST['txn_id']

You can find a lot more IPN variables listed on https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_IPNandPDTVariables#id08CTB0S055Z

Basically; PayPal will generate a unique seller transaction ID. This tranaction ID may go through various stages before it's 'Completed', so you'll need to be able to handle these exceptions.

As for PayPal's note in the documentation: PayPal may resend individual IPN mesages if it encounters errors during delivery. For example, it's required for your script to return a proper HTTP/1.1 200 OK HTTP status response whenever PayPal POST's the IPN data to it.
If you don't return a HTTP/1.1 200 OK response, PayPal will reattempt sending the same data up to 16 times per indiviudal IPN message.

Note: A seller's transaction ID is different from a buyer's transction ID, since they're two different actions (one debit, one credit).

9
votes

ipn_track_id is not unique for recurring payments. At least for installment plan it's not. When customer create the installment plan and if your plan have a first payment at checkout you will receive 2 IPN messages with the same ipn_track_id (Example bellow).

First notification "recurring_payment_profile_created" the the first payment "recurring_payment"

Plan created IPN

 [txn_type] => recurring_payment_profile_created
 [recurring_payment_id] => I-57UAPHFJ3SBY
 [product_name] => Risk-Free Trial
 [time_created] => 06:24:39 Aug 15, 2013 PDT
 [ipn_track_id] => bdd94fdee935a

First Payment IPN

 [txn_type] => recurring_payment
 [mc_gross] => 10.95
 [shipping] => 0.00
 [product_type] => 1
 [time_created] => 06:24:39 Aug 15, 2013 PDT
 [ipn_track_id] => bdd94fdee935a
2
votes

Not all IPN messages contain a $_POST['txn_id'], so if you solely check for a txn_id, you may intermittently not log IPN messages where they don't contain this key.

1
votes

In my PHP API call, i use these fields and store them in my database:

custom=xxxx  

(or invoice=ZZZZZZ)

then, when your page receives IPN, it should check (from database) if the custom=xxxx or etc...

-1
votes

IPN transactions are unique, also and transactions that change the payment will generate a new txn_id. For example refunds, so its a good idea to store all txn_id related to a single purchase. As of yet I don't know what transactions other than refunds generate a new txn_id, possibly reversals do too.