def sell_to_customer_create_invoice(self, cmID): """ Add an invoice after adding a membership :param cmID: db.customers_memberships.id :return: db.invoices.id """ from openstudio.os_customer_membership import CustomerMembership from openstudio.os_invoice import Invoice db = current.db T = current.T cm = CustomerMembership(cmID) # Check if price exists and > 0: if self.row.Price: igpt = db.invoices_groups_product_types(ProductType='membership') iID = db.invoices.insert( invoices_groups_id=igpt.invoices_groups_id, Description=cm.get_name(), Status='sent', payment_methods_id=cm.row.payment_methods_id ) invoice = Invoice(iID) invoice.link_to_customer(cm.row.auth_customer_id) invoice.item_add_membership( cmID, ) return iID else: return None
def webhook_invoice_chargeback(iID, amount, date, mollie_payment_id, mollie_chargeback_id, note): """ Actuall add chargeback invoice payment This function is separate for testability """ from openstudio.os_invoice import Invoice invoice = Invoice(iID) print("note in wic") print(note) ipID = invoice.payment_add( amount, date, payment_methods_id=100, # Static id for Mollie payments mollie_payment_id=mollie_payment_id, mollie_chargeback_id=mollie_chargeback_id, note=note) # Notify customer of chargeback cuID = invoice.get_linked_customer_id() os_mail = OsMail() msgID = os_mail.render_email_template('payment_recurring_failed') os_mail.send_and_archive(msgID, cuID)
def upgrade_to_201906(): """ Upgrade operations to 2019.06 """ from openstudio.os_invoice import Invoice query = (db.customers_notes.Processed == None) db(query).update(Processed=True) # Insert missing links to customers for some subscription invoices left = [ db.invoices_items.on(db.invoices_items_customers_subscriptions. invoices_items_id == db.invoices_items.id), db.invoices.on(db.invoices_items.invoices_id == db.invoices.id), db.invoices_customers.on( db.invoices_customers.invoices_id == db.invoices.id), db.customers_subscriptions.on( db.invoices_items_customers_subscriptions. customers_subscriptions_id == db.customers_subscriptions.id), db.auth_user.on( db.customers_subscriptions.auth_customer_id == db.auth_user.id) ] query = (db.invoices_customers.id == None) rows = db(query).select(db.invoices.id, db.auth_user.id, db.invoices_customers.id, left=left) for row in rows: invoice = Invoice(row.invoices.id) invoice.link_to_customer(row.auth_user.id)
def set_status(self, status): """ :param status: Set status of class booking :return: """ from .os_cache_manager import OsCacheManager self.row.BookingStatus = status self.row.update_record() if status == 'cancelled': ## # Change invoice status to cancelled ## from openstudio.os_invoice import Invoice db = current.db query = (db.invoices_items_classes_attendance.classes_attendance_id == self.id) rows = db(query).select(db.invoices_items_classes_attendance.ALL) for row in rows: item = db.invoices_items(row.invoices_items_id) invoice = Invoice(item.invoices_id) invoice.set_status('cancelled') # Clear api cache to refresh available spaces ocm = OsCacheManager() ocm.clear_classschedule_api()
def sell_to_customer_create_invoice(self, cmID): """ Add an invoice after adding a membership :param cmID: db.customers_memberships.id :return: db.invoices.id """ from openstudio.os_customer_membership import CustomerMembership from openstudio.os_invoice import Invoice db = current.db T = current.T cm = CustomerMembership(cmID) # Check if price exists and > 0: if self.get_price_on_date(cm.row.Startdate): period_start = cm.row.Startdate period_end = cm.get_period_enddate(cm.row.Startdate) igpt = db.invoices_groups_product_types(ProductType='membership') iID = db.invoices.insert( invoices_groups_id=igpt.invoices_groups_id, Description=cm.get_name(), MembershipPeriodStart=period_start, MembershipPeriodEnd=period_end, Status='sent') invoice = Invoice(iID) invoice.link_to_customer(cm.row.auth_customer_id) invoice.item_add_membership(cmID, period_start, period_end) return iID else: return None
def _list_invoices_get_balance(self, row): """ Retuns the balance for an invoice """ from os_invoice import Invoice iID = row.invoices.id invoice = Invoice(iID) return invoice.get_balance(formatted=True)
def add_get_form(self, cuID, csID=None, cmID=None, full_width=True): """ Returns add form for an invoice """ from os_customer import Customer from os_invoice import Invoice self._add_get_form_permissions_check() db = current.db T = current.T customer = Customer(cuID) self._add_get_form_set_default_values_customer(customer) self._add_get_form_enable_minimal_fields() if csID: self._add_get_form_enable_subscription_fields(csID) if cmID: self._add_get_form_enable_membership_fields(cmID) form = SQLFORM(db.invoices, formstyle='bootstrap3_stacked') elements = form.elements('input, select, textarea') for element in elements: element['_form'] = "invoice_add" form_element = form.element('form') form['_id'] = 'invoice_add' if form.process().accepted: iID = form.vars.id invoice = Invoice( iID ) # This sets due date and Invoice (calls invoice.on_create() # invoice.link_to_customer(cuID) self._add_reset_list_status_filter() if csID: invoice.link_to_customer_subscription(csID) invoice.item_add_subscription(form.vars.SubscriptionYear, form.vars.SubscriptionMonth) if cmID: invoice.item_add_membership(cmID, form.vars.MembershipPeriodStart, form.vars.MembershipPeriodEnd) redirect(URL('invoices', 'edit', vars={'iID': iID})) # So the grids display the fields normally for field in db.invoices: field.readable = True return form
def webhook_invoice_paid(iID, payment_amount, payment_date, mollie_payment_id): """ :param iID: db.invoices.id :return: None """ invoice = Invoice(iID) ipID = invoice.payment_add( payment_amount, payment_date, payment_methods_id=100, # Static id for Mollie payments mollie_payment_id=mollie_payment_id)
def webhook_invoice_paid(iID, payment_amount, payment_date, mollie_payment_id): """ :param iID: db.invoices.id :return: None """ query = (db.invoices_payments.mollie_payment_id == mollie_payment_id) if not db(query).count(): # Don't process a payment twice invoice = Invoice(iID) ipID = invoice.payment_add( payment_amount, payment_date, payment_methods_id=100, # Static id for Mollie payments mollie_payment_id=mollie_payment_id)
def webhook_invoice_refund(iID, amount, date, mollie_payment_id, mollie_refund_id, note): """ Actually add refund invoice payment This function is separate for testability """ from openstudio.os_invoice import Invoice invoice = Invoice(iID) ipID = invoice.payment_add( amount, date, payment_methods_id=100, # Static id for Mollie payments mollie_payment_id=mollie_payment_id, mollie_refund_id=mollie_refund_id, note=note)
def upgrade_to_20183(): """ Upgrade operations to 2018.3 """ ## # Set invoice customer data ## query = (db.invoices_customers.id > 0) rows = db(query).select(db.invoices_customers.ALL) for row in rows: invoice = Invoice(row.invoices_id) invoice.set_customer_info(row.auth_customer_id) ## # clear cache ## cache.ram.clear(regex='.*')
def webhook_invoice_chargeback(iID, chargeback_amount, chargeback_date, mollie_payment_id, chargeback_id, chargeback_details): """ Chargebacks happen when a direct debit payment fails due to insufficient funds in the customers' bank account :return: """ invoice = Invoice(iID) ipID = invoice.payment_add( chargeback_amount, chargeback_date, payment_methods_id=100, # Static id for Mollie payments mollie_payment_id=mollie_payment_id, note="Mollie Chargeback (%s) - %s" % (chargeback_id, chargeback_details)) # Notify customer of chargeback cuID = invoice.get_linked_customer_id() os_mail = OsMail() msgID = os_mail.render_email_template('payment_recurring_failed') os_mail.send(msgID, cuID)
def invoice_pay(): """ Link to mollie payment page from invoice payment """ from openstudio.os_customer import Customer #response.title = T("Pay invoice") iID = request.vars['iID'] invoice = Invoice(iID) invoice_amounts = invoice.get_amounts() if not invoice.get_linked_customer_id() == auth.user.id: return 'Not authorized' mollie = Mollie.API.Client() mollie_api_key = get_sys_property('mollie_website_profile') mollie.setApiKey(mollie_api_key) description = invoice.invoice.Description + ' - ' + invoice.invoice.InvoiceID recurring_type = None mollie_customer_id = None # Subscription invoice? if invoice.get_linked_customer_subscription_id(): # subscription invoice # customer = Customer(auth.user.id) # mollie_customer_id = customer.row.mollie_customer_id # check if we have a mollie customer id os_customer = Customer(auth.user.id) if os_customer.row.mollie_customer_id: # yep mollie_customer_id = os_customer.row.mollie_customer_id try: mollie_customer = mollie.customers.get(mollie_customer_id) # print "we've got one!" # print mollie_customer # print mollie.customers.all() except Exception as e: # print e.__class__.__name__ # print str(e) # print 'customer id invalid, create new customer' if 'The customer id is invalid' in str(e): create_mollie_customer(auth.user.id, mollie) os_customer = Customer(auth.user.id) # refresh else: create_mollie_customer(auth.user.id, mollie) os_customer = Customer(auth.user.id) # refresh mandates = os_customer.get_mollie_mandates() # set default recurring type, change to recurring if a valid mandate is found. recurring_type = 'first' if mandates['count'] > 0: # background payment valid_mandate = False for mandate in mandates: if mandate['status'] == 'valid': valid_mandate = True break if valid_mandate: # Do a normal payment, probably an automatic payment failed somewhere in the process # and customer should pay manually now recurring_type = None # Do a regular payment or first recurring payment try: webhook_url = 'https://' + request.env.http_host + '/mollie/webhook' payment = mollie.payments.create({ 'amount': invoice_amounts.TotalPriceVAT, 'description': description, 'recurringType': recurring_type, 'customerId': mollie_customer_id, 'redirectUrl': 'https://' + request.env.http_host + '/shop/complete?iID=' + unicode(iID), 'webhookUrl': webhook_url, 'metadata': { 'invoice_id': invoice.invoice.id, 'customers_orders_id': 'invoice' # This lets the webhook function know it's dealing with an invoice } }) db.invoices_mollie_payment_ids.insert( invoices_id=iID, mollie_payment_id=payment['id'], RecurringType=recurring_type, WebhookURL=webhook_url ) # Send the customer off to complete the payment. redirect(payment.getPaymentUrl()) except Mollie.API.Error as e: return 'API call failed: ' + e.message
def task_mollie_subscription_invoices_and_payments(): """ Create subscription invoices for subscriptions with payment method 100 Collect payment for these invoices """ def send_mail_failed(cuID): """ When a recurring payment fails, mail customer with request to pay manually """ os_mail = OsMail() msgID = os_mail.render_email_template('payment_recurring_failed') os_mail.send_and_archive(msgID, cuID) from openstudio.os_customer import Customer # hostname sys_hostname = get_sys_property('sys_hostname') # set up Mollie mollie = Client() mollie_api_key = get_sys_property('mollie_website_profile') mollie.set_api_key(mollie_api_key) # set dates today = datetime.date.today() firstdaythismonth = datetime.date(today.year, today.month, 1) lastdaythismonth = get_last_day_month(firstdaythismonth) # call some function to do stuff # find all active subscriptions with payment method 100 (Mollie) query = (db.customers_subscriptions.payment_methods_id == 100) & \ (db.customers_subscriptions.Startdate <= lastdaythismonth) & \ ((db.customers_subscriptions.Enddate >= firstdaythismonth) | (db.customers_subscriptions.Enddate == None)) rows = db(query).select(db.customers_subscriptions.ALL) success = 0 failed = 0 # create invoices for i, row in enumerate(rows): cs = CustomerSubscription(row.id) # This function returns the invoice id if it already exists iID = cs.create_invoice_for_month(TODAY_LOCAL.year, TODAY_LOCAL.month) #print 'invoice created' #print iID # Do we have an invoice? if not iID: continue invoice = Invoice(iID) # Only do something if the invoice status is sent if not invoice.invoice.Status == 'sent': continue # We're good, continue processing invoice_amounts = invoice.get_amounts() #print invoice.invoice.InvoiceID description = invoice.invoice.Description + ' - ' + invoice.invoice.InvoiceID db.commit() #create recurring payments using mandates #subscription invoice customer = Customer(row.auth_customer_id) mollie_customer_id = customer.row.mollie_customer_id mandates = customer.get_mollie_mandates() valid_mandate = False # set default recurring type, change to recurring if a valid mandate is found. if mandates['count'] > 0: # background payment for mandate in mandates['_embedded']['mandates']: if mandate['status'] == 'valid': valid_mandate = True break if valid_mandate: # Create recurring payment try: webhook_url = URL('mollie', 'webhook', scheme='https', host=sys_hostname) payment = mollie.payments.create({ 'amount': { 'currency': CURRENCY, 'value': str(invoice_amounts.TotalPriceVAT) }, 'customerId': mollie_customer_id, 'sequenceType': 'recurring', # important 'description': description, 'webhookUrl': webhook_url, 'metadata': { 'invoice_id': invoice.invoice.id, 'customers_orders_id': 'invoice' # This lets the webhook function know it's dealing with an invoice } }) # link invoice to mollie_payment_id db.invoices_mollie_payment_ids.insert( invoices_id=invoice.invoice.id, mollie_payment_id=payment['id'], RecurringType='recurring', WebhookURL=webhook_url) success += 1 except MollieError as e: print(e) # send mail to ask customer to pay manually send_mail_failed(cs.auth_customer_id) failed += 1 # return error # return 'API call failed: ' + e.message else: # send mail to ask customer to pay manually send_mail_failed(cs.auth_customer_id) failed += 1 # For scheduled tasks, db has to be committed manually db.commit() return T("Payments collected") + ': ' + str(success) + '<br>' + \ T("Payments failed to collect") + ': ' + str(failed)
def batch_generate_teachers_invoices(self, year, month): """ :return: Int - number of generated invoices """ from general_helpers import NRtoMonth from os_teacher import Teacher from os_teachers import Teachers from openstudio.os_invoice import Invoice import datetime db = current.db T = current.T # Get list of teachers teachers = Teachers() result = teachers.get_teachers_list_classes_in_month(year, month) #print teacher_classes invoices_created = 0 for teID in result: teacher_classes = result[teID]['classes'] number_of_classes = result[teID]['classes_count'] if not number_of_classes: continue teacher = Teacher(teID) default_rate = teacher.get_payment_fixed_rate_default() if not default_rate: continue # No default rate, not enough data to create invoice # Check if we have an invoice already, if so, skip query = (db.invoices_customers.auth_customer_id == teID) & \ (db.invoices.TeacherPayment == True) & \ (db.invoices.TeacherPaymentMonth == month) & \ (db.invoices.TeacherPaymentYear == year) & \ (db.invoices_customers.invoices_id == db.invoices.id) if db(query).count(): continue # Create invoice invoices_created += 1 igpt = db.invoices_groups_product_types( ProductType='teacher_payments') iID = db.invoices.insert( invoices_groups_id=igpt.invoices_groups_id, TeacherPayment=True, TeacherPaymentMonth=month, TeacherPaymentYear=year, Description=T('Classes') + ' ' + NRtoMonth(month) + ' ' + unicode(year), Status='sent') invoice = Invoice(iID) invoice.link_to_customer(teID) for date, rows in sorted(teacher_classes.iteritems()): prev_class_end = datetime.datetime(date.year, date.month, date.day, 0, 0) for row in rows: ## Check not adding travel allowance for consecutive classes class_start = datetime.datetime( date.year, date.month, date.day, row.classes.Starttime.hour, row.classes.Starttime.minute) consecutive = class_start <= ( prev_class_end + datetime.timedelta(minutes=30)) if not consecutive: invoice.item_add_teacher_class_credit_travel_allowance( row.classes.id, date) prev_class_end = datetime.datetime( date.year, date.month, date.day, row.classes.Endtime.hour, row.classes.Endtime.minute) # Add class invoice.item_add_teacher_class_credit_payment( row.classes.id, date) invoice.set_amounts() return invoices_created