Esempio n. 1
0
from payflowpro.classes import (CreditCard, Amount, Profile, Address, Tracking, Response, CustomerInfo, AchPayment)
from payflowpro.client import PayflowProClient, find_classes_in_list, find_class_in_list

#PARTNER_ID = 'Paypal'
#VENDOR_ID = 'wiremine'
#USERNAME = '******'
#PASSWORD = '******'



client = PayflowProClient(partner=PARTNER_ID, vendor=VENDOR_ID, username=USERNAME, password=PASSWORD, url_base='https://pilot-payflowpro.paypal.com')
credit_card = CreditCard(acct=5555555555554444, expdate="1212")
responses, unconsumed_data = client.sale(credit_card, Amount(amt=15, currency="USD"), extras=[Address(street="82 E 38th Street", zip="49423")])

#arc_payment = AchPayment(aba='111111118', acct='1111111111', accttype='S')
#customer = CustomerInfo(firstname='Bob Smith')
#responses, unconsumed_data = client.sale(arc_payment, Amount(amt=15, currency="USD"), extras=[customer])

print responses[0]
Esempio n. 2
0
class PaymentProcessor(BasePaymentProcessor):
    """
    PayflowPro payment processing module
    You must have an account with PayPal in order to use this module.
    """
    def __init__(self, settings):
        super(PaymentProcessor, self).__init__('payflowpro', settings)
        partner = self.settings.PARTNER.value
        vendor = self.settings.VENDOR.value
        username = self.settings.USER.value
        password = self.settings.PASSWORD.value
        testing = not self.settings.LIVE.value
        if testing:
            url_base = PayflowProClient.URL_BASE_TEST
        else:
            url_base = PayflowProClient.URL_BASE_LIVE

        self.payflow = PayflowProClient(partner=partner, vendor=vendor,
                                        username=username, password=password,
                                        url_base=url_base)

    def get_charge_data(self, amount=None):
        """
        Build the dictionary needed to process a credit card charge.

        Return: a dictionary with the following key-values:
            * log_string: the transaction data without the sensible
                           buyer data. Suitable for logs.
            * credit_card, amount, address, ship_address, customer_info :
                 the payflowpro.classes.* instances to be passed to
                 self.payflow
        """
        order = self.order
        if amount is None:
            amount = order.balance
        balance = trunc_decimal(amount, 2)


        ret = {
            'credit_card': CreditCard(
                acct=order.credit_card.decryptedCC,
                expdate="%02d%02d" % (order.credit_card.expire_month,
                                      order.credit_card.expire_year % 100),
                cvv2=order.credit_card.ccv,
                ),
            
            'amount': Amount(amt=balance,),

            'address': Address(
                street=order.full_bill_street,
                zip=order.bill_postal_code,
                city=order.bill_city,
                state=order.bill_state,
                country=order.bill_country,
                ),

            'ship_address': ShippingAddress(
                shiptostreet=order.full_ship_street,
                shiptocity=order.ship_city,
                shiptofirstname=order.ship_first_name,
                shiptolastname=order.ship_last_name,
                shiptostate=order.ship_state,
                shiptocountry=order.ship_country,
                shiptozip=order.ship_postal_code,
                ),

            'customer_info': CustomerInfo(
                firstname=order.contact.first_name,
                lastname=order.contact.last_name,
                ),
            }

        redacted_data = ret.copy()
        redacted_data['credit_card'] = {
            'acct': order.credit_card.display_cc,
            'expdate': "%d%d" % (order.credit_card.expire_year,
                                 order.credit_card.expire_month),
            'cvv2': "REDACTED",
            }

        dicts = [getattr(d, 'data', d) for d in redacted_data.values()]
        ret['log_string'] = "\n".join("%s: %s" % (k, v) for d in dicts
                                      for k, v in d.items())

        return ret

    def _handle_unconsumed(self, unconsumed_data):
        """
        Handler for when we've got unconsumed data from the response
        """
        if unconsumed_data:
            self.log.warn("Something went wrong with python-payflowpro. "
                          "We got some unconsumed data: %s" % 
                          str(unconsumed_data))

    def _log_responses(self, responses):
        """
        Log the responses from PayflowPro for debugging
        """
        self.log_extra("Response variables from payflowpro:")
        for response in responses:
            self.log_extra("%(classname)s: %(response_fields)s" % {
                    'classname': response.__class__.__name__,
                    'response_fields': "%s" % response.data })


    def authorize_payment(self, order=None, amount=None, testing=False):
        """
        Authorize a single payment.

        Returns: ProcessorResult
        """
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        if order.paid_in_full:
            self.log_extra('%s is paid in full, no authorization attempted.',
                           order)
            result = ProcessorResult(self.key, True,
                                      _("No charge needed, paid in full."))
        else:
            self.log_extra('Authorizing payment of %s for %s', amount, order)

            data = self.get_charge_data(amount=amount)
            data['extras'] = [data['address'], data['ship_address'], 
                              data['customer_info'],]

            result = self.send_post(data=data, testing=testing,
                                    post_func=self.send_authorize_post,)

        return result

    def can_authorize(self):
        return True

    #def can_recur_bill(self):
    #    return True

    def capture_authorized_payment(self, authorization, testing=False,
                                   order=None, amount=None):
        """
        Capture a single payment
        """
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        if order.authorized_remaining == Decimal('0.00'):
            self.log_extra('No remaining authorizations on %s', order)
            return ProcessorResult(self.key, True, _("Already complete"))

        self.log_extra('Capturing Authorization #%i for %s',
                       authorization.id, order)
        data = self.get_charge_data()
        data['authorization_id'] = authorization.transaction_id
        result = self.send_post(data=data, testing=testing,
                                post_func=self.send_capture_post,)

        return result

    def capture_payment(self, testing=False, order=None, amount=None):
        """
        Process payments without an authorization step.
        """
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        if order.paid_in_full:
            self.log_extra('%s is paid in full, no capture attempted.', order)
            result = ProcessorResult(self.key, True, _("No charge needed, "
                                                        "paid in full."))
            self.record_payment()
        else:
            self.log_extra('Capturing payment for %s', order)

            data = self.get_charge_data(amount=amount)
            data['extras'] = [data['address'], data['ship_address'], 
                              data['customer_info'],]
            result = self.send_post(data=data, post_func=self.send_sale_post,
                                     testing=testing,)

        return result

    def release_authorized_payment(self, order=None, auth=None, testing=False):
        """Release a previously authorized payment."""
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        self.log_extra('Releasing Authorization #%i for %s', auth.id, order)
        data = self.get_charge_data()
        data['authorization_id'] = auth.transaction_id
        result = self.send_post(data=data, post_func=self.send_release_post,
                                testing=testing)
        if result.success:
            auth.complete = True
            auth.save()

        return result

    def send_authorize_post(self, data):
        """
        Authorize sell with PayflowPro
        """
        responses, unconsumed_data = self.payflow.authorization(
            credit_card=data['credit_card'], amount=data['amount'],
            extras=data['extras'])
        return responses, unconsumed_data, self.record_authorization

    def send_capture_post(self, data):
        """
        Capture previously authorized sale
        """
        responses, unconsumed_data = self.payflow.capture(
            data['authorization_id'])
        return responses, unconsumed_data, self.record_payment

    def send_release_post(self, data):
        """
        Release previously authorized sale
        """
        responses, unconsumed_data = self.payflow.void(
            data['authorization_id'])
        def nothing(*args, **kwargs):
            return None
        return responses, unconsumed_data, nothing 

    def send_sale_post(self, data):
        """
        Immediately charge a credit card
        """
        responses, unconsumed_data = self.payflow.sale(
            credit_card=data['credit_card'], amount=data['amount'],
            extras=data['extras'])
        return responses, unconsumed_data, self.record_payment

    def send_post(self, data, post_func, testing=False):
        """
        Execute the post to PayflowPro.

        Params:
        - data: the argument expected by `post_func`. Usually a dict which this
                function knows how to use
        - post_func: a function that takes `data` as argument, and sends the
                     actual request to the PayflowPro Gateway. It should return
                     a 3-tuple (responses, unconsumed_data, record_* function)
        - testing: if true, then don't record the payment

        Returns:
        - ProcessorResult
        """
        self.log_extra("About to send PayflowPro a request: %s",
                       data['log_string'])

        if 'amount' in data:
            amount = data['amount'].amt
        else:
            amount = self.order.balance

        responses, unconsumed_data, record_function = post_func(data)
        self._handle_unconsumed(unconsumed_data)
        self._log_responses(responses)

        response = responses[0]

        success = response.result == '0'
        transaction_id = response.pnref
        response_text = response.respmsg
        reason_code = response.result
        if success:
            # success!
            self.log.info("successful %s for order #%d",
                          post_func.__name__, self.order.id)
            if not testing:
                self.log_extra("Success, calling %s", record_function.__name__)
                payment = record_function(
                    order=self.order, amount=amount,
                    transaction_id=transaction_id, reason_code=reason_code)
        else:
            # failure =(
            self.log.info("failed %s for order #%d",
                          post_func.__name__, self.order.id)
            if not testing:
                payment = self.record_failure(
                    amount=amount, transaction_id=transaction_id,
                    reason_code=reason_code, details=response_text)
                
        self.log_extra("Returning success=%s, reason=%s, response_text=%s",
                       success, reason_code, response_text)

        return ProcessorResult(self.key, success, response_text,
                                 payment=payment)
Esempio n. 3
0
class PaymentProcessor(BasePaymentProcessor):
    """
    PayflowPro payment processing module
    You must have an account with PayPal in order to use this module.
    """
    def __init__(self, settings):
        super(PaymentProcessor, self).__init__('payflowpro', settings)
        partner = self.settings.PARTNER.value
        vendor = self.settings.VENDOR.value
        username = self.settings.USER.value
        password = self.settings.PASSWORD.value
        testing = not self.settings.LIVE.value
        if testing:
            url_base = PayflowProClient.URL_BASE_TEST
        else:
            url_base = PayflowProClient.URL_BASE_LIVE

        self.payflow = PayflowProClient(partner=partner, vendor=vendor,
                                        username=username, password=password,
                                        url_base=url_base)

    def get_charge_data(self, amount=None):
        """
        Build the dictionary needed to process a credit card charge.

        Return: a dictionary with the following key-values:
            * log_string: the transaction data without the sensible
                           buyer data. Suitable for logs.
            * credit_card, amount, address, ship_address, customer_info :
                 the payflowpro.classes.* instances to be passed to
                 self.payflow
        """
        order = self.order
        if amount is None:
            amount = order.balance
        balance = trunc_decimal(amount, 2)


        ret = {
            'credit_card': CreditCard(
                acct=order.credit_card.decryptedCC,
                expdate="%02d%02d" % (order.credit_card.expire_month,
                                      order.credit_card.expire_year % 100),
                cvv2=order.credit_card.ccv,
                ),
            
            'amount': Amount(amt=balance,),

            'address': Address(
                street=order.full_bill_street,
                zip=order.bill_postal_code,
                city=order.bill_city,
                state=order.bill_state,
                country=order.bill_country,
                ),

            'ship_address': ShippingAddress(
                shiptostreet=order.full_ship_street,
                shiptocity=order.ship_city,
                shiptofirstname=order.ship_first_name,
                shiptolastname=order.ship_last_name,
                shiptostate=order.ship_state,
                shiptocountry=order.ship_country,
                shiptozip=order.ship_postal_code,
                ),

            'customer_info': CustomerInfo(
                firstname=order.bill_first_name,
                lastname=order.bill_last_name,
                ),
            }

        redacted_data = ret.copy()
        redacted_data['credit_card'] = {
            'acct': order.credit_card.display_cc,
            'expdate': "%d%d" % (order.credit_card.expire_year,
                                 order.credit_card.expire_month),
            'cvv2': "REDACTED",
            }

        dicts = [getattr(d, 'data', d) for d in redacted_data.values()]
        ret['log_string'] = "\n".join("%s: %s" % (k, v) for d in dicts
                                      for k, v in d.items())

        return ret

    def _handle_unconsumed(self, unconsumed_data):
        """
        Handler for when we've got unconsumed data from the response
        """
        if unconsumed_data:
            self.log.warn("Something went wrong with python-payflowpro. "
                          "We got some unconsumed data: %s" % 
                          str(unconsumed_data))

    def _log_responses(self, responses):
        """
        Log the responses from PayflowPro for debugging
        """
        self.log_extra("Response variables from payflowpro:")
        for response in responses:
            self.log_extra("%(classname)s: %(response_fields)s" % {
                    'classname': response.__class__.__name__,
                    'response_fields': "%s" % response.data })


    def authorize_payment(self, order=None, amount=None, testing=False):
        """
        Authorize a single payment.

        Returns: ProcessorResult
        """
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        if order.paid_in_full:
            self.log_extra('%s is paid in full, no authorization attempted.',
                           order)
            result = ProcessorResult(self.key, True,
                                      _("No charge needed, paid in full."))
        else:
            self.log_extra('Authorizing payment of %s for %s', amount, order)

            data = self.get_charge_data(amount=amount)
            data['extras'] = [data['address'], data['ship_address'], 
                              data['customer_info'],]

            result = self.send_post(data=data, testing=testing,
                                    post_func=self.send_authorize_post,)

        return result

    def can_authorize(self):
        return True

    #def can_recur_bill(self):
    #    return True

    def capture_authorized_payment(self, authorization, testing=False,
                                   order=None, amount=None):
        """
        Capture a single payment
        """
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        if order.authorized_remaining == Decimal('0.00'):
            self.log_extra('No remaining authorizations on %s', order)
            return ProcessorResult(self.key, True, _("Already complete"))

        self.log_extra('Capturing Authorization #%i for %s',
                       authorization.id, order)
        data = self.get_charge_data()
        data['authorization_id'] = authorization.transaction_id
        result = self.send_post(data=data, testing=testing,
                                post_func=self.send_capture_post,)

        return result

    def capture_payment(self, testing=False, order=None, amount=None):
        """
        Process payments without an authorization step.
        """
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        if order.paid_in_full:
            self.log_extra('%s is paid in full, no capture attempted.', order)
            result = ProcessorResult(self.key, True, _("No charge needed, "
                                                        "paid in full."))
            self.record_payment()
        else:
            self.log_extra('Capturing payment for %s', order)

            data = self.get_charge_data(amount=amount)
            data['extras'] = [data['address'], data['ship_address'], 
                              data['customer_info'],]
            result = self.send_post(data=data, post_func=self.send_sale_post,
                                     testing=testing,)

        return result

    def release_authorized_payment(self, order=None, auth=None, testing=False):
        """Release a previously authorized payment."""
        if order:
            self.prepare_data(order)
        else:
            order = self.order

        self.log_extra('Releasing Authorization #%i for %s', auth.id, order)
        data = self.get_charge_data()
        data['authorization_id'] = auth.transaction_id
        result = self.send_post(data=data, post_func=self.send_release_post,
                                testing=testing)
        if result.success:
            auth.complete = True
            auth.save()

        return result

    def send_authorize_post(self, data):
        """
        Authorize sell with PayflowPro
        """
        responses, unconsumed_data = self.payflow.authorization(
            credit_card=data['credit_card'], amount=data['amount'],
            extras=data['extras'])
        return responses, unconsumed_data, self.record_authorization

    def send_capture_post(self, data):
        """
        Capture previously authorized sale
        """
        responses, unconsumed_data = self.payflow.capture(
            data['authorization_id'])
        return responses, unconsumed_data, self.record_payment

    def send_release_post(self, data):
        """
        Release previously authorized sale
        """
        responses, unconsumed_data = self.payflow.void(
            data['authorization_id'])
        def nothing(*args, **kwargs):
            return None
        return responses, unconsumed_data, nothing 

    def send_sale_post(self, data):
        """
        Immediately charge a credit card
        """
        responses, unconsumed_data = self.payflow.sale(
            credit_card=data['credit_card'], amount=data['amount'],
            extras=data['extras'])
        return responses, unconsumed_data, self.record_payment

    def send_post(self, data, post_func, testing=False):
        """
        Execute the post to PayflowPro.

        Params:
        - data: the argument expected by `post_func`. Usually a dict which this
                function knows how to use
        - post_func: a function that takes `data` as argument, and sends the
                     actual request to the PayflowPro Gateway. It should return
                     a 3-tuple (responses, unconsumed_data, record_* function)
        - testing: if true, then don't record the payment

        Returns:
        - ProcessorResult
        """
        self.log_extra("About to send PayflowPro a request: %s",
                       data['log_string'])

        if 'amount' in data:
            amount = data['amount'].amt
        else:
            amount = self.order.balance

        responses, unconsumed_data, record_function = post_func(data)
        self._handle_unconsumed(unconsumed_data)
        self._log_responses(responses)

        response = responses[0]

        success = response.result == '0'
        transaction_id = response.pnref
        response_text = response.respmsg
        reason_code = response.result
        if success:
            # success!
            self.log.info("successful %s for order #%d",
                          post_func.__name__, self.order.id)
            if not testing:
                self.log_extra("Success, calling %s", record_function.__name__)
                payment = record_function(
                    order=self.order, amount=amount,
                    transaction_id=transaction_id, reason_code=reason_code)
        else:
            # failure =(
            self.log.info("failed %s for order #%d",
                          post_func.__name__, self.order.id)
            if not testing:
                payment = self.record_failure(
                    amount=amount, transaction_id=transaction_id,
                    reason_code=reason_code, details=response_text)
                
        self.log_extra("Returning success=%s, reason=%s, response_text=%s",
                       success, reason_code, response_text)

        return ProcessorResult(self.key, success, response_text,
                                 payment=payment)
Esempio n. 4
0
from payflowpro.classes import (CreditCard, Amount, Profile, Address, Tracking,
                                Response, CustomerInfo, AchPayment)
from payflowpro.client import PayflowProClient, find_classes_in_list, find_class_in_list

#PARTNER_ID = 'Paypal'
#VENDOR_ID = 'wiremine'
#USERNAME = '******'
#PASSWORD = '******'

client = PayflowProClient(partner=PARTNER_ID,
                          vendor=VENDOR_ID,
                          username=USERNAME,
                          password=PASSWORD,
                          url_base='https://pilot-payflowpro.paypal.com')
credit_card = CreditCard(acct=5555555555554444, expdate="1212")
responses, unconsumed_data = client.sale(
    credit_card,
    Amount(amt=15, currency="USD"),
    extras=[Address(street="82 E 38th Street", zip="49423")])

#arc_payment = AchPayment(aba='111111118', acct='1111111111', accttype='S')
#customer = CustomerInfo(firstname='Bob Smith')
#responses, unconsumed_data = client.sale(arc_payment, Amount(amt=15, currency="USD"), extras=[customer])

print responses[0]
Esempio n. 5
0
class PayPalConnection (OnlinePaymentBase):
    # map codes to exceptions - tuples denote ranges
    CODE_MAP = OnlinePaymentBase._explode_map({
        1         : OnlinePaymentBase.AuthError,
        2         : OnlinePaymentBase.InvalidCardType,
        4         : OnlinePaymentBase.AmountInvalid,
        12        : OnlinePaymentBase.TransactionDeclined,
        23        : OnlinePaymentBase.CardNumberInvalid,
        24        : OnlinePaymentBase.CardExpirationInvalid,
        26        : OnlinePaymentBase.AuthError,
        30        : OnlinePaymentBase.DuplicateTransaction,
        33        : OnlinePaymentBase.RecurringOrderDoesNotExist,
        50        : OnlinePaymentBase.TransactionDeclined,
        51        : OnlinePaymentBase.AmountTooHigh,
        102       : OnlinePaymentBase.TryAgainLater,
        106       : OnlinePaymentBase.TryAgainLater,
        112       : OnlinePaymentBase.AVSFailure,
        114       : OnlinePaymentBase.CCVInvalid,
        (125,128) : OnlinePaymentBase.FraudCheckFailed,
    })

    TRANS_STATES = {
          1: 'error',
          6: 'settlement pending',
          7: 'settlement in progress',
          8: 'settlement completed/successfully',
          11: 'settlement failed',
          14: 'settlement incomplete',
    }

    FIELD_MAP = {
        'card_num'           : 'acct',
        'card_code'          : 'cvv2',
        'exp_date'           : 'expdate',
        'amount'             : 'amt',
        'address'            : 'street',
        'company'            : 'companyname',
        'description'        : 'comment1',
        'ship_to_address'    : 'shiptostreet',
        'ship_to_first_name' : 'shiptofirstname',
        'ship_to_last_name'  : 'shiptolastname',
        'ship_to_country'    : 'shiptocountry',
        'ship_to_city'       : 'shiptocity',
        'ship_to_state'      : 'shiptostate',
        'ship_to_zip'        : 'shiptozip',
        'first_name'         : 'firstname',
        'last_name'          : 'lastname',
        'phone'              : 'phonenum',
        'invoice_num'        : 'ponum',

        # recurring fields
        'recurring_id'    : 'profile_id',
        'recurring_start'       : 'start',
        'recurring_period'      : 'payperiod',
        'recurring_total_occurrences' : 'term',
    }

    RECURRING_FIELD_MAPPING = {
        'AMT'             : 'amount',
        'START'           : 'start',
        'NEXTPAYMENT'     : 'next',
        'AGGREGATEAMT'    : 'total',
        'NUMFAILPAYMENTS' : 'failed_payments'
    }

    PAY_PERIOD_MAPPING = {
        'months': 'MONT',
        'weeks' : 'WEEK',
    }

    REVERSE_PAY_PERIOD_MAPPING = dict(
        (v,k) for (k,v) in PAY_PERIOD_MAPPING.items())

    def __init__(self, **kwargs):
        super(PayPalConnection, self).__init__(**kwargs)

        if not self.debug:
            logging.getLogger('payflow_pro').setLevel(logging.WARNING)


        url_base = PayflowProClient.URL_BASE_TEST if self.test_mode \
                   else PayflowProClient.URL_BASE_LIVE
        self.cc = PayflowProClient(url_base=url_base, **self.auth)

    def validate_auth(self, auth):
        for key in ("partner", "vendor", "username", "password"):
            if not key in auth:
                raise InvalidAuthException("Missing required '%s' parameter."\
                                           % key)

    def authorize(self, **kwargs):
        (card, amount, extras) = self._process_params(kwargs)

        sale_result = self.cc.authorization(card, amount, extras=extras)
        result = self._munge_result(sale_result)
        
        self._handle_errors(result)

        return result

    def capture(self, **kwargs):
        trans_id = kwargs['trans_id']
        del(kwargs['trans_id'])

        # card and amount aren't really used here
        (card, amount, extras) = self._process_params(kwargs)

        sale_result = self.cc.capture(trans_id, extras=extras)
        result = self._munge_result(sale_result)
        
        self._handle_errors(result)

        return result


    def sale(self, **kwargs):
        (card, amount, extras) = self._process_params(kwargs)

        sale_result = self.cc.sale(card, amount, extras=extras)
        result = self._munge_result(sale_result)
        
        self._handle_errors(result)

        return result

    def void(self, **kwargs):
        trans_id = kwargs['trans_id']
        del(kwargs['trans_id'])

        # card and amount aren't really used here
        (card, amount, extras) = self._process_params(kwargs)

        void_result = self.cc.void(trans_id, extras=extras)
        result = self._munge_result(void_result)
        
        self._handle_errors(result)

        return result

    def credit(self, **kwargs):
        trans_id = kwargs['trans_id']
        del(kwargs['trans_id'])

        # card and amount aren't really used here
        (card, amount, extras) = self._process_params(kwargs)

        credit_result = self.cc.credit_referenced(trans_id, extras=extras)
        result = self._munge_result(credit_result)
        
        self._handle_errors(result)

        return result

    def inquiry(self, **kwargs):
        trans_id = kwargs['trans_id']
        del(kwargs['trans_id'])

        inquiry_result = self.cc.inquiry(original_pnref=trans_id)
        result = self._munge_result(inquiry_result)

        self._handle_errors(result)

        return result


    def recurring_order_create(self, **kwargs):

        (card, amount, extras) = self._process_params(kwargs)
        (profile,) = find_classes_in_list(Profile, extras)

        result = self.cc.profile_add(profile, card, amount, extras=extras)
        result = self._munge_result(result)

        self._handle_errors(result)

        logging.debug('result = %s' % (result,))

        return result

    def recurring_order_update(self, **kwargs):
        profile_id = kwargs['recurring_id']
        del(kwargs['recurring_id'])

        (card, amount, extras) = self._process_params(kwargs)

        # everything is an extra for update
        extras.append(amount)
        extras.append(card)

        result = self.cc.profile_modify(profile_id, extras=extras)
        result = self._munge_result(result)

        self._handle_errors(result)

        return result


    def recurring_order_cancel(self, **kwargs):
        profile_id = kwargs['recurring_id']
        del(kwargs['recurring_id'])

        result = self.cc.profile_cancel(profile_id)
        result = self._munge_result(result)

        self._handle_errors(result)

        return result

    def recurring_order_payments(self, **kwargs):
        profile_id = kwargs['recurring_id']
        del(kwargs['recurring_id'])

        payment_history_only = kwargs.get('payment_history_only', True)

        result = self.cc.profile_inquiry(
            profile_id, payment_history_only=payment_history_only)
        result = self._munge_result(result)

        self._handle_errors(result)

        return result

    def recurring_order_inquiry(self, **kwargs):
        return \
            self.recurring_order_payments(payment_history_only=False, **kwargs)

    # override process params to build payflow objects after mapping names
    def _process_params(self, kwargs):
        # check and process params
        kwargs = super(PayPalConnection, self)._process_params(kwargs)

        card = self._build_card(kwargs)
        amount = self._build_amount(kwargs)
        extras = self._build_extras(kwargs)

        # if there's anything left in kwargs it's not getting to
        # paypal, so log a warning.  I'm tempted to make this fatal,
        # but I imagine in many cases it's not such a big deal - just
        # a trivial extra field that would be accepted by another
        # processor that's not applicable to paypal.
        if len(kwargs):
            self.log.warning("Extra parameters found: %s" % ','.join(kwargs))

        return (card, amount, extras)

    # Code to build the objects the payflow pro lib uses, only to
    # flatten them again later.  Yeesh.
    def _build_card(self, param):
        return CreditCard(acct    = param.pop('acct', ''),
                          expdate = param.pop('expdate', ''),
                          cvv2    = param.pop('cvv2', ''))

    def _build_amount(self, param):
        return Amount(amt      = param.pop('amt', ''),
                      currency = param.pop('currency', 'USD'),
                      taxamt   = param.pop('tax',''))


    def _build_extras(self, param):
        extras = []

        extras.append(self._build_address(param))
        extras.append(self._build_tracking(param))
        extras.append(self._build_shipping_address(param))
        extras.append(self._build_cust_info(param))
        extras.append(self._build_purchase_info(param))
        extras.append(self._build_recurring_info(param))

        return extras

    def _build_recurring_info(self, param):
        
        start = '%s' % param.pop('start', '')
        if re.match(r'\d{4}-\d{2}-\d{2}', start):
            start = re.sub(r'^(\d{4})-(\d{2})-(\d{2})', r'\2\3\1', start)

        profilename = param.pop('profilename', '')
        if not profilename:
            profilename = 'Default Profile Name'

        term = param.pop('term', '0') # default to forever

        payperiod = param.pop('payperiod', 'MONT')
        if payperiod in self.PAY_PERIOD_MAPPING:
            payperiod = self.PAY_PERIOD_MAPPING[payperiod]

        # For some reason having a blank desc is an error.  And it's
        # only an error on the live server, of course.
        desc = param.pop('desc', '')
        if not len(desc):
            desc = "unused"

        return Profile(
            profilename             = profilename,
            start                   = start,
            term                    = term,
            payperiod               = payperiod,
            maxfailpayments         = param.pop('maxfailpayments', ''),
            desc                    = desc,
            optionaltrx             = param.pop('optionaltrx', ''),
            optionaltrxamt          = param.pop('optionaltrxamt', ''),
            status                  = param.pop('status', ''),
            paymentsleft            = param.pop('paymentsleft', ''),
            nextpayment             = param.pop('nextpayment', ''),
            end                     = param.pop('end', ''),
            numfailpayments         = param.pop('numfailpayments', ''),
            retrynumdays            = param.pop('retrynumdays', ''),
            aggregateamt            = param.pop('aggregateamt', ''),
            aggregateoptionalamt    = param.pop('aggregateoptionalamt', ''))

    def _build_address(self, param):
        return Address(street      = param.pop('street', ''),
                       zip         = param.pop('zip', ''),
                       city        = param.pop('city', ''),
                       state       = param.pop('state', ''),
                       country     = param.pop('country', ''),
                       companyname = param.pop('companyname', ''))

    def _build_tracking(self, param):
        return Tracking(comment1   = param.pop('comment1',''),
                        comment2   = param.pop('comment2',''),
                        verbosity  = param.pop('verbosity',''))

    def _build_shipping_address(self, param):
        return ShippingAddress(
            shiptostreet           = param.pop('shiptostreet',''),
            shiptocity             = param.pop('shiptocity',''),
            shiptofirstname        = param.pop('shiptofirstname',''),
            shiptomiddlename       = param.pop('shiptomiddlename',''),
            shiptolastname         = param.pop('shiptolastname',''),
            shiptostate            = param.pop('shiptostate',''),
            shiptocountry          = param.pop('shiptocountry',''),
            shiptozip              = param.pop('shiptozip',''))
           
    def _build_cust_info(self, param):
        return CustomerInfo(
            custcode               = param.pop('custcode',''),
            email                  = param.pop('email',''),
            firstname              = param.pop('firstname',''),
            name                   = param.pop('name',''),
            middlename             = param.pop('middlename',''),
            lastname               = param.pop('lastname',''),
            phonenum               = param.pop('phonenum',''))

    def _build_purchase_info(self, param):
        return PurchaseInfo(
            ponum                  = param.pop('ponum',''))

    def _parse_transaction_time(self, ts):
        # '24-Nov-09  04:33 AM'
        parsed  = datetime.strptime(ts, '%d-%b-%y  %I:%M %p')
        pacific = pytz.timezone('America/Los_Angeles')
        return pacific.localize(parsed).astimezone(pytz.utc)
        # return datetime(utc.year, utc.month, utc.day, utc.hour, utc.second)

    def _build_recurring_payment(self, payment):
        return OnlinePaymentRecurringPayment(
            trans_id        = payment.p_pnref,
            amount          = payment.p_amt,
            trans_timestamp = self._parse_transaction_time(payment.p_transtime),
            success         = payment.p_result == '0',
            original        = payment,
        )

    def _munge_result(self, orig):

        (response,) = find_classes_in_list([Response],orig[0])
        if (self.debug):
            self.log.debug("Result: %s" % repr(response.__dict__))

        result = {}

        result['code']     = int(response.result)
        result['message']  = response.respmsg
        result['success']  = True if result['code'] == 0 else False
        result['trans_id'] = response.pnref
        result['orig']     = response

        # recurring response of some kind
        (profile_response,) = find_classes_in_list([ProfileResponse], orig[0])
        if profile_response:
            return self._munge_recurring_result(profile_response, result, orig)

        return OnlinePaymentResult(**result)

    def _munge_recurring_result(self, profile_response, result, orig):

        result['trans_id'] = profile_response.rpref
        if hasattr(profile_response, 'profileid'):
            result['recurring_id'] = profile_response.profileid
        else:
            result['recurring_id'] = None

        # Profile, means a profile status request?
        (profile,) = find_classes_in_list([Profile], orig[0])
        if profile:
            payperiod = self.REVERSE_PAY_PERIOD_MAPPING.get(
                           profile.payperiod, profile.payperiod)
            result['profile'] = OnlinePaymentRecurringProfile(
                recurring_id  = profile_response.profileid,
                payperiod           = payperiod,
                status              = profile.status,
                start               = profile.start,
                next                = profile.nextpayment,
                amount              = '', # profile.amt does not exist, amount not included!?
                total               = profile.aggregateamt,
                failed_payments     = profile.numfailpayments,
                orig                = profile
            )

        # RecurringPayments, means we've got a payment history
        (profile_payments,) = find_classes_in_list([RecurringPayments], orig[0])
        if profile_payments:
            result['payments'] = [ self._build_recurring_payment(p) 
                                   for p in profile_payments.payments ]

        return OnlinePaymentRecurringResult(**result)