#!/usr/bin/python

# tz_paypal.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       November 23, 2007       bar
#       November 24, 2007       bar     give the class a generic name
#                                       "live" done right
#                                       timeout to constructor
#                                       a_pdt
#                                       strip the info values
#       November 27, 2007       bar     remove hard-coded adrs and such
#       November 27, 2007       bar     insert boilerplate copyright
#       May 17, 2008            bar     email adr
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_paypal
#
#
#       Use the Paypal API.
#
#           https://www.paypal.com/en_US/ebook/PP_NVPAPI_DeveloperGuide/Appx_fieldreference.html
#
#


import  cgi
import  re
import  urllib
import  urllib2

import  url_getter



############################################################################################
#
#
#       You want to change these, or simply pass your values to the routines that need them.
#
#

#       Used for API.
#
DEFAULT_BIZ_USER        =   "this_is_your_business_email_address_at_paypal"
DEFAULT_BIZ_PASSWORD    =   "1234567890"
DEFAULT_BIZ_SIGNATURE   =   "you_get_this_from_paypal________________________________"

#       Used for PDT query.
#
DEFAULT_ID_TOKEN        =   "you_get_this_from_paypal___________________________________"

#
#
#
############################################################################################



ACK         = "ACK"
SUCCESS     = "SUCCESS"
FAIL        = "FAIL"


def info_ok(info) :
    """
        Is this information array from Paypal filled with a successful query's response?
    """

    ack = info.get(ACK.lower(), [""])[0].lower()

    if  ack != SUCCESS.lower() :
        return(False)

    return(True)



def info_str(info) :
    """
        Turn an information array from Paypal in to a nicely printed, multi-line string.
    """

    s       = ""
    if  info :
        s   = "\n"
        kys = info.keys()
        kys.sort()
        s  += "\n".join( [ str(k) + "=" + info[k][0] for k in kys ] )

    return(s)



class   an_api :
    """
        Do whatever I want with Paypal's API.
    """

    #
    #
    #   ack=Success
    #   addressid=PayPal
    #   addressstatus=None
    #   amt=19.95
    #   build=1.0006
    #   correlationid=6ec964232934
    #   countrycode=US
    #   currencycode=USD
    #   email=test_p_1111111111_per@tranzoa.com
    #   feeamt=0.88
    #   firstname=Test
    #   l_name0=test 3
    #   l_number0=five
    #   l_optionsname0=pidnum
    #   l_optionsvalue0=yet another test
    #   l_qty0=1
    #   lastname=User
    #   ordertime=2007-11-25T07:46:09Z
    #   payerid=QW7B4ZFJA4PQA
    #   payerstatus=verified
    #   paymentstatus=Completed
    #   paymenttype=instant
    #   pendingreason=None
    #   reasoncode=None
    #   receiverbusiness=test_p_1111111111_biz@tranzoa.com
    #   receiveremail=test_p_1111111111_biz@tranzoa.com
    #   receiverid=4JFY34DL2UWJW
    #   salestax=0.00
    #   taxamt=0.00
    #   timestamp=2007-11-25T08:35:11Z
    #   transactionid=4FU4686147978701J
    #   transactiontype=webaccept
    #   version=3.200000
    #
    #

    SANDBOX_SIG_SERVER  = "api-3t.sandbox.paypal.com"
    LIVE_SIG_SERVER     = "api-3t.paypal.com"


    def __init__(me, server = None, user_name = None, password = None, signature = None, timeout = None) :
        """ Constructor. """

        server          = server    or  an_api.SANDBOX_SIG_SERVER
        if  server.lower() == "sandbox" :
            server      =               an_api.SANDBOX_SIG_SERVER
        if  server.lower() == "live"    :
            server      =               an_api.LIVE_SIG_SERVER

        me.server       = server
        me.user_name    = user_name or DEFAULT_BIZ_USER
        me.password     = password  or DEFAULT_BIZ_PASSWORD
        me.signature    = signature or DEFAULT_BIZ_SIGNATURE

        me.timeout      = timeout

        me.latest_tid   = ""
        me.transactions = {}



    def get_transaction_id_information(me, tid, timeout = None) :
        """
            Do what the name says.
        """

        info    = {}

        if  timeout == None :
            timeout = me.timeout
        if  timeout == None :
            timeout = 20

        url     = "https://" + me.server + "/nvp?version=3.2"

        url    += "&user="          + urllib.quote_plus(me.user_name)
        url    += "&pwd="           + urllib.quote_plus(me.password)
        url    += "&signature="     + urllib.quote_plus(me.signature)
        url    += "&method="        + urllib.quote_plus("GetTransactionDetails")
        url    += "&transactionid=" + urllib.quote_plus(tid)

        r       = url_getter.url_open_read_with_timeout(url, timeout = timeout)
        if  r :
            info    = cgi.parse_qs(r)
            for k in info.keys() :
                v                   = [ v.strip() for v in info[k] ]
                info[k]             = v
                info[k.lower()]     = v
            pass

        me.transactions[tid]    = info
        me.latest_tid           = tid

        return(info)            # note that info is a hash with the names of the values as keys (and all names are available as lower case), and the values are an array - always 1 element, to my knowledge.


    def tid_str(me, tid = "") :
        """
            Return a printable string for the get_transaction_id_information() response for this TID if there has been one.
            If tid is None or "", get the string for the results of the latest call to get_transaction_id_information().
        """

        tid     = tid or me.latest_tid

        return(info_str(me.transactions.get(tid, "")))



    pass        #   an_api




class   a_pdt :
    """
        Do a POST-back to Paypal to get transaction details.

        Remember that Paypal ages the PDT stuff real fast, in the sandbox, at least (after 5 queries and when the buying account is logged off).
        What that means is that to test this code, you must make a "purchase" (which you'll do through the sandbox if you have any sense), get the transaction ID and then call this routine with it.
        And, you'll only get a few calls with before you need to make another "purchase".
    """

    #
    #
    #   All of this information is in the API return values in some form.
    #
    #   These come in with the URL to the landing page after the buy:
    #
    #       tx=4FU4686147978701J
    #       st=Completed
    #       amt=19.95
    #       cc=USD
    #       cm=
    #       item_number=five
    #       sig=hbR5ZsmY7TnZ9FYDVRvBQ0IScrCcHmBV3IJolHsGnvJnotWMJerE8mrt2mDq8gfTtYw5pg2f2bLsFQhp0DacVIfN65WMtj35G4mxcgwuU1EZ6WZg6aZatPeQYOSKT890vlEyu5p3d
    #
    #   From a_pdt:
    #
    #       ACK=SUCCESS
    #       ack=SUCCESS
    #       business=test_p_1111111111_biz@tranzoa.com
    #       charset=windows-1252
    #       custom=
    #       first_name=Test
    #       item_name=test 3
    #       item_number=five
    #       last_name=User
    #       mc_currency=USD
    #       mc_fee=0.88
    #       mc_gross=19.95
    #       option_name1=pidnum
    #       option_selection1=yet another test
    #       payer_email=test_p_1111111111_per@tranzoa.com
    #       payer_id=QW7B4ZFJA4PQA
    #       payer_status=verified
    #       payment_date=23:46:09 Nov 24, 2007 PST
    #       payment_fee=0.88
    #       payment_gross=19.95
    #       payment_status=Completed
    #       payment_type=instant
    #       quantity=1
    #       receiver_email=test_p_1111111111_biz@tranzoa.com
    #       receiver_id=4JFY34DL2UWJW
    #       residence_country=US
    #       shipping=0.00
    #       tax=0.00
    #       txn_id=4FU4686147978701J
    #       txn_type=web_accept
    #
    #



    SANDBOX_SIG_SERVER  = "www.sandbox.paypal.com"
    LIVE_SIG_SERVER     = "www.paypal.com"


    def __init__(me, server = None, id_token = None, timeout = None) :
        """ Constructor. """

        server          = server    or  a_pdt.SANDBOX_SIG_SERVER
        if  server.lower() == "sandbox" :
            server      =               a_pdt.SANDBOX_SIG_SERVER
        if  server.lower() == "live"    :
            server      =               a_pdt.LIVE_SIG_SERVER

        me.server       = server

        me.timeout      = timeout

        me.id_token     = id_token  or  DEFAULT_ID_TOKEN

        me.latest_tid   = ""
        me.transactions = {}



    def get_transaction_id_information(me, tid, id_token = None, timeout = None) :
        """
            Do what the name says.

            If this does not return information, see the note above!
        """

        if  timeout == None :
            timeout = me.timeout
        if  timeout == None :
            timeout = 20

        id_token    = id_token or me.id_token

        data        = ""
        data       += "tx=" + urllib.quote_plus(tid)
        data       += "&at=" + urllib.quote_plus(id_token)
        data       += "&cmd=_notify-synch"

        url         = urllib2.Request("https://" + me.server + "/cgi-bin/webscr", data)

        for i in xrange(2) :
            info    = {}

            r       = url_getter.url_open_read_with_timeout(url, timeout = timeout)
            if  r :
                if  re.match(SUCCESS + r"\r?\n", r) :
                    for kv in re.split(r"\r?\n", r)[1:] :
                        kv          = kv.strip()
                        if  kv :
                            kx      = kv.find("=")
                            if  kx  < 0 :
                                kx  = len(kv) - 1
                            k       = kv[0:kx].strip()
                            v       = urllib.unquote_plus(kv[kx + 1:]).strip()
                            info[k]         = [ v ]
                            info[k.lower()] = [ v ]
                        pass

                    info[ACK]           = [ SUCCESS ]
                    info[ACK.lower()]   = [ SUCCESS ]
                    break
                pass
            pass

        me.transactions[tid]    = info
        me.latest_tid           = tid

        return(info)            # note that info is a hash with the names of the values as keys (and all names are available as lower case), and the values are an array - always 1 element, to my knowledge.


    def tid_str(me, tid = "") :
        """
            Return a printable string for the get_transaction_id_information() response for this TID if there has been one.
            If tid is None or "", get the string for the results of the latest call to get_transaction_id_information().
        """

        tid     = tid or me.latest_tid

        return(info_str(me.transactions.get(tid, "")))



    pass        #   a_pdt






#
#
#       Test
#
#
if __name__ == '__main__' :

    if  True :

        import  ConfigParser

        ini =   ConfigParser.ConfigParser({
                                              "user"              :  "",
                                              "password"          :  "",
                                              "signature"         :  "",
                                              "id_token"          :  "",
                                          }
                                         )

        INI_SECTION = "tz_paypal"
        ini.add_section(INI_SECTION)
        ini.read( [ 'tz_paypal.cfg' ] )

        user        = ini.get(INI_SECTION, "user")
        pw          = ini.get(INI_SECTION, "password")
        sig         = ini.get(INI_SECTION, "signature")
        id_token    = ini.get(INI_SECTION, "id_token")


        pp  = an_api(user_name = user, password = pw, signature = sig)

        #   sandbox transaction oct 31, 07
        info    = pp.get_transaction_id_information("263382001U270513W")
        print
        print   info_ok(info)
        print   pp.tid_str()

        #   sandbox transaction oct 31, 07
        info    = pp.get_transaction_id_information("15V92047M85880137")
        print
        print   info_ok(info)
        print   pp.tid_str()

        #   sandbox transaction oct 31, 07
        info    = pp.get_transaction_id_information("1S912699DT701474J")
        print
        print   info_ok(info)
        print   pp.tid_str()

        #   wrong type of transaction
        info    = pp.get_transaction_id_information("41G06797H3905744V")
        print
        print   info_ok(info)
        print   pp.tid_str()

        #   bad transaction id
        info    = pp.get_transaction_id_information("x")
        print
        print   info_ok(info)
        print   pp.tid_str()

        #   sandbox transaction Nov 24, 07
        info    = pp.get_transaction_id_information("4FU4686147978701J")
        print
        print   info_ok(info)
        print   pp.tid_str()

    if  True :
        pp      = a_pdt()
        info    = pp.get_transaction_id_information("6G067646D38163543", id_token)
        print
        print   info_ok(info)
        print   pp.tid_str()


    pass

#
#
#
# eof
