示例#1
0
def cancel_charge(customer_id, order_number, charge_id):
    """Cancels a charge so adjustments can be made to the order. Rolls back the next charge date of the subscription order.

    Args:
        customer_id:
        order_number:
        charge_id:

    Returns:
        None
    """
    to_put = list()
    now_ = now()
    charge, order, customer = db.get([Charge.create_key(charge_id, order_number, customer_id),
                                      Order.create_key(customer_id, order_number),
                                      Customer.create_key(customer_id)])
    charge.date_cancelled = now_
    charge.status = Charge.STATUS_CANCELLED
    to_put.append(charge)
    order_items = list(OrderItem.list_by_order(order.key()))
    if order.is_subscription_order:
        months = 0
        for item in order_items:
            product = item.product
            if product.is_subscription and product.price > 0:
                months += item.count
            if not product.is_subscription and product.extra_subscription_months > 0:
                months += product.extra_subscription_months

        if months > 0:
            next_charge_datetime = datetime.datetime.utcfromtimestamp(now()) - relativedelta(months=months)
            order.next_charge_date = get_epoch_from_datetime(next_charge_datetime)
        else:
            order.next_charge_date = Order.default_next_charge_date()
    else:
        extra_months = 0
        for item in order_items:
            product = item.product
            if not product.is_subscription and product.extra_subscription_months > 0:
                extra_months += product.extra_subscription_months

        if extra_months > 0:
            sub_order = Order.get_by_order_number(customer_id, customer.subscription_order_number)
            next_charge_datetime = datetime.datetime.utcfromtimestamp(sub_order.next_charge_date) - relativedelta(
                months=extra_months)
            sub_order.next_charge_date = get_epoch_from_datetime(next_charge_datetime)
            to_put.append(sub_order)
    db.put(to_put)
    def trans():
        charge = None
        expired_subscription, customer = db.get([ExpiredSubscription.create_key(customer_id),
                                                       Customer.create_key(customer_id)])
        expired_subscription.status = status
        expired_subscription.status_updated_timestamp = now()

        if status == ExpiredSubscription.STATUS_WILL_LINK_CREDIT_CARD:
            # Create a task for regiomanager to check if the customer has linked his credit card after two weeks.
            # the ExpiredSubscription object from this customer will be cleaned up in recurrentbilling the day after he has linked it.
            to_put.append(expired_subscription)
            team, prospect = db.get([RegioManagerTeam.create_key(customer.team_id),
                                     Prospect.create_key(customer.prospect_id)])
            execution_time = now() + DAY * 14
            date_string = datetime.datetime.utcfromtimestamp(execution_time).strftime(u'%A %d %b %Y')
            comment = u'Check if the customer has linked his creditcard (for automatic subscription renewal).' \
                      u' If he hasn\'t linked it before %s, contact him again.' % date_string
            task = create_task(current_user.email(), prospect, team.support_manager, execution_time,
                               ShopTask.TYPE_CHECK_CREDIT_CARD, prospect.app_id, comment=comment)
            to_put.append(task)

        elif status == ExpiredSubscription.STATUS_EXTEND_SUBSCRIPTION:
            # Creates a new charge using the customer his subscription order.
            subscription_order, team = db.get([Order.create_key(customer.id, customer.subscription_order_number),
                                               RegioManagerTeam.create_key(customer.team_id)])
            extension_order_item_keys = list()
            order_items = list(OrderItem.list_by_order(subscription_order.key()))
            products_to_get = list()
            for item in order_items:
                products_to_get.append(Product.create_key(item.product_code))
            products = {p.code: p for p in Product.get(products_to_get)}
            # extend per year
            months = 12
            total_amount = 0
            for item in order_items:
                product = products[item.product_code]
                if product.is_subscription and item.price > 0:
                    total_amount += months * item.price
                elif not product.is_subscription and (product.is_subscription_discount or product.extra_subscription_months > 0):
                    total_amount += months * item.price
                elif product.is_subscription_extension:
                    total_amount += months * item.price
                    extension_order_item_keys.append(item.key())

            if total_amount <= 0:
                raise BusinessException('The created charge has a negative amount (%d)' % total_amount)
            next_charge_datetime = datetime.datetime.utcfromtimestamp(now()) + relativedelta(months=months)
            subscription_order.next_charge_date = get_epoch_from_datetime(next_charge_datetime)
            to_put.append(subscription_order)

            # reconnect all previously connected friends if the service was disabled in the past
            if customer.service_disabled_at != 0:
                deferred.defer(set_service_enabled, customer.id, _transactional=True)

            vat_pct = get_vat_pct(customer, team)
            charge = Charge(parent=subscription_order)
            charge.date = now()
            charge.type = Charge.TYPE_SUBSCRIPTION_EXTENSION
            charge.subscription_extension_length = months
            charge.subscription_extension_order_item_keys = extension_order_item_keys
            charge.amount = total_amount
            charge.vat_pct = vat_pct
            charge.vat = int(total_amount * vat_pct / 100)
            charge.total_amount = charge.amount + charge.vat
            charge.currency_code = team.legal_entity.currency_code
            to_put.append(charge)
            to_delete.append(expired_subscription)

        db.put(to_put)
        if to_delete:
            db.delete(to_delete)

        return charge
示例#3
0
def create_reseller_invoice_for_legal_entity(legal_entity,
                                             start_date,
                                             end_date,
                                             do_send_email=True):
    """
    Args:
        legal_entity (LegalEntity) 
        start_date (long)
        end_date (long)
        do_send_email (bool)
    """
    if legal_entity.is_mobicage:
        # To avoid a composite index we don't filter on is_mobicage
        return
    solution_server_settings = get_solution_server_settings()
    from_email = solution_server_settings.shop_no_reply_email
    to_emails = solution_server_settings.shop_payment_admin_emails
    mobicage_legal_entity = get_mobicage_legal_entity()
    logging.info(
        'Exporting reseller invoices for legal entity %s(id %d) from %s(%s) to %s(%s)',
        legal_entity.name, legal_entity.id, start_date, time.ctime(start_date),
        end_date, time.ctime(end_date))
    invoices = list(Invoice.all().filter(
        'legal_entity_id',
        legal_entity.id).filter('paid_timestamp >', start_date).filter(
            'paid_timestamp <', end_date).filter('paid', True).filter(
                'payment_type IN',
                (Invoice.PAYMENT_MANUAL, Invoice.PAYMENT_MANUAL_AFTER)))
    start_time = time.strftime('%m/%d/%Y', time.gmtime(int(start_date)))
    end_time = time.strftime('%m/%d/%Y', time.gmtime(int(end_date)))
    if not invoices:
        message = 'No new invoices for reseller %s for period %s - %s' % (
            legal_entity.name, start_time, end_time)
        logging.info(message)
        if do_send_email:
            send_mail(from_email, to_emails, message, message)
        return
    items_per_customer = {}
    customers_to_get = set()
    products = {
        p.code: p
        for p in Product.list_by_legal_entity(legal_entity.id)
    }
    for invoice in invoices:
        # get all subscription order items
        order_items = list(OrderItem.list_by_order(invoice.order_key))
        for item in reversed(order_items):
            product = products[item.product_code]
            # We're only interested in subscription items
            if product.is_subscription or product.is_subscription_extension or product.is_subscription_discount:
                if invoice.customer_id not in items_per_customer:
                    items_per_customer[invoice.customer_id] = []
                    customers_to_get.add(
                        Customer.create_key(invoice.customer_id))
                items_per_customer[invoice.customer_id].append(item)
            else:
                order_items.remove(item)
    if not customers_to_get:
        message = 'No new invoices containing subscriptions for reseller %s for period %s - %s' % (
            legal_entity.name, start_time, end_time)
        logging.info(message)
        if do_send_email:
            send_mail(from_email, to_emails, message, message)
        return
    customers = {c.id: c for c in db.get(customers_to_get)}
    product_totals = {}
    for customer_id in items_per_customer:
        items = items_per_customer[customer_id]
        for item in items:
            if item.product_code not in product_totals:
                product_totals[item.product_code] = {
                    'count': 0,
                    'price': int(item.price * legal_entity.revenue_percent)
                }
            product_totals[item.product_code]['count'] += item.count
    total_amount = 0
    for product in product_totals:
        p = product_totals[product]
        price = p['count'] * p['price']
        p['total_price'] = format_currency(
            price / 100.,
            legal_entity.currency_code,
            locale=mobicage_legal_entity.country_code)
        total_amount += price
    total_amount_formatted = format_currency(
        total_amount / 100.,
        legal_entity.currency_code,
        locale=mobicage_legal_entity.country_code)
    vat_amount = total_amount / mobicage_legal_entity.vat_percent if mobicage_legal_entity.country_code == legal_entity.country_code else 0
    vat_amount_formatted = format_currency(
        vat_amount / 100.,
        legal_entity.currency_code,
        locale=mobicage_legal_entity.country_code)
    from_date = format_datetime(datetime.utcfromtimestamp(start_date),
                                locale=SHOP_DEFAULT_LANGUAGE,
                                format='dd/MM/yyyy HH:mm')
    until_date = format_datetime(datetime.utcfromtimestamp(end_date),
                                 locale=SHOP_DEFAULT_LANGUAGE,
                                 format='dd/MM/yyyy HH:mm')

    solution_server_settings = get_solution_server_settings()
    template_variables = {
        'products':
        products,
        'customers':
        customers,
        'invoices':
        invoices,
        'items_per_customer':
        items_per_customer,
        'product_totals':
        product_totals.items(),
        'mobicage_legal_entity':
        mobicage_legal_entity,
        'legal_entity':
        legal_entity,
        'language':
        SHOP_DEFAULT_LANGUAGE,
        'from_date':
        from_date,
        'until_date':
        until_date,
        'revenue_percent':
        legal_entity.revenue_percent,
        'vat_amount_formatted':
        vat_amount_formatted,
        'total_amount_formatted':
        total_amount_formatted,
        'logo_path':
        '../html/img/osa_white_en_250.jpg',
        'tos_link':
        '<a href="%s">%s</a>' %
        (solution_server_settings.shop_privacy_policy_url,
         solution_server_settings.shop_privacy_policy_url)
    }
    source_html = SHOP_JINJA_ENVIRONMENT.get_template(
        'invoice/reseller_invoice.html').render(template_variables)
    output_stream = StringIO()
    pisa.CreatePDF(src=source_html,
                   dest=output_stream,
                   path='%s/invoice' % SHOP_TEMPLATES_FOLDER)
    invoice_pdf_contents = output_stream.getvalue()
    output_stream.close()
    # Create an order, order items, charge and invoice.
    _now = now()
    customer = legal_entity.get_or_create_customer()
    mobicage_team = RegioManagerTeam.get_mobicage()

    def trans():
        to_put = list()
        order_number = OrderNumber.next(mobicage_legal_entity)
        order_key = db.Key.from_path(Order.kind(),
                                     order_number,
                                     parent=customer.key())
        order = Order(key=order_key)
        order.contact_id = legal_entity.contact_id
        order.date = _now
        order.vat_pct = mobicage_legal_entity.vat_percent if legal_entity.country_code == mobicage_legal_entity.country_code else 0
        order.amount = int(round(total_amount))
        order.vat = int(round(vat_amount))
        order.total_amount = int(round(total_amount + vat_amount))
        order.is_subscription_order = False
        order.is_subscription_extension_order = False
        order.team_id = mobicage_team.id
        order.manager = customer.manager
        order.status = Order.STATUS_SIGNED
        to_put.append(order)

        for i, (product_code, item) in enumerate(product_totals.iteritems()):
            order_item = OrderItem(parent=order_key)
            order_item.number = i + 1
            order_item.comment = products[product_code].default_comment(
                SHOP_DEFAULT_LANGUAGE)
            order_item.product_code = product_code
            order_item.count = item['count']
            order_item.price = item['price']
            to_put.append(order_item)

        charge_key = Charge.create_key(allocate_id(Charge), order_number,
                                       customer.id)
        charge = Charge(key=charge_key)
        charge.date = _now
        charge.type = Charge.TYPE_ORDER_DELIVERY
        charge.amount = order.amount
        charge.vat_pct = order.vat_pct
        charge.vat = order.vat
        charge.total_amount = order.total_amount
        charge.manager = order.manager
        charge.team_id = order.team_id
        charge.charge_number = ChargeNumber.next(mobicage_legal_entity)
        charge.currency_code = legal_entity.currency_code
        to_put.append(charge)

        invoice_number = InvoiceNumber.next(mobicage_legal_entity)
        invoice = Invoice(key_name=invoice_number,
                          parent=charge,
                          amount=charge.amount,
                          vat_pct=charge.vat_pct,
                          vat=charge.vat,
                          total_amount=charge.total_amount,
                          currency_code=legal_entity.currency_code,
                          date=_now,
                          payment_type=Invoice.PAYMENT_MANUAL_AFTER,
                          operator=charge.manager,
                          paid=False,
                          legal_entity_id=mobicage_legal_entity.id,
                          pdf=invoice_pdf_contents)
        charge.invoice_number = invoice_number
        to_put.append(invoice)
        put_and_invalidate_cache(*to_put)
        return order, charge, invoice

    order, charge, invoice = run_in_xg_transaction(trans)

    if do_send_email:
        serving_url = '%s/internal/shop/invoice/pdf?customer_id=%d&order_number=%s&charge_id=%d&invoice_number=%s' % (
            get_server_settings().baseUrl, customer.id, order.order_number,
            charge.id, invoice.invoice_number)
        subject = 'New reseller invoice for %s, %s - %s' % (
            legal_entity.name, start_time, end_time)
        body_text = 'A new invoice is available for reseller %s for period %s to %s here: %s' % (
            legal_entity.name, start_time, end_time, serving_url)

        send_mail(from_email, to_emails, subject, body_text)
示例#4
0
    def trans():
        customer_id = order_key.parent().id()
        order, customer = db.get([order_key, Customer.create_key(customer_id)])
        if not order.next_charge_date:
            logging.warning(
                'Not creating recurrent charge for order %s (%s: %s) because no next charge date is set',
                order.order_number, customer_id, customer.name)
            return None
        elif order.next_charge_date > today:
            # Scenario: this job fails today, tomorrow this job runs again and fails again
            # -> 2 jobs for the same order would create 2 charges when the bug is fixed
            logging.warning(
                'This order has already been charged this month, skipping... %s (%s: %s)',
                order.order_number, customer_id, customer.name)
            return None
        elif customer.subscription_cancel_pending_date:
            logging.info('Disabling service from customer %s (%d)',
                         customer.name, customer.id)
            try:
                cancel_order(customer, order.order_number)
            except OrderAlreadyCanceledException as exception:
                logging.info('Order %s already canceled, continuing...',
                             exception.order.order_number)

            set_service_disabled(customer, Customer.DISABLED_OTHER)
            cleanup_expired_subscription(customer)
            return None

        logging.info("Creating recurrent charge for order %s (%s: %s)",
                     order.order_number, customer_id, customer.name)
        subscription_extension_orders = list(
            Order.all().ancestor(customer).filter(
                "next_charge_date <",
                today).filter("is_subscription_order =", False).filter(
                    'is_subscription_extension_order =',
                    True).filter("status =",
                                 Order.STATUS_SIGNED))  # type: list[Order]
        subscription_extension_order_keys = [
            o.key() for o in subscription_extension_orders
        ]
        order_item_qry = OrderItem.all().ancestor(
            customer if subscription_extension_order_keys else order)

        subscription_extension_order_item_keys = []
        total_amount = 0
        subscription_length = 0
        current_date = datetime.datetime.utcnow()
        to_put = []
        for order_item in order_item_qry:  # type: OrderItem
            product = products[order_item.product_code]
            if order_item.order_number == order.order_number:
                if product.is_subscription:
                    subscription_length = order_item.count
                if product.is_subscription or product.is_subscription_discount or product.is_subscription_extension:
                    if product.charge_interval != 1:
                        last_charge_date = datetime.datetime.utcfromtimestamp(
                            order_item.last_charge_timestamp)
                        new_charge_date = last_charge_date + relativedelta(
                            months=product.charge_interval)
                        if new_charge_date < current_date:
                            logging.debug(
                                'new_charge_date %s < current_date %s, adding %s to total_amount',
                                new_charge_date, current_date,
                                order_item.price)
                            total_amount += order_item.price
                            order_item.last_charge_timestamp = now()
                            to_put.append(order_item)
                    else:
                        total_amount += order_item.price

            elif order_item.parent().key(
            ) in subscription_extension_order_keys:
                if product.is_subscription_extension:
                    total_amount += order_item.price
                    subscription_extension_order_item_keys.append(
                        order_item.key())
        if total_amount == 0:
            order.next_charge_date = Order.default_next_charge_date()
            order.put()
            logging.info(
                "Skipping, cannot calculate recurrent charge of 0 euros for order %s (%s: %s)",
                order.order_number, customer_id, customer.name)
            return None

        if subscription_length == 0:
            raise Exception('subscription_length is 0')

        if not (customer.stripe_id and
                customer.stripe_credit_card_id) and subscription_length != 1:
            logging.debug(
                'Tried to bill customer, but no credit card info was found')
            audit_log(
                customer.id,
                'Tried to bill customer, but no credit card info was found')
            # Log the customer as expired. If this has not been done before.
            expired_subscription_key = ExpiredSubscription.create_key(
                customer_id)
            if not ExpiredSubscription.get(expired_subscription_key):
                to_put.append(
                    ExpiredSubscription(
                        key=expired_subscription_key,
                        expiration_timestamp=order.next_charge_date))
                # Create a task for the support manager
                assignee = customer.manager and customer.manager.email()
                if customer.team_id is not None:
                    team = RegioManagerTeam.get_by_id(customer.team_id)
                    if team.support_manager:
                        assignee = team.support_manager
                if assignee:
                    if customer.prospect_id:
                        prospect = Prospect.get(
                            Prospect.create_key(customer.prospect_id))
                    else:
                        # We can only create tasks for prospects. So we must create a prospect if there was none.
                        prospect = create_prospect_from_customer(customer)
                        customer.prospect_id = prospect.id
                        to_put.append(customer)
                        to_put.append(prospect)
                    to_put.append(
                        create_task(
                            created_by=None,
                            prospect_or_key=prospect,
                            assignee=assignee,
                            execution_time=today + 11 * 3600,
                            task_type=ShopTask.TYPE_SUPPORT_NEEDED,
                            app_id=prospect.app_id,
                            status=ShopTask.STATUS_NEW,
                            comment=
                            u"Customer needs to be contacted for subscription renewal",
                            notify_by_email=True))
                put_and_invalidate_cache(*to_put)
            return None
        else:
            cleanup_expired_subscription(customer)

        @db.non_transactional  # prevent contention on entity group RegioManagerTeam
        def get_currency_code():
            return customer.team.legal_entity.currency_code

        charge = Charge(parent=order_key)
        charge.date = now()
        charge.type = Charge.TYPE_RECURRING_SUBSCRIPTION
        charge.subscription_extension_length = 1
        charge.subscription_extension_order_item_keys = subscription_extension_order_item_keys
        charge.currency_code = get_currency_code()
        charge.team_id = customer.team_id
        charge.amount = total_amount
        charge.vat_pct = order.vat_pct
        charge.vat = int(total_amount * order.vat_pct / 100)
        charge.total_amount = charge.amount + charge.vat
        to_put.append(charge)

        next_charge_datetime = datetime.datetime.utcfromtimestamp(
            order.next_charge_date) + relativedelta(months=1)
        next_charge_date_int = int(
            (next_charge_datetime -
             datetime.datetime.utcfromtimestamp(0)).total_seconds())
        order.next_charge_date = next_charge_date_int
        to_put.append(order)
        for extension_order in subscription_extension_orders:
            extension_order.next_charge_date = next_charge_date_int
            to_put.append(extension_order)

        put_and_invalidate_cache(*to_put)
        return charge