def refund(self, order, amount): logger.info('Credit...') annotation = IAnnotations(order) transaction_id = annotation[GetPaidInterfaces.keys.processor_txn_id] client = PayflowProClient(partner=self.options.partner, vendor=self.options.vendor, username=self.options.username, password=self.options.password, url_base=self._sites.get(self.options.server_url)) responses, unconsumed_data = client.credit_referenced(transaction_id) annotation[CREDIT_RESULT] = responses[0].result annotation[CREDIT_RESPMSG] = responses[0].respmsg if responses[0].result == '0': ret = GetPaidInterfaces.keys.results_success else: ret = responses[0].respmsg logger.info("CREDIT_PNREF: %s" % annotation[GetPaidInterfaces.keys.processor_txn_id]) logger.info("CREDIT_RESULT: %s" % annotation[CREDIT_RESULT]) logger.info("CREDIT_RESPMSG: %s" % annotation[CREDIT_RESPMSG]) return ret
def authorize( self, order, payment ): logger.info('Authorize...') client = PayflowProClient(partner=self.options.partner, vendor=self.options.vendor, username=self.options.username, password=self.options.password, url_base=self._sites.get(self.options.server_url)) card_exp_date = '' if hasattr(payment.cc_expiration, 'strftime'): card_exp_date = payment.cc_expiration.strftime('%m%y') else: # If cc_expiration is not of type date, then it should be # a string like this: '2011-08-03 00:00' # This is a bug in getpaid.formgen's single page checkout # and the correct fix is to swap out it's expiration date # widget with one that returns a date. yearMonthDay = payment.cc_expiration.split(' ')[0].split('-') _date = date(int(yearMonthDay[0]), int(yearMonthDay[1]), int(yearMonthDay[2])) card_exp_date = _date.strftime('%m%y') credit_card = CreditCard(acct=payment.credit_card, expdate=card_exp_date, cvv2=payment.cc_cvc) ba = order.billing_address responses, unconsumed_data = client.authorization(credit_card, Amount(amt=order.getTotalPrice(), currency=self.options.currency), extras=[Address(street=ba.bill_first_line, city=ba.bill_city, state=ba.bill_state, zip=ba.bill_postal_code)]) order.user_payment_info_trans_id = responses[0].pnref annotation = IAnnotations(order) annotation[GetPaidInterfaces.keys.processor_txn_id] = responses[0].pnref annotation[RESULT] = responses[0].result annotation[RESPMSG] = responses[0].respmsg annotation[CVV2_MATCH] = responses[0].cvv2match annotation[AUTH_CODE] = responses[0].authcode annotation[LAST_FOUR] = payment.credit_card[-4:] if responses[0].result == '0': ret = GetPaidInterfaces.keys.results_success else: ret = responses[0].respmsg logger.info("PNREF: %s" % annotation[GetPaidInterfaces.keys.processor_txn_id]) logger.info("RESULT: %s" % annotation[RESULT]) logger.info("RESPMSG: %s" % annotation[RESPMSG]) logger.info("CVV2_MATCH: %s" % annotation[CVV2_MATCH]) logger.info("AUTH_CODE: %s" % annotation[AUTH_CODE]) return ret
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 __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)
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]
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)
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]
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)
def purchase(userprofile=None, amt=15.00, shippingprice=5.00, tax=0.00, cctype='Visa', ccnum=settings.TEST_VISA_ACCOUNT_NO, expdate=settings.TEST_VISA_EXPIRATION, csc=settings.TEST_CVV2, firstname='John', middlename='', lastname='Doe', street='1313 Mockingbird Lane', street2='', city='Beverly Hills', state='CA', zip='90110', countrycode='US', currencycode='USD', journal_id=0, email=settings.TEST_EMAIL_PERSONAL, phone='', ipaddress='', quantity=0, coupon=''): """ Direct purchase through paypal. """ # make sure all amounts are not more precise than cents amt = Decimal(amt).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) shippingprice = Decimal(shippingprice).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) tax = Decimal(tax).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) total_amt = amt + shippingprice + tax settings.SHOP_LOGGER.info( "Purchase called: amt: %s, shipping: %s, tax: %s, cctype: %s, ccnum: %s, expdate: %s, csc: %s, firstname: %s, middlename: %s, lastname: %s, street: %s, street2: %s, city: %s, state: %s, zip: %s, countrycode: %s, currencycode: %s, journal_id: %s, email: %s, phone: %s, ipaddress: %s, coupon: %s" % (amt, shippingprice, tax, cctype, ccnum, expdate, csc, firstname, middlename, lastname, street, street2, city, state, zip, countrycode, currencycode, journal_id, email, phone, ipaddress, coupon)) if not validate_totals(state, shippingprice, tax, amt, quantity, coupon): return 'FAILURE: price hacked' try: if total_amt == 0: response_message = 'Approved' result = '0' auth_code = transaction_id = 'zero_value_from_coupon' else: # Setup the client object. client = PayflowProClient(partner=settings.PAYPAL_PARTNER_ID, vendor=settings.PAYPAL_VENDOR_ID, username=settings.PAYPAL_USERNAME, password=settings.PAYPAL_PASSWORD, url_base='https://payflowpro.paypal.com') # Here's a VISA Credit Card for testing purposes. credit_card = CreditCard(acct=ccnum, expdate=expdate, cvv2=csc) # Let's do a quick sale, auth only, with manual capture later responses, unconsumed_data = client.authorization( credit_card, Amount(amt=total_amt, currency="USD"), extras=[ Address(street=street + ',' + street2, city=city, billtocountry=countrycode, state=state, country=countrycode, companyname='', zip=zip) ]) # If the sale worked, we'll have a transaction ID transaction_id = responses[0].pnref # little string response_message = responses[ 0].respmsg # 'Approved', 'Invalid account number' auth_code = responses[0].authcode # little string, None on failure result = responses[0].result # 0, positive numbers are error codes auth_errors = responses[0].errors # {} if len(responses) > 1: # on error responses[1] may not exist avsaddr = responses[1].avsaddr # 'Y' iavs = responses[1].iavs # ? avszip = responses[1].avszip # ? addr_errors = responses[1].errors # {} if response_message == 'Approved' and result == '0': settings.SHOP_LOGGER.info( "Purchase called: response from paypal was SUCCESS: success: %s, transactionid: %s, auth_code: %s, response_message: %s" % (result, transaction_id, auth_code, response_message)) # create order-related db entries # XXX unitprice: replace with product's unit price HACK TODO FIXME shipping = create_shipping(firstname=firstname, middlename=middlename, lastname=lastname, addr1=street, addr2=street2, city=city, state=state, zip=zip, country=countrycode, telephone=phone) order = create_order(userprofile=userprofile, shipping=shipping, journal_id=journal_id, quantity=quantity, status='sold', ccauth=auth_code, notes='transaction_id: %s' % transaction_id, unitprice=Decimal("15.00"), shippingprice=shippingprice, tax=tax, coupon_id=coupon) # create validation ticket return get_ticket(journal_id) settings.SHOP_LOGGER.info( "Purchase called: response from paypal was FAILURE: success: %s, transactionid: %s, auth_errors: %s, response_message: %s" % (result, transaction_id, auth_errors, response_message)) return 'FAILURE: %s' % (result) except Exception, err: traceback.print_exc() settings.SHOP_LOGGER.info( "Purchase called: FAILURE, see exception log") settings.SHOP_LOGGER.exception("Exception:") return 'FAILURE: exception'
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)
def purchase(userprofile=None, amt=15.00, shippingprice=5.00, tax=0.00, cctype='Visa', ccnum=settings.TEST_VISA_ACCOUNT_NO, expdate=settings.TEST_VISA_EXPIRATION, csc=settings.TEST_CVV2, firstname='John', middlename='', lastname='Doe', street='1313 Mockingbird Lane', street2='', city='Beverly Hills', state='CA', zip='90110', countrycode='US', currencycode='USD', journal_id=0, email=settings.TEST_EMAIL_PERSONAL, phone='', ipaddress='', quantity=0, coupon=''): """ Direct purchase through paypal. """ # make sure all amounts are not more precise than cents amt = Decimal(amt).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) shippingprice = Decimal(shippingprice).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) tax = Decimal(tax).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) total_amt = amt + shippingprice + tax settings.SHOP_LOGGER.info("Purchase called: amt: %s, shipping: %s, tax: %s, cctype: %s, ccnum: %s, expdate: %s, csc: %s, firstname: %s, middlename: %s, lastname: %s, street: %s, street2: %s, city: %s, state: %s, zip: %s, countrycode: %s, currencycode: %s, journal_id: %s, email: %s, phone: %s, ipaddress: %s, coupon: %s" % (amt, shippingprice, tax, cctype, ccnum, expdate, csc, firstname, middlename, lastname, street, street2, city, state, zip, countrycode, currencycode, journal_id, email, phone, ipaddress, coupon)) if not validate_totals(state, shippingprice, tax, amt, quantity, coupon): return 'FAILURE: price hacked' try: if total_amt == 0: response_message = 'Approved' result = '0' auth_code = transaction_id = 'zero_value_from_coupon' else: # Setup the client object. client = PayflowProClient(partner=settings.PAYPAL_PARTNER_ID, vendor=settings.PAYPAL_VENDOR_ID, username=settings.PAYPAL_USERNAME, password=settings.PAYPAL_PASSWORD, url_base='https://payflowpro.paypal.com') # Here's a VISA Credit Card for testing purposes. credit_card = CreditCard(acct=ccnum, expdate=expdate, cvv2=csc) # Let's do a quick sale, auth only, with manual capture later responses, unconsumed_data = client.authorization(credit_card, Amount(amt=total_amt, currency="USD"), extras=[Address(street=street + ',' + street2, city=city, billtocountry=countrycode, state=state, country=countrycode, companyname='', zip=zip)]) # If the sale worked, we'll have a transaction ID transaction_id = responses[0].pnref # little string response_message = responses[0].respmsg # 'Approved', 'Invalid account number' auth_code = responses[0].authcode # little string, None on failure result = responses[0].result # 0, positive numbers are error codes auth_errors = responses[0].errors # {} if len(responses) > 1: # on error responses[1] may not exist avsaddr = responses[1].avsaddr # 'Y' iavs = responses[1].iavs # ? avszip = responses[1].avszip # ? addr_errors = responses[1].errors # {} if response_message == 'Approved' and result == '0': settings.SHOP_LOGGER.info("Purchase called: response from paypal was SUCCESS: success: %s, transactionid: %s, auth_code: %s, response_message: %s" % (result, transaction_id, auth_code, response_message)) # create order-related db entries # XXX unitprice: replace with product's unit price HACK TODO FIXME shipping = create_shipping(firstname=firstname, middlename=middlename, lastname=lastname, addr1=street, addr2=street2, city=city, state=state, zip=zip, country=countrycode, telephone=phone) order = create_order(userprofile=userprofile, shipping=shipping, journal_id=journal_id, quantity=quantity, status='sold', ccauth=auth_code, notes='transaction_id: %s' % transaction_id, unitprice=Decimal("15.00"), shippingprice=shippingprice, tax=tax, coupon_id=coupon) # create validation ticket return get_ticket(journal_id) settings.SHOP_LOGGER.info("Purchase called: response from paypal was FAILURE: success: %s, transactionid: %s, auth_errors: %s, response_message: %s" % (result, transaction_id, auth_errors, response_message)) return 'FAILURE: %s' % (result) except Exception, err: traceback.print_exc() settings.SHOP_LOGGER.info("Purchase called: FAILURE, see exception log") settings.SHOP_LOGGER.exception("Exception:") return 'FAILURE: exception'