コード例 #1
0
 def __init__(self, order):
     self._order = order
     self._price_resolver = PriceResolver()
     self.charging_processors = {
         'initial': self._process_initial_charge,
         'recurring': self._process_renovation_charge,
         'usage': self._process_use_charge
     }
     self.end_processors = {
         'initial': self._end_initial_charge,
         'recurring': self._end_renovation_charge,
         'usage': self._end_use_charge
     }
 def __init__(self, order):
     self._order = order
     self._price_resolver = PriceResolver()
     self.charging_processors = {
         'initial': self._process_initial_charge,
         'recurring': self._process_renovation_charge,
         'usage': self._process_use_charge
     }
     self.end_processors = {
         'initial': self._end_initial_charge,
         'recurring': self._end_renovation_charge,
         'usage': self._end_use_charge
     }
コード例 #3
0
ファイル: charging_engine.py プロジェクト: huygun/wstore
    def resolve_charging(self, new_purchase=False, sdr=False):

        # Check if there is a new purchase
        if new_purchase:
            # Create the contract
            self._create_purchase_contract()
            charge = False
            related_model = {}

            # Check if there are price parts different from pay per use
            if 'single_payment' in self._price_model:
                charge = True
                related_model['single_payment'] = self._price_model['single_payment']

            if 'subscription' in self._price_model:
                charge = True
                related_model['subscription'] = self._price_model['subscription']

            price = 0
            if charge:
                # Call the price resolver
                resolver = PriceResolver()
                price = resolver.resolve_price(related_model)

                # Check user expenditure limits and accumulated balance
                self._check_expenditure_limits(price)

                # Make the charge
                redirect_url = self._charge_client(price, 'initial charge', self._price_model['general_currency'])
            else:
                # If it is not necessary to charge the customer the state is set to paid
                self._purchase.state = 'paid'

            if self._purchase.state == 'paid':
                self.end_charging(price, 'initial charge', related_model)
            else:
                price = self._fix_price(price)
                self._purchase.contract.pending_payment = {
                    'price': price,
                    'concept': 'initial charge',
                    'related_model': related_model
                }
                self._purchase.contract.save()
                return redirect_url

        else:
            self._price_model = self._purchase.contract.pricing_model
            self._purchase.state = 'pending'
            self._purchase.save()

            # If not SDR received means that the call is a renovation
            if not sdr:
                # Determine the price parts to renovate
                if not 'subscription' in self._price_model:
                    raise Exception('No subscriptions to renovate')

                related_model = {
                    'subscription': []
                }

                now = datetime.now()
                unmodified = []

                for s in self._price_model['subscription']:
                    renovation_date = s['renovation_date']

                    if renovation_date < now:
                        related_model['subscription'].append(s)
                    else:
                        unmodified.append(s)

                accounting_info = None
                # If pending SDR documents resolve the use charging
                if len(self._purchase.contract.pending_sdrs) > 0:
                    related_model['pay_per_use'] = self._price_model['pay_per_use']
                    accounting_info = []
                    accounting_info.extend(self._purchase.contract.pending_sdrs)

                # If deductions have been included resolve the discount
                if 'deductions' in self._price_model and len(self._price_model['deductions']) > 0:
                    related_model['deductions'] = self._price_model['deductions']

                resolver = PriceResolver()
                price = resolver.resolve_price(related_model, accounting_info)

                # Deductions can make the price 0
                if price > 0:
                    # If not use made, check expenditure limits and accumulated balance
                    if not accounting_info:
                        self._check_expenditure_limits(price)

                    redirect_url = self._charge_client(price, 'Renovation', self._price_model['general_currency'])

                if len(unmodified) > 0:
                    related_model['unmodified'] = unmodified

                # Check if applied accounting info is needed to finish the purchase
                applied_accounting = None
                if accounting_info:
                    applied_accounting = resolver.get_applied_sdr()

                if self._purchase.state == 'paid':
                    self.end_charging(price, 'Renovation', related_model, applied_accounting)
                else:
                    price = self._fix_price(price)
                    pending_payment = {
                        'price': price,
                        'concept': 'Renovation',
                        'related_model': related_model
                    }

                    # If some accounting has been used include it to be saved
                    if accounting_info:
                        pending_payment['accounting'] = applied_accounting

                    self._purchase.contract.pending_payment    
                    self._purchase.contract.save()
                    return redirect_url

            # If sdr is true means that the call is a request for charging the use
            # made of a service.
            else:
                # Aggregate the calculated charges
                pending_sdrs = []
                pending_sdrs.extend(self._purchase.contract.pending_sdrs)

                if len(pending_sdrs) == 0:
                    raise Exception('No SDRs to charge')

                related_model = {
                    'pay_per_use': self._price_model['pay_per_use']
                }

                if 'deductions' in self._price_model and len(self._price_model['deductions']) > 0:
                    related_model['deductions'] = self._price_model['deductions']

                resolver = PriceResolver()
                price = resolver.resolve_price(related_model, pending_sdrs)
                # Charge the client

                # Deductions can make the price 0
                if price > 0:
                    redirect_url = self._charge_client(price, 'Pay per use', self._price_model['general_currency'])

                applied_accounting = resolver.get_applied_sdr()

                if self._purchase.state == 'paid':
                    self.end_charging(price, 'pay per use', related_model, applied_accounting)
                else:
                    price = self._fix_price(price)
                    self._purchase.contract.pending_payment = {
                        'price': price,
                        'concept': 'pay per use',
                        'related_model': related_model,
                        'accounting': applied_accounting
                    }
                    self._purchase.contract.save()
                    return redirect_url
class ChargingEngine:

    def __init__(self, order):
        self._order = order
        self._price_resolver = PriceResolver()
        self.charging_processors = {
            'initial': self._process_initial_charge,
            'recurring': self._process_renovation_charge,
            'usage': self._process_use_charge
        }
        self.end_processors = {
            'initial': self._end_initial_charge,
            'recurring': self._end_renovation_charge,
            'usage': self._end_use_charge
        }

    def _initial_charge_timeout(self, order):
        ordering_client = OrderingClient()
        raw_order = ordering_client.get_order(order.order_id)

        # Setting all the items as Failed, set the whole order as failed
        # ordering_client.update_state(raw_order, 'Failed')
        ordering_client.update_items_state(raw_order, 'Failed')

        order.delete()

    def _renew_charge_timeout(self, order):
        order.state = 'paid'
        order.pending_payment = {}

        order.save()

    def _timeout_handler(self):

        db = get_database_connection()

        # Uses an atomic operation to get and set the _lock value in the purchase
        # document
        pre_value = db.wstore_order.find_one_and_update(
            {'_id': ObjectId(self._order.pk)},
            {'$set': {'_lock': True}}
        )

        # If _lock not exists or is set to false means that this function has
        # acquired the resource
        if '_lock' not in pre_value or not pre_value['_lock']:

            # Only rollback if the state is pending
            if pre_value['state'] == 'pending':
                order = Order.objects.get(pk=self._order.pk)
                timeout_processors = {
                    'initial': self._initial_charge_timeout,
                    'recurring': self._renew_charge_timeout,
                    'usage': self._renew_charge_timeout
                }
                timeout_processors[self._concept](order)

            db.wstore_order.find_one_and_update(
                {'_id': ObjectId(self._order.pk)},
                {'$set': {'_lock': False}}
            )

    def _charge_client(self, transactions):

        # Load payment client
        cln_str = settings.PAYMENT_CLIENT
        client_package, client_class = cln_str.rsplit('.', 1)

        payment_client = getattr(importlib.import_module(client_package), client_class)

        # build the payment client
        client = payment_client(self._order)

        client.start_redirection_payment(transactions)
        checkout_url = client.get_checkout_url()

        # Set timeout for PayPal transaction to 5 minutes
        t = threading.Timer(300, self._timeout_handler)
        t.start()

        return checkout_url

    def _calculate_renovation_date(self, unit):
        return datetime.utcnow() + timedelta(days=recurring_periods[unit.lower()])

    def _end_initial_charge(self, contract, transaction):
        # If a subscription part has been charged update renovation date
        related_model = transaction['related_model']
        valid_to = None
        if 'subscription' in related_model:
            updated_subscriptions = []

            for subs in contract.pricing_model['subscription']:
                up_sub = subs
                # Calculate renovation date
                valid_to = self._calculate_renovation_date(subs['unit'])
                up_sub['renovation_date'] = valid_to
                updated_subscriptions.append(up_sub)

            contract.pricing_model['subscription'] = updated_subscriptions
            related_model['subscription'] = updated_subscriptions

        # Save offerings in org profile
        self._order.owner_organization.acquired_offerings.append(contract.offering.pk)
        self._order.owner_organization.save()

        return None, valid_to

    def _end_renovation_charge(self, contract, transaction):

        related_model = transaction['related_model']
        # Process contract subscriptions
        valid_to = None
        for subs in related_model['subscription']:
            valid_to = self._calculate_renovation_date(subs['unit'])
            subs['renovation_date'] = valid_to
            updated_subscriptions = related_model['subscription']

            if 'unmodified' in related_model:
                updated_subscriptions.extend(related_model['unmodified'])

            # Save pricing model with new renovation dates
            contract.pricing_model['subscription'] = updated_subscriptions
            related_model['subscription'] = updated_subscriptions

        return None, valid_to

    def _end_use_charge(self, contract, transaction):
        # Change applied usage documents SDR Guided to Rated
        usage_client = UsageClient()

        for sdr_info in transaction['applied_accounting']:
            for sdr in sdr_info['accounting']:

                usage_client.rate_usage(
                    sdr['usage_id'],
                    unicode(contract.last_charge),
                    sdr['duty_free'],
                    sdr['price'],
                    sdr_info['model']['tax_rate'],
                    transaction['currency'],
                    contract.product_id
                )

        transaction['related_model']['accounting'] = transaction['applied_accounting']

        return contract.charges[-1].date if len(contract.charges) > 0 else self._order.date, None

    def end_charging(self, transactions, concept):
        """
        Process the second step of a payment once the customer has approved the charge
        :param transactions: List of transactions applied including the total price and the related model
        :param concept: Concept of the charge, it can be initial, renovation, or use
        """

        # Update purchase state
        if self._order.state == 'pending':
            self._order.state = 'paid'
            self._order.save()

        time_stamp = datetime.utcnow()

        self._order.pending_payment = {}

        invoice_builder = InvoiceBuilder(self._order)
        billing_client = BillingClient() if concept != 'initial' else None

        for transaction in transactions:
            contract = self._order.get_item_contract(transaction['item'])
            contract.last_charge = time_stamp

            valid_from, valid_to = self.end_processors[concept](contract, transaction)

            # If the customer has been charged create the CDR
            cdr_manager = CDRManager(self._order, contract)
            cdr_manager.generate_cdr(transaction['related_model'], time_stamp.isoformat() + 'Z')

            # Generate the invoice
            invoice_path = ''
            try:
                invoice_path = invoice_builder.generate_invoice(contract, transaction, concept)
            except:
                pass

            # Update contracts
            charge = Charge(
                date=time_stamp,
                cost=transaction['price'],
                duty_free=transaction['duty_free'],
                currency=transaction['currency'],
                concept=concept,
                invoice=invoice_path
            )
            contract.charges.append(charge)

            # Send the charge to the billing API to allow user accesses
            if concept != 'initial':
                # When the change concept is initial, the product has not been yet created in the inventory
                billing_client.create_charge(charge, contract.product_id, start_date=valid_from, end_date=valid_to)

        self._order.save()

        # TODO: Improve the rollback in case of unexpected exception
        try:
            # Send notifications if required
            handler = NotificationsHandler()
            if concept == 'initial':
                # Send customer and provider notifications
                handler.send_acquired_notification(self._order)
                for cont in self._order.contracts:
                    handler.send_provider_notification(self._order, cont)

            elif concept == 'recurring' or concept == 'usage':
                handler.send_renovation_notification(self._order, transactions)
        except:
            pass

    def _save_pending_charge(self, transactions):
        pending_payment = {
            'transactions': transactions,
            'concept': self._concept
        }

        self._order.pending_payment = pending_payment
        self._order.save()

    def _append_transaction(self, transactions, contract, related_model, accounting=None):
        # Call the price resolver
        price, duty_free = self._price_resolver.resolve_price(related_model, accounting)

        if 'alteration' in related_model and not self._price_resolver.is_altered():
            del related_model['alteration']

        transaction = {
            'price': price,
            'duty_free': duty_free,
            'description': contract.offering.description,
            'currency': contract.pricing_model['general_currency'],
            'related_model': related_model,
            'item': contract.item_id
        }

        # Get the applied accounting info is needed
        if accounting is not None:
            transaction['applied_accounting'] = self._price_resolver.get_applied_sdr()

        transactions.append(transaction)

    def _process_initial_charge(self, contracts):
        """
        Resolves initial charges, which can include single payments or the initial payment of a subscription
        :return: The URL where redirecting the customer to approve the charge
        """

        transactions = []
        redirect_url = None

        for contract in contracts:
            related_model = {}
            # Check if there are price parts different from pay per use
            if 'single_payment' in contract.pricing_model:
                related_model['single_payment'] = contract.pricing_model['single_payment']

            if 'subscription' in contract.pricing_model:
                related_model['subscription'] = contract.pricing_model['subscription']

            if 'alteration' in contract.pricing_model:
                related_model['alteration'] = contract.pricing_model['alteration']

            if len(related_model):
                self._append_transaction(transactions, contract, related_model)

        if len(transactions):
            # Make the charge
            redirect_url = self._charge_client(transactions)
            self._save_pending_charge(transactions)
        else:
            # If it is not necessary to charge the customer, the state is set to paid
            self._order.state = 'paid'
            self.end_charging(transactions, self._concept)

        return redirect_url

    def _execute_renovation_transactions(self, transactions, err_msg):
        if len(transactions):
            # Make the charge
            redirect_url = self._charge_client(transactions)
            self._save_pending_charge(transactions)
        else:
            # If it is not necessary to charge the customer, the state is set to paid
            self._order.state = 'paid'
            self._order.save()
            raise OrderingError(err_msg)

        return redirect_url

    def _process_renovation_charge(self, contracts):
        """
        Resolves renovation charges, which includes the renovation of subscriptions and optionally usage payments
        :return: The URL where redirecting the customer to approve the charge
        """

        self._order.state = 'pending'

        now = datetime.utcnow()
        transactions = []
        for contract in contracts:
            # Check if the contract has any recurring model
            if 'subscription' not in contract.pricing_model:
                continue

            # Determine the price parts to renovate
            related_model = {
                'subscription': []
            }

            unmodified = []
            for s in contract.pricing_model['subscription']:
                renovation_date = s['renovation_date']
                if renovation_date < now:
                    related_model['subscription'].append(s)
                else:
                    unmodified.append(s)

            # Save unmodified recurring payment (not ended yed)
            if len(unmodified):
                related_model['unmodified'] = unmodified

            if 'alteration' in contract.pricing_model and \
               contract.pricing_model['alteration'].get('period') == 'recurring':
                related_model['alteration'] = contract.pricing_model['alteration']

            # Calculate the price to be charged if required
            if len(related_model['subscription']):
                self._append_transaction(transactions, contract, related_model)

        return self._execute_renovation_transactions(transactions, 'There is not recurring payments to renovate')

    def _parse_raw_accounting(self, usage):
        sdr_manager = SDRManager()
        sdrs = []

        for usage_document in usage:
            sdr_values = sdr_manager.get_sdr_values(usage_document)
            sdr_values.update({'usage_id': usage_document['id']})
            sdrs.append(sdr_values)

        return sdrs

    def _process_use_charge(self, contracts):
        """
        Resolves usage charges, which includes pay-per-use payments
        :return: The URL where redirecting the customer to approve the charge
        """
        self._order.state = 'pending'

        transactions = []
        usage_client = UsageClient()
        for contract in contracts:
            if 'pay_per_use' not in contract.pricing_model:
                continue

            related_model = {
                'pay_per_use': contract.pricing_model['pay_per_use']
            }

            accounting = self._parse_raw_accounting(usage_client.get_customer_usage(
                self._order.owner_organization.name, contract.product_id, state='Guided'))

            if 'alteration' in contract.pricing_model and \
               contract.pricing_model['alteration'].get('period') == 'recurring':
                related_model['alteration'] = contract.pricing_model['alteration']

            if len(accounting) > 0:
                self._append_transaction(
                    transactions,
                    contract,
                    related_model,
                    accounting=accounting
                )

        return self._execute_renovation_transactions(transactions, 'There is not usage payments to renovate')

    def resolve_charging(self, type_='initial', related_contracts=None):
        """
        Calculates the charge of a customer depending on the pricing model and the type of charge.
        :param type_: Type of charge, it defines if it is an initial charge, a renovation or a usage based charge
        :param related_contracts: optional field that can be used to specify a set of contracts to be processed.
        If None all the contracts in the order are processed
        :return: The URL where redirecting the user to be charged (PayPal)
        """

        self._concept = type_

        if type_ not in self.charging_processors:
            raise ValueError('Invalid charge type, must be initial, recurring, or usage')

        if related_contracts is None:
            related_contracts = self._order.contracts

        return self.charging_processors[type_](related_contracts)
コード例 #5
0
    def resolve_charging(self, new_purchase=False, sdr=False):

        # Check if there is a new purchase
        if new_purchase:
            # Create the contract
            self._create_purchase_contract()
            charge = False
            related_model = {}

            # Check if there are price parts different from pay per use
            if 'single_payment' in self._price_model:
                charge = True
                related_model['single_payment'] = self._price_model[
                    'single_payment']

            if 'subscription' in self._price_model:
                charge = True
                related_model['subscription'] = self._price_model[
                    'subscription']

            price = 0
            if charge:
                # Call the price resolver
                resolver = PriceResolver()
                price = resolver.resolve_price(related_model)

                # Check user expenditure limits and accumulated balance
                self._check_expenditure_limits(price)

                # Make the charge
                redirect_url = self._charge_client(
                    price, 'initial charge',
                    self._price_model['general_currency'])
            else:
                # If it is not necessary to charge the customer the state is set to paid
                self._purchase.state = 'paid'

            if self._purchase.state == 'paid':
                self.end_charging(price, 'initial charge', related_model)
            else:
                price = self._fix_price(price)
                self._purchase.contract.pending_payment = {
                    'price': price,
                    'concept': 'initial charge',
                    'related_model': related_model
                }
                self._purchase.contract.save()
                return redirect_url

        else:
            self._price_model = self._purchase.contract.pricing_model
            self._purchase.state = 'pending'
            self._purchase.save()

            # If not SDR received means that the call is a renovation
            if not sdr:
                # Determine the price parts to renovate
                if 'subscription' not in self._price_model:
                    raise Exception('No subscriptions to renovate')

                related_model = {'subscription': []}

                now = datetime.now()
                unmodified = []

                for s in self._price_model['subscription']:
                    renovation_date = s['renovation_date']

                    if renovation_date < now:
                        related_model['subscription'].append(s)
                    else:
                        unmodified.append(s)

                accounting_info = None
                # If pending SDR documents resolve the use charging
                if len(self._purchase.contract.pending_sdrs) > 0:
                    related_model['pay_per_use'] = self._price_model[
                        'pay_per_use']
                    accounting_info = []
                    accounting_info.extend(
                        self._purchase.contract.pending_sdrs)

                # If deductions have been included resolve the discount
                if 'deductions' in self._price_model and len(
                        self._price_model['deductions']) > 0:
                    related_model['deductions'] = self._price_model[
                        'deductions']

                resolver = PriceResolver()
                price = resolver.resolve_price(related_model, accounting_info)

                # Deductions can make the price 0
                if price > 0:
                    # If not use made, check expenditure limits and accumulated balance
                    if not accounting_info:
                        self._check_expenditure_limits(price)

                    redirect_url = self._charge_client(
                        price, 'Renovation',
                        self._price_model['general_currency'])

                if len(unmodified) > 0:
                    related_model['unmodified'] = unmodified

                # Check if applied accounting info is needed to finish the purchase
                applied_accounting = None
                if accounting_info:
                    applied_accounting = resolver.get_applied_sdr()

                if self._purchase.state == 'paid':
                    self.end_charging(price, 'Renovation', related_model,
                                      applied_accounting)
                else:
                    price = self._fix_price(price)
                    pending_payment = {
                        'price': price,
                        'concept': 'Renovation',
                        'related_model': related_model
                    }

                    # If some accounting has been used include it to be saved
                    if accounting_info:
                        pending_payment['accounting'] = applied_accounting

                    self._purchase.contract.pending_payment
                    self._purchase.contract.save()
                    return redirect_url

            # If sdr is true means that the call is a request for charging the use
            # made of a service.
            else:
                # Aggregate the calculated charges
                pending_sdrs = []
                pending_sdrs.extend(self._purchase.contract.pending_sdrs)

                if len(pending_sdrs) == 0:
                    raise Exception('No SDRs to charge')

                related_model = {
                    'pay_per_use': self._price_model['pay_per_use']
                }

                if 'deductions' in self._price_model and len(
                        self._price_model['deductions']) > 0:
                    related_model['deductions'] = self._price_model[
                        'deductions']

                resolver = PriceResolver()
                price = resolver.resolve_price(related_model, pending_sdrs)
                # Charge the client

                # Deductions can make the price 0
                if price > 0:
                    redirect_url = self._charge_client(
                        price, 'Pay per use',
                        self._price_model['general_currency'])

                applied_accounting = resolver.get_applied_sdr()

                if self._purchase.state == 'paid':
                    self.end_charging(price, 'pay per use', related_model,
                                      applied_accounting)
                else:
                    price = self._fix_price(price)
                    self._purchase.contract.pending_payment = {
                        'price': price,
                        'concept': 'pay per use',
                        'related_model': related_model,
                        'accounting': applied_accounting
                    }
                    self._purchase.contract.save()
                    return redirect_url
コード例 #6
0
class ChargingEngine:
    def __init__(self, order):
        self._order = order
        self._price_resolver = PriceResolver()
        self.charging_processors = {
            'initial': self._process_initial_charge,
            'recurring': self._process_renovation_charge,
            'usage': self._process_use_charge
        }
        self.end_processors = {
            'initial': self._end_initial_charge,
            'recurring': self._end_renovation_charge,
            'usage': self._end_use_charge
        }

    def _initial_charge_timeout(self, order):
        ordering_client = OrderingClient()
        raw_order = ordering_client.get_order(order.order_id)

        # Setting all the items as Failed, set the whole order as failed
        # ordering_client.update_state(raw_order, 'Failed')
        ordering_client.update_items_state(raw_order, 'Failed')

        order.delete()

    def _renew_charge_timeout(self, order):
        order.state = 'paid'
        order.pending_payment = None

        order.save()

    def _timeout_handler(self):

        db = get_database_connection()

        # Uses an atomic operation to get and set the _lock value in the purchase
        # document
        pre_value = db.wstore_order.find_one_and_update(
            {'_id': ObjectId(self._order.pk)}, {'$set': {
                '_lock': True
            }})

        # If _lock not exists or is set to false means that this function has
        # acquired the resource
        if '_lock' not in pre_value or not pre_value['_lock']:

            # Only rollback if the state is pending
            if pre_value['state'] == 'pending':
                order = Order.objects.get(pk=self._order.pk)
                timeout_processors = {
                    'initial': self._initial_charge_timeout,
                    'recurring': self._renew_charge_timeout,
                    'usage': self._renew_charge_timeout
                }
                timeout_processors[self._concept](order)

            db.wstore_order.find_one_and_update(
                {'_id': ObjectId(self._order.pk)}, {'$set': {
                    '_lock': False
                }})

    def _charge_client(self, transactions):

        # Load payment client
        cln_str = settings.PAYMENT_CLIENT
        client_package, client_class = cln_str.rsplit('.', 1)

        payment_client = getattr(importlib.import_module(client_package),
                                 client_class)

        # build the payment client
        client = payment_client(self._order)

        client.start_redirection_payment(transactions)
        checkout_url = client.get_checkout_url()

        # Set timeout for PayPal transaction to 5 minutes
        t = threading.Timer(300, self._timeout_handler)
        t.start()

        return checkout_url

    def _calculate_renovation_date(self, unit):
        return datetime.utcnow() + timedelta(days=ChargePeriod.get_value(unit))

    def _end_initial_charge(self, contract, transaction):
        # If a subscription part has been charged update renovation date
        related_model = transaction['related_model']
        valid_to = None
        if 'subscription' in related_model:
            updated_subscriptions = []

            for subs in contract.pricing_model['subscription']:
                up_sub = subs
                # Calculate renovation date
                valid_to = self._calculate_renovation_date(subs['unit'])
                up_sub['renovation_date'] = valid_to
                updated_subscriptions.append(up_sub)

            contract.pricing_model['subscription'] = updated_subscriptions
            related_model['subscription'] = updated_subscriptions

        # Save offerings in org profile
        self._order.owner_organization.acquired_offerings.append(
            contract.offering.pk)
        self._order.owner_organization.save()

        return None, valid_to

    def _end_renovation_charge(self, contract, transaction):

        related_model = transaction['related_model']
        # Process contract subscriptions
        valid_to = None
        for subs in related_model['subscription']:
            valid_to = self._calculate_renovation_date(subs['unit'])
            subs['renovation_date'] = valid_to
            updated_subscriptions = related_model['subscription']

            if 'unmodified' in related_model:
                updated_subscriptions.extend(related_model['unmodified'])

            # Save pricing model with new renovation dates
            contract.pricing_model['subscription'] = updated_subscriptions
            related_model['subscription'] = updated_subscriptions

        return None, valid_to

    def _end_use_charge(self, contract, transaction):
        # Change applied usage documents SDR Guided to Rated
        usage_client = UsageClient()

        for sdr_info in transaction['applied_accounting']:
            for sdr in sdr_info['accounting']:

                usage_client.rate_usage(sdr['usage_id'],
                                        unicode(contract.last_charge),
                                        sdr['duty_free'], sdr['price'],
                                        sdr_info['model']['tax_rate'],
                                        transaction['currency'],
                                        contract.product_id)

        transaction['related_model']['accounting'] = transaction[
            'applied_accounting']

        return contract.charges[-1].date if len(
            contract.charges) > 0 else self._order.date, None

    def _send_notification(self, concept, transactions):
        # TODO: Improve the rollback in case of unexpected exception
        try:
            # Send notifications if required
            handler = NotificationsHandler()
            if concept == 'initial':
                # Send customer and provider notifications
                handler.send_acquired_notification(self._order)
                for cont in self._order.contracts:
                    handler.send_provider_notification(self._order, cont)

            elif concept == 'recurring' or concept == 'usage':
                handler.send_renovation_notification(self._order, transactions)
        except:
            pass

    def end_charging(self, transactions, free_contracts, concept):
        """
        Process the second step of a payment once the customer has approved the charge
        :param transactions: List of transactions applied including the total price and the related model
        :param concept: Concept of the charge, it can be initial, renovation, or use
        """

        # Update purchase state
        if self._order.state == 'pending':
            self._order.state = 'paid'
            self._order.save()

        time_stamp = datetime.utcnow()

        self._order.pending_payment = None

        invoice_builder = InvoiceBuilder(self._order)
        billing_client = BillingClient() if concept != 'initial' else None

        for transaction in transactions:
            contract = self._order.get_item_contract(transaction['item'])
            contract.last_charge = time_stamp

            valid_from, valid_to = self.end_processors[concept](contract,
                                                                transaction)

            # If the customer has been charged create the CDR
            cdr_manager = CDRManager(self._order, contract)
            cdr_manager.generate_cdr(transaction['related_model'],
                                     time_stamp.isoformat() + 'Z')

            # Generate the invoice
            invoice_path = ''
            try:
                invoice_path = invoice_builder.generate_invoice(
                    contract, transaction, concept)
            except:
                pass

            # Update contracts
            charge = Charge(date=time_stamp,
                            cost=transaction['price'],
                            duty_free=transaction['duty_free'],
                            currency=transaction['currency'],
                            concept=concept,
                            invoice=invoice_path)
            contract.charges.append(charge)

            # Send the charge to the billing API to allow user accesses
            if concept != 'initial':
                # When the change concept is initial, the product has not been yet created in the inventory
                billing_client.create_charge(charge,
                                             contract.product_id,
                                             start_date=valid_from,
                                             end_date=valid_to)

        for free in free_contracts:
            self._order.owner_organization.acquired_offerings.append(
                free.offering.pk)

        self._order.owner_organization.save()
        self._order.save()
        self._send_notification(concept, transactions)

    def _save_pending_charge(self, transactions, free_contracts=[]):
        pending_payment = Payment(transactions=transactions,
                                  concept=self._concept,
                                  free_contracts=free_contracts)

        self._order.pending_payment = pending_payment
        self._order.save()

    def _append_transaction(self,
                            transactions,
                            contract,
                            related_model,
                            accounting=None):
        # Call the price resolver
        price, duty_free = self._price_resolver.resolve_price(
            related_model, accounting)

        if 'alteration' in related_model and not self._price_resolver.is_altered(
        ):
            del related_model['alteration']

        transaction = {
            'price': price,
            'duty_free': duty_free,
            'description': contract.offering.description,
            'currency': contract.pricing_model['general_currency'],
            'related_model': related_model,
            'item': contract.item_id
        }

        # Get the applied accounting info is needed
        if accounting is not None:
            transaction[
                'applied_accounting'] = self._price_resolver.get_applied_sdr()

        transactions.append(transaction)

    def _process_initial_charge(self, contracts):
        """
        Resolves initial charges, which can include single payments or the initial payment of a subscription
        :return: The URL where redirecting the customer to approve the charge
        """

        transactions = []
        free_contracts = []
        redirect_url = None

        for contract in contracts:
            related_model = {}
            # Check if there are price parts different from pay per use
            if 'single_payment' in contract.pricing_model:
                related_model['single_payment'] = contract.pricing_model[
                    'single_payment']

            if 'subscription' in contract.pricing_model:
                related_model['subscription'] = contract.pricing_model[
                    'subscription']

            if 'alteration' in contract.pricing_model:
                related_model['alteration'] = contract.pricing_model[
                    'alteration']

            if len(related_model):
                self._append_transaction(transactions, contract, related_model)
            else:
                free_contracts.append(contract)

        if len(transactions):
            # Make the charge
            redirect_url = self._charge_client(transactions)
            self._save_pending_charge(transactions,
                                      free_contracts=free_contracts)
        else:
            # If it is not necessary to charge the customer, the state is set to paid
            self._order.state = 'paid'
            self.end_charging(transactions, free_contracts, self._concept)

        return redirect_url

    def _execute_renovation_transactions(self, transactions, err_msg):
        if len(transactions):
            # Make the charge
            redirect_url = self._charge_client(transactions)
            self._save_pending_charge(transactions)
        else:
            # If it is not necessary to charge the customer, the state is set to paid
            self._order.state = 'paid'
            self._order.save()
            raise OrderingError(err_msg)

        return redirect_url

    def _process_renovation_charge(self, contracts):
        """
        Resolves renovation charges, which includes the renovation of subscriptions and optionally usage payments
        :return: The URL where redirecting the customer to approve the charge
        """

        self._order.state = 'pending'

        now = datetime.utcnow()
        transactions = []
        for contract in contracts:
            # Check if the contract has any recurring model
            if 'subscription' not in contract.pricing_model:
                continue

            # Determine the price parts to renovate
            related_model = {'subscription': []}

            unmodified = []
            for s in contract.pricing_model['subscription']:
                renovation_date = s['renovation_date']
                if renovation_date < now:
                    related_model['subscription'].append(s)
                else:
                    unmodified.append(s)

            # Save unmodified recurring payment (not ended yed)
            if len(unmodified):
                related_model['unmodified'] = unmodified

            if 'alteration' in contract.pricing_model and \
               contract.pricing_model['alteration'].get('period') == 'recurring':
                related_model['alteration'] = contract.pricing_model[
                    'alteration']

            # Calculate the price to be charged if required
            if len(related_model['subscription']):
                self._append_transaction(transactions, contract, related_model)

        return self._execute_renovation_transactions(
            transactions, 'There is not recurring payments to renovate')

    def _parse_raw_accounting(self, usage):
        sdr_manager = SDRManager()
        sdrs = []

        for usage_document in usage:
            sdr_values = sdr_manager.get_sdr_values(usage_document)
            sdr_values.update({'usage_id': usage_document['id']})
            sdrs.append(sdr_values)

        return sdrs

    def _process_use_charge(self, contracts):
        """
        Resolves usage charges, which includes pay-per-use payments
        :return: The URL where redirecting the customer to approve the charge
        """
        self._order.state = 'pending'

        transactions = []
        usage_client = UsageClient()
        for contract in contracts:
            if 'pay_per_use' not in contract.pricing_model:
                continue

            related_model = {
                'pay_per_use': contract.pricing_model['pay_per_use']
            }

            accounting = self._parse_raw_accounting(
                usage_client.get_customer_usage(
                    self._order.owner_organization.name,
                    contract.product_id,
                    state='Guided'))

            if 'alteration' in contract.pricing_model and \
               contract.pricing_model['alteration'].get('period') == 'recurring':
                related_model['alteration'] = contract.pricing_model[
                    'alteration']

            if len(accounting) > 0:
                self._append_transaction(transactions,
                                         contract,
                                         related_model,
                                         accounting=accounting)

        return self._execute_renovation_transactions(
            transactions, 'There is not usage payments to renovate')

    def resolve_charging(self, type_='initial', related_contracts=None):
        """
        Calculates the charge of a customer depending on the pricing model and the type of charge.
        :param type_: Type of charge, it defines if it is an initial charge, a renovation or a usage based charge
        :param related_contracts: optional field that can be used to specify a set of contracts to be processed.
        If None all the contracts in the order are processed
        :return: The URL where redirecting the user to be charged (PayPal)
        """

        self._concept = type_

        if type_ not in self.charging_processors:
            raise ValueError(
                'Invalid charge type, must be initial, recurring, or usage')

        if related_contracts is None:
            related_contracts = self._order.contracts

        return self.charging_processors[type_](related_contracts)