def create_payment_model(self): """ Create payment instance with base information to track payment submissions """ self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, provider=self.provider, invoice=self.invoice, created=timezone.now()) self.payment.result[ 'account_number'] = self.payment_info.cleaned_data.get( 'card_number')[-4:] self.payment.payee_full_name = self.payment_info.cleaned_data.get( 'full_name') self.payment.payee_company = self.billing_address.cleaned_data.get( 'company') billing_address = self.billing_address.save(commit=False) billing_address, created = self.invoice.profile.get_or_create_address( billing_address) if created: billing_address.profile = self.invoice.profile billing_address.save() self.payment.billing_address = billing_address self.payment.save()
def test_refund_fail_invalid_transaction_id(self): """ Checks for transaction_submitted fail because the transaction id does not match """ self.processor = AuthorizeNetProcessor(self.existing_invoice) status_before_transaction = self.existing_invoice.status # Get Settled payment start_date, end_date = (timezone.now() - timedelta(days=31)), timezone.now() batch_list = self.processor.get_settled_batch_list( start_date, end_date) transaction_list = self.processor.get_transaction_batch_list( str(batch_list[-1].batchId)) payment = Payment() payment.invoice = self.existing_invoice payment.amount = 0.01 payment.transaction = '111222333412' payment.result["raw"] = str( {'accountNumber': transaction_list[-1].accountNumber.text}) payment.save() self.processor.payment = payment self.processor.refund_payment(payment) self.assertFalse(self.processor.transaction_submitted) self.assertEquals(self.processor.invoice.status, status_before_transaction)
def test_refund_fail_invalid_amount(self): """ Checks for transaction_submitted fail because the amount exceeds the payment transaction settled. """ status_before_transaction = self.existing_invoice.status # Get Settled payment start_date, end_date = (timezone.now() - timedelta(days=31)), timezone.now() batch_list = self.processor.get_settled_batch_list( start_date, end_date) transaction_list = self.processor.get_transaction_batch_list( str(batch_list[-1].batchId)) payment = Payment() payment.invoice = self.existing_invoice payment.amount = 1000000.00 payment.transaction = transaction_list[-1].transId.text payment.result["raw"] = str( {'accountNumber': transaction_list[-1].accountNumber.text}) payment.save() self.processor.payment = payment self.processor.refund_payment(payment) self.assertFalse(self.processor.transaction_submitted) self.assertEquals(self.processor.invoice.status, status_before_transaction)
def test_refund_fail_invalid_account_number(self): """ Checks for transaction_submitted fail because the account number does not match the payment transaction settled. """ status_before_transaction = self.existing_invoice.status # Get Settled payment start_date, end_date = (timezone.now() - timedelta(days=31)), timezone.now() batch_list = self.processor.get_settled_batch_list( start_date, end_date) if not batch_list: print("No Transactions to refund Skipping\n") return transaction_list = self.processor.get_transaction_batch_list( str(batch_list[-1].batchId)) payment = Payment() payment.invoice = self.existing_invoice payment.amount = 0.01 payment.transaction = transaction_list[-1].transId.text payment.result["raw"] = str({'accountNumber': '6699'}) payment.save() self.processor.payment = payment self.processor.refund_payment(payment) self.assertFalse(self.processor.transaction_submitted) self.assertEquals(self.processor.invoice.status, status_before_transaction)
def test_refund_success(self): """ In order for this test to pass a transaction has to be settled first. The settlement process takes effect once a day. It is defined in the Sandbox in the cut-off time. The test will get a settle payment and test refund transaction. """ # Get Settled payment start_date, end_date = (timezone.now() - timedelta(days=31)), timezone.now() batch_list = self.processor.get_settled_batch_list(start_date, end_date) for batch in batch_list: transaction_list = self.processor.get_transaction_batch_list(str(batch.batchId)) successfull_transactions = [ t for t in transaction_list if t['transactionStatus'] == 'settledSuccessfully' ] if len(successfull_transactions) > 0: break if not successfull_transactions: print("No Transactions to refund Skipping\n") return payment = Payment() # payment.amount = transaction_detail.authAmount.pyval # Hard coding minimum amount so the test can run multiple times. payment.amount = 0.01 payment.invoice = self.existing_invoice payment.transaction = successfull_transactions[-1].transId.text payment.result["raw"] = str({ 'accountNumber': successfull_transactions[-1].accountNumber.text}) payment.save() self.processor.payment = payment self.processor.refund_payment(payment) # print(f'Message: {self.processor.transaction_message}\nResponse: {self.processor.transaction_response}') self.assertEquals(Invoice.InvoiceStatus.REFUNDED, self.existing_invoice.status)
def renew_subscription(self, past_receipt, payment_info): """ Function to renew already paid subscriptions form the payment gateway provider. """ self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, invoice=self.invoice, created=timezone.now()) self.payment.result = payment_info self.transaction_submitted = True self.payment.success = True self.payment.transaction = past_receipt.transaction self.payment.payee_full_name = " ".join([ self.invoice.profile.user.first_name, self.invoice.profile.user.last_name ]) self.payment.save() self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) self.create_receipts(self.invoice.order_items.all())
def free_payment(self): """ Called to handle an invoice with total zero. This are the base internal steps to process a free payment. """ self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, provider=self.provider, invoice=self.invoice, created=timezone.now()) self.payment.save() self.transaction_submitted = True self.payment.success = True self.payment.transaction = f"{self.payment.uuid}-free" self.payment.payee_full_name = " ".join([ self.invoice.profile.user.first_name, self.invoice.profile.user.last_name ]) self.payment.save() self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) self.create_receipts(self.invoice.order_items.all())
def create_payment_model(self): self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, provider=self.provider, invoice=self.invoice, created=timezone.now())
class PaymentProcessorBase(object): """ Setup the core functionality for all processors. """ status = None invoice = None provider = None payment = None payment_info = {} billing_address = {} transaction_token = None transaction_submitted = False transaction_message = {} transaction_response = {} def __init__(self, invoice): """ This should not be overriden. Override one of the methods it calls if you need to. """ self.set_invoice(invoice) self.provider = self.__class__.__name__ self.processor_setup() def processor_setup(self): """ This is for setting up any of the settings needed for the payment processing. For example, here you would set the """ pass def set_payment_info(self, **kwargs): self.payment_info = kwargs def set_invoice(self, invoice): self.invoice = invoice def create_payment_model(self): self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, provider=self.provider, invoice=self.invoice, created=timezone.now()) def save_payment_transaction(self): pass def update_invoice_status(self, new_status): if self.transaction_submitted: self.invoice.status = new_status else: self.invoice.status = Invoice.InvoiceStatus.CART self.invoice.save() def create_receipt_by_term_type(self, product, order_item, term_type): receipt = Receipt() receipt.profile = self.invoice.profile receipt.order_item = order_item receipt.transaction = self.payment.transaction receipt.status = PurchaseStatus.COMPLETE receipt.start_date = timezone.now() if term_type == TermType.SUBSCRIPTION: total_months = int( order_item.offer.term_details['period_length']) * int( order_item.offer.term_details['payment_occurrences']) receipt.end_date = timezone.now() + timedelta(days=(total_months * 31)) receipt.auto_renew = True elif term_type == TermType.MONTHLY_SUBSCRIPTION: total_months = 1 receipt.end_date = timezone.now() + timedelta(days=(total_months * 31)) receipt.auto_renew = True elif term_type == TermType.QUARTERLY_SUBSCRIPTION: total_months = 3 receipt.end_date = timezone.now() + timedelta(days=(total_months * 31)) receipt.auto_renew = True elif term_type == TermType.SEMIANNUAL_SUBSCRIPTION: total_months = 6 receipt.end_date = timezone.now() + timedelta(days=(total_months * 31)) receipt.auto_renew = True elif term_type == TermType.ANNUAL_SUBSCRIPTION: total_months = 12 receipt.end_date = timezone.now() + timedelta(days=(total_months * 31)) receipt.auto_renew = True elif term_type == TermType.PERPETUAL: receipt.auto_renew = False elif term_type == TermType.ONE_TIME_USE: receipt.auto_renew = False return receipt def create_receipts(self): if self.payment.success and self.invoice.status == Invoice.InvoiceStatus.COMPLETE: for order_item in self.invoice.order_items.all(): for product in order_item.offer.products.all(): receipt = self.create_receipt_by_term_type( product, order_item, order_item.offer.terms) receipt.save() receipt.products.add(product) def update_subscription_receipt(self, subscription, subscription_id, status): """ subscription: OrderItem subscription_id: int status: PurchaseStatus """ subscription_receipt = self.invoice.order_items.get( offer=subscription.offer).receipts.get( transaction=self.payment.transaction) subscription_receipt.meta['subscription_id'] = subscription_id subscription_receipt.status = status subscription_receipt.save() def amount(self): # Retrieves the total amount from the invoice self.invoice.update_totals() return self.invoice.total def amount_without_subscriptions(self): subscription_total = sum([ oi.total for oi in self.invoice.order_items.filter( offer__terms=TermType.SUBSCRIPTION) ]) amount = self.invoice.total - subscription_total return amount def get_transaction_id(self): return "{}-{}-{}-{}".format(self.invoice.profile.pk, settings.SITE_ID, self.invoice.pk, str(self.payment.created)[-12:-6]) def get_billing_address_form_data(self, form_data, form_class): self.billing_address = form_class(form_data) def get_payment_info_form_data(self, form_data, form_class): self.payment_info = form_class(form_data) def is_data_valid(self): if not (self.billing_address.is_valid() and self.payment_info.is_valid() and self.invoice and self.invoice.order_items.count()): return False return True #------------------- # Data for the View def get_checkout_context(self, request=None, context={}): ''' The Invoice plus any additional values to include in the payment record. ''' # context = deepcopy(context) context['invoice'] = self.invoice return context def get_header_javascript(self): """ Scripts that are expected to show in the top of the template. This will return a list of relative static URLs to the scripts. """ return [] def get_javascript(self): """ Scripts added to the bottom of the page in the normal js location. This will return a list of relative static URLs to the scripts. """ return [] def get_template(self): """ Unique partial template for the processor """ pass #------------------- # Process a Payment def authorize_payment(self): """ This runs the chain of events in a transaction. This should not be overriden. Override one of the methods it calls if you need to. """ self.status = PurchaseStatus.QUEUED # TODO: Set the status on the invoice. Processor status should be the invoice's status. vendor_pre_authorization.send(sender=self.__class__, invoice=self.invoice) self.pre_authorization() self.status = PurchaseStatus.ACTIVE # TODO: Set the status on the invoice. Processor status should be the invoice's status. vendor_process_payment.send(sender=self.__class__, invoice=self.invoice) if not self.invoice.total: self.free_payment() elif self.is_data_valid(): self.process_payment() else: return None vendor_post_authorization.send(sender=self.__class__, invoice=self.invoice) self.post_authorization() #TODO: Set the status based on the result from the process_payment() def pre_authorization(self): """ Called before the authorization begins. """ pass def process_payment(self): """ Called to handle the authorization. This is where the core of the payment processing happens. """ # Gateway Transaction goes here... self.transaction_submitted = True pass def free_payment(self): """ Called to handle an invoice with total zero. This are the base internal steps to process a free payment. """ self.transaction_submitted = True self.create_payment_model() self.payment.success = True self.payment.transation = f"{self.payment.pk}-free" self.payment.payee_full_name = " ".join([ self.invoice.profile.user.first_name, self.invoice.profile.user.last_name ]) self.payment.save() self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) self.create_receipts() def post_authorization(self): """ Called after the authorization is complete. """ pass def capture_payment(self): """ Called to handle the capture. (some gateways handle this at the same time as authorize_payment() ) """ pass #------------------- # Process a Subscription def subscription_payment(self): pass def update_subscription_payment(self): pass def cancel_subscription_payment(self): pass #------------------- # Refund a Payment def refund_payment(self): pass
class PaymentProcessorBase(object): """ Setup the core functionality for all processors. """ API_ENDPOINT = None status = None invoice = None provider = None payment = None payment_info = {} billing_address = {} transaction_token = None transaction_id = "" transaction_submitted = False transaction_message = {} transaction_response = {} def __init__(self, invoice): """ This should not be overriden. Override one of the methods it calls if you need to. """ self.set_invoice(invoice) self.provider = self.__class__.__name__ self.processor_setup() self.set_api_endpoint() def set_api_endpoint(self): """ Sets the API endpoint for debugging or production.It is dependent on the VENDOR_STATE enviornment variable. Default value is DEBUG for the VENDOR_STATE this function should be overwrote upon necesity of each Payment Processor """ if config.VENDOR_STATE == 'DEBUG': self.API_ENDPOINT = None elif config.VENDOR_STATE == 'PRODUCTION': self.API_ENDPOINT = None def processor_setup(self): """ This is for setting up any of the settings needed for the payment processing. For example, here you would set the """ pass def set_payment_info(self, **kwargs): self.payment_info = kwargs def set_invoice(self, invoice): self.invoice = invoice def create_payment_model(self): """ Create payment instance with base information to track payment submissions """ self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, provider=self.provider, invoice=self.invoice, created=timezone.now()) self.payment.result[ 'account_number'] = self.payment_info.cleaned_data.get( 'card_number')[-4:] self.payment.payee_full_name = self.payment_info.cleaned_data.get( 'full_name') self.payment.payee_company = self.billing_address.cleaned_data.get( 'company') billing_address = self.billing_address.save(commit=False) billing_address, created = self.invoice.profile.get_or_create_address( billing_address) if created: billing_address.profile = self.invoice.profile billing_address.save() self.payment.billing_address = billing_address self.payment.save() def save_payment_transaction_result(self, payment_success, transaction_id, result_info): """ Saves the result output of any transaction. """ self.payment.success = payment_success self.payment.transaction = transaction_id self.payment.result.update(result_info) self.payment.save() def update_invoice_status(self, new_status): """ Updates the Invoice status if the transaction was submitted. Otherwise it returns the invoice to the Cart. The error is saved in the payment for the transaction. """ if self.transaction_submitted: self.invoice.status = new_status else: self.invoice.status = Invoice.InvoiceStatus.CART self.invoice.save() def is_payment_and_invoice_complete(self): """ If payment was successful and invoice status is complete returns True. Otherwise false and no receipts should be created. """ if self.payment.success and self.invoice.status == Invoice.InvoiceStatus.COMPLETE: return True return False def get_future_date_months(self, today, add_months): """ Returns a datetime object with the a new added months """ newday = today.day newmonth = (((today.month - 1) + add_months) % 12) + 1 newyear = today.year + (((today.month - 1) + add_months) // 12) if newday > mdays[newmonth]: newday = mdays[newmonth] if newyear % 4 == 0 and newmonth == 2: newday += 1 return date(newyear, newmonth, newday) def get_future_date_days(self, today, add_days): """ Returns a datetime object with the a new added days """ return today + timedelta(days=add_days) def get_trial_occurrences(self, subscription): return subscription.offer.term_details.get('trial_occurrences', 0) def get_payment_schedule_start_date(self, subscription): """ Determines the start date offset so the payment gateway starts charging the monthly subscriptions If the customer has already purchased the subscription it will return timezone.now() """ if self.invoice.profile.has_previously_owned_products( subscription.offer.products.all()): return timezone.now() units = subscription.offer.term_details.get('term_units', TermDetailUnits.MONTH) if units == TermDetailUnits.MONTH: return self.get_future_date_months( timezone.now(), self.get_trial_occurrences(subscription)) elif units == TermDetailUnits.DAY: return self.get_future_date_days( timezone.now(), self.get_trial_occurrences(subscription)) def create_receipt_by_term_type(self, product, order_item, term_type): today = timezone.now() receipt = Receipt() receipt.profile = self.invoice.profile receipt.order_item = order_item receipt.transaction = self.payment.transaction receipt.status = PurchaseStatus.COMPLETE receipt.start_date = today if term_type == TermType.PERPETUAL or term_type == TermType.ONE_TIME_USE: receipt.auto_renew = False elif term_type == TermType.SUBSCRIPTION: total_months = int( order_item.offer.term_details['period_length']) * int( order_item.offer.term_details['payment_occurrences']) else: total_months = term_type - 100 # Get if it is monthy, bi-monthly, quartarly of annually if term_type < TermType.PERPETUAL: trial_offset = self.get_payment_schedule_start_date( order_item ) # If there are any trial days or months you need to offset it on the end date. receipt.end_date = self.get_future_date_months( trial_offset, total_months) receipt.auto_renew = True return receipt def create_order_item_receipt(self, order_item): """ Creates a receipt for every product in the order item according to its, offering term type. """ for product in order_item.offer.products.all(): receipt = self.create_receipt_by_term_type(product, order_item, order_item.offer.terms) receipt.save() receipt.products.add(product) def create_receipts(self, order_items): """ It then creates receipt for the order items supplied. """ for order_item in order_items.all(): self.create_order_item_receipt(order_item) def update_subscription_receipt(self, subscription, subscription_id, status): """ subscription: OrderItem subscription_id: int status: PurchaseStatus """ subscription_receipt = self.invoice.order_items.get( offer=subscription.offer).receipts.get( transaction=self.payment.transaction) subscription_receipt.meta['subscription_id'] = subscription_id subscription_receipt.status = status subscription_receipt.save() def amount(self): # Retrieves the total amount from the invoice self.invoice.update_totals() return self.invoice.total def amount_without_subscriptions(self): subscription_total = sum([ oi.total for oi in self.invoice.order_items.filter( offer__terms=TermType.SUBSCRIPTION) ]) amount = self.invoice.total - subscription_total return amount def get_transaction_id(self): return "{}-{}-{}-{}".format(self.invoice.profile.pk, settings.SITE_ID, self.invoice.pk, str(self.payment.created)[-12:-6]) def set_billing_address_form_data(self, form_data, form_class): self.billing_address = form_class(form_data) def set_payment_info_form_data(self, form_data, form_class): self.payment_info = form_class(form_data) def is_data_valid(self): if not (self.billing_address.is_valid() and self.payment_info.is_valid() and self.invoice and self.invoice.order_items.count()): return False return True #------------------- # Data for the View def get_checkout_context(self, request=None, context={}): ''' The Invoice plus any additional values to include in the payment record. ''' # context = deepcopy(context) context['invoice'] = self.invoice return context def get_header_javascript(self): """ Scripts that are expected to show in the top of the template. This will return a list of relative static URLs to the scripts. """ return [] def get_javascript(self): """ Scripts added to the bottom of the page in the normal js location. This will return a list of relative static URLs to the scripts. """ return [] def get_template(self): """ Unique partial template for the processor """ pass #------------------- # Process a Payment def authorize_payment(self): """ This runs the chain of events in a transaction. This should not be overriden. Override one of the methods it calls if you need to. """ # TODO: Should this validation be outside the call to authorize the payment the call? # Why bother to call the processor is the forms are wrong if not self.invoice.total: self.free_payment() return None if not self.is_data_valid(): return None self.status = PurchaseStatus.QUEUED # TODO: Set the status on the invoice. Processor status should be the invoice's status. vendor_pre_authorization.send(sender=self.__class__, invoice=self.invoice) self.pre_authorization() self.status = PurchaseStatus.ACTIVE # TODO: Set the status on the invoice. Processor status should be the invoice's status. vendor_process_payment.send(sender=self.__class__, invoice=self.invoice) if self.invoice.get_one_time_transaction_order_items(): self.create_payment_model() self.process_payment() self.save_payment_transaction_result(self.transaction_submitted, self.transaction_id, self.transaction_response) self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) if self.is_payment_and_invoice_complete(): self.create_receipts( self.invoice.get_one_time_transaction_order_items()) if self.invoice.get_recurring_order_items(): self.process_subscriptions() vendor_post_authorization.send(sender=self.__class__, invoice=self.invoice) self.post_authorization() #TODO: Set the status based on the result from the process_payment() def pre_authorization(self): """ Called before the authorization begins. """ pass def process_payment(self): """ Called to handle the authorization. This is where the core of the payment processing happens. """ # Gateway Transaction goes here... def free_payment(self): """ Called to handle an invoice with total zero. This are the base internal steps to process a free payment. """ self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, provider=self.provider, invoice=self.invoice, created=timezone.now()) self.payment.save() self.transaction_submitted = True self.payment.success = True self.payment.transaction = f"{self.payment.uuid}-free" self.payment.payee_full_name = " ".join([ self.invoice.profile.user.first_name, self.invoice.profile.user.last_name ]) self.payment.save() self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) self.create_receipts(self.invoice.order_items.all()) def post_authorization(self): """ Called after the authorization is complete. """ pass def capture_payment(self): """ Called to handle the capture. (some gateways handle this at the same time as authorize_payment() ) """ pass def void_payment(self): """ Call to handle a payment that has not been settled and wants to be voided """ pass #------------------- # Process a Subscription def process_subscriptions(self): """ Process/subscribies recurring payments throught the payement gateway and creates a payment model for each subscription. If a payment is completed it will create a receipt for the subscription """ if not self.is_card_valid(): return None for subscription in self.invoice.get_recurring_order_items(): self.create_payment_model() self.subscription_payment(subscription) self.save_payment_transaction_result(self.transaction_submitted, self.transaction_id, self.transaction_response) self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) if self.is_payment_and_invoice_complete(): self.create_order_item_receipt(subscription) def subscription_payment(self, subscription): """ Call handels the authrization and creation for a subscription. """ # Gateway Transaction goes here... def subscription_info(self): pass def subscription_update_payment(self): pass def subscription_cancel(self): pass def is_card_valid(self): """ Function to validate a credit card by method of makeing a microtransaction and voiding it if authorized. """ pass def renew_subscription(self, past_receipt, payment_info): """ Function to renew already paid subscriptions form the payment gateway provider. """ self.payment = Payment(profile=self.invoice.profile, amount=self.invoice.total, invoice=self.invoice, created=timezone.now()) self.payment.result = payment_info self.transaction_submitted = True self.payment.success = True self.payment.transaction = past_receipt.transaction self.payment.payee_full_name = " ".join([ self.invoice.profile.user.first_name, self.invoice.profile.user.last_name ]) self.payment.save() self.update_invoice_status(Invoice.InvoiceStatus.COMPLETE) self.create_receipts(self.invoice.order_items.all()) #------------------- # Refund a Payment def refund_payment(self): pass