def create(self, request):

        try:
            event = json.loads(request.body)
        except:
            return build_response(
                request, 400, 'The provided data is not a valid JSON object')

        if event['eventType'] != 'ProductCreationNotification':
            return build_response(request, 200, 'OK')

        product = event['event']['product']

        # Extract order id
        order_id = product['name'].split('=')[1]

        # Get order
        order = Order.objects.get(order_id=order_id)
        contract = None

        # Search contract
        for cont in order.contracts:
            if product['productOffering']['id'] == cont.offering.off_id:
                contract = cont

        if contract is None:
            return build_response(
                request, 404,
                'There is not a contract for the specified product')

        # Save contract id
        contract.product_id = product['id']
        order.save()

        # Activate asset
        try:
            on_product_acquired(order, contract)
        except:
            return build_response(request, 400,
                                  'The asset has failed to be activated')

        # Change product state to active
        inventory_client = InventoryClient()
        inventory_client.activate_product(product['id'])

        # Create the initial charge in the billing API
        if len(contract.charges) == 1:
            billing_client = BillingClient()
            valid_to = None
            # If the initial charge was a subscription is needed to determine the expiration date
            if 'subscription' in contract.pricing_model:
                valid_to = contract.pricing_model['subscription'][0][
                    'renovation_date']

            billing_client.create_charge(contract.charges[0],
                                         contract.product_id,
                                         start_date=None,
                                         end_date=valid_to)

        return build_response(request, 200, 'OK')
    def create(self, request):
        task, order, contract, error_response = validate_product_job(
            self, request)

        if error_response is not None:
            return error_response

        # If the model is pay-per-use charge for pending payment
        redirect_url = None
        if task['priceType'].lower() == 'usage':
            # The update of the product status need to be postponed if there is a pending payment
            redirect_url, error_response = process_product_payment(
                self, request, task, order, contract)

            if error_response is not None:
                return error_response

        response = build_response(request, 200, 'OK')

        # Include redirection header if needed
        if redirect_url is not None:
            response['X-Redirect-URL'] = redirect_url
        else:
            # Suspend the product as no pending payment
            on_product_suspended(order, contract)

            contract.suspended = True
            order.save()

            client = InventoryClient()
            client.suspend_product(contract.product_id)

        return response
    def _process_delete_items(self, items):
        for item in items:
            if 'product' not in item:
                raise OrderingError(
                    'It is required to specify product information in delete order items'
                )

            product = item['product']

            if 'id' not in product:
                raise OrderingError(
                    'It is required to provide product id in delete order items'
                )

            # Set the contract as terminated
            client = InventoryClient()
            order, contract = self._get_existing_contract(
                client, product['id'])

            # Suspend the access to the service
            on_product_suspended(order, contract)

            contract.terminated = True
            order.save()

            # Terminate product in the inventory
            client.terminate_product(product['id'])
Ejemplo n.º 4
0
    def _set_renovation_states(self, transactions, raw_order, order):
        inventory_client = InventoryClient()

        for transaction in transactions:
            try:
                contract = order.get_item_contract(transaction['item'])
                inventory_client.activate_product(contract.product_id)
            except:
                pass
    def _set_renovation_states(self, transactions, raw_order, order):
        inventory_client = InventoryClient()

        for transaction in transactions:
            try:
                contract = order.get_item_contract(transaction['item'])
                inventory_client.activate_product(contract.product_id)

                # Activate the product
                on_product_acquired(order, contract)
            except:
                pass
    def ready(self):
        import sys

        from django.conf import settings
        from django.core.exceptions import ImproperlyConfigured

        from wstore.models import Context
        from wstore.store_commons.utils.url import is_valid_url
        from wstore.ordering.inventory_client import InventoryClient
        from wstore.rss_adaptor.rss_manager import ProviderManager

        # Creates a new user profile when an user is created
        # post_save.connect(create_user_profile, sender=User)
        register_signals()

        testing = sys.argv[1:2] == ['test'] or sys.argv[1:2] == ['migrate']
        if not testing:
            # Validate that a correct site and local_site has been provided
            if not is_valid_url(settings.SITE) or not is_valid_url(settings.LOCAL_SITE):
                raise ImproperlyConfigured('SITE and LOCAL_SITE settings must be a valid URL')

            # Create context object if it does not exists
            if not len(Context.objects.all()):
                Context.objects.create(
                    failed_cdrs=[],
                    failed_upgrades=[]
                )

            inventory = InventoryClient()
            inventory.create_inventory_subscription()

            # Create RSS default aggregator and provider
            credentials = {
                'user': settings.STORE_NAME,
                'roles': [settings.ADMIN_ROLE],
                'email': settings.WSTOREMAIL
            }
            prov_manager = ProviderManager(credentials)

            try:
                prov_manager.register_aggregator({
                    'aggregatorId': settings.WSTOREMAIL,
                    'aggregatorName': settings.STORE_NAME,
                    'defaultAggregator': True
                })
            except Exception as e:  # If the error is a conflict means that the aggregator is already registered
                if e.response.status_code != 409:
                    raise e
    def _process_modify_items(self, items):
        if len(items) > 1:
            raise OrderingError(
                'Only a modify item is supported per order item')

        item = items[0]
        if 'product' not in item:
            raise OrderingError(
                'It is required to specify product information in modify order items'
            )

        product = item['product']

        if 'id' not in product:
            raise OrderingError(
                'It is required to provide product id in modify order items')

        client = InventoryClient()
        order, contract = self._get_existing_contract(client, product['id'])

        # Build the new contract
        new_contract = self._build_contract(item)
        if new_contract.pricing_model != {}:
            contract.pricing_model = new_contract.pricing_model
            contract.revenue_class = new_contract.revenue_class

        order.save()

        # The modified item is treated as an initial payment
        charging_engine = ChargingEngine(order)
        return charging_engine.resolve_charging(type_='initial',
                                                related_contracts=[contract])
Ejemplo n.º 8
0
    def __init__(self, asset):
        Thread.__init__(self)
        self._asset = asset
        self._client = InventoryClient()

        # Get product name
        try:
            prod_url = '{}/api/catalogManagement/v2/productSpecification/{}?fields=name'\
                .format(settings.CATALOG, self._asset.product_id)

            resp = requests.get(prod_url)
            resp.raise_for_status()

            self._product_name = resp.json()['name']
        except HTTPError:
            self._product_name = None
Ejemplo n.º 9
0
    def _check_renovation_date(self, renovation_date, order, contract):
        now = datetime.now()

        timed = renovation_date - now

        if timed.days < 7:
            handler = NotificationsHandler()

            if timed.days < 0:
                # Notify that the subscription has finished
                handler.send_payment_required_notification(order, contract)

                # Set the product as suspended
                client = InventoryClient()
                client.suspend_product(contract.product_id)
            else:
                # There is less than a week remaining
                handler.send_near_expiration_notification(order, contract, timed.days)
Ejemplo n.º 10
0
    def _process_delete_items(self, items):
        for item in items:
            if 'product' not in item:
                raise OrderingError('It is required to specify product information in delete order items')

            product = item['product']

            if 'id' not in product:
                raise OrderingError('It is required to provide product id in delete order items')

            # Set the contract as terminated
            client = InventoryClient()
            order, contract = self._get_existing_contract(client, product['id'])
            contract.terminated = True

            order.save()

            # Terminate product in the inventory
            client.terminate_product(product['id'])
def process_product_payment(self, request, task, order, contract):
    # Refresh accounting information
    on_usage_refreshed(order, contract)

    # Build charging engine
    charging_engine = ChargingEngine(order)

    redirect_url = None
    try:
        redirect_url = charging_engine.resolve_charging(
            type_=task['priceType'].lower(), related_contracts=[contract])
    except ValueError as e:
        return None, build_response(request, 400, str(e))
    except OrderingError as e:
        # The error might be raised because renewing a suspended product not expired
        if str(
                e
        ) == 'OrderingError: There is not recurring payments to renovate' and contract.suspended:
            try:
                on_product_acquired(order, contract)

                # Change product state to active
                contract.suspended = False
                order.save()

                inventory_client = InventoryClient()
                inventory_client.activate_product(contract.product_id)
            except:
                return None, build_response(
                    request, 400, 'The asset has failed to be activated')

        else:
            return None, build_response(request, 422, str(e))
    except:
        return None, build_response(
            request, 500,
            'An unexpected event prevented your payment to be created')

    return redirect_url, None
    def _check_renovation_date(self, renovation_date, order, contract):
        now = datetime.utcnow()

        timed = renovation_date - now

        if timed.days < 7:
            handler = NotificationsHandler()

            if timed.days < 0:
                # Suspend the access to the service
                on_product_suspended(order, contract)

                # Notify that the subscription has finished
                handler.send_payment_required_notification(order, contract)

                # Set the product as suspended
                client = InventoryClient()
                client.suspend_product(contract.product_id)

            else:
                # There is less than a week remaining
                handler.send_near_expiration_notification(
                    order, contract, timed.days)
Ejemplo n.º 13
0
from __future__ import unicode_literals

import sys

from django.conf import settings

from wstore.models import Context
from wstore.ordering.inventory_client import InventoryClient
from wstore.rss_adaptor.rss_manager import ProviderManager


testing = sys.argv[1:2] == ['test']

if not testing and Context.objects.all():
    inventory = InventoryClient()
    inventory.create_inventory_subscription()

    # Create RSS default aggregator and provider
    credentials = {
        'user': settings.STORE_NAME,
        'roles': ['provider'],
        'email': settings.WSTOREMAIL
    }
    prov_manager = ProviderManager(credentials)

    try:
        prov_manager.register_aggregator({
            'aggregatorId': settings.WSTOREMAIL,
            'aggregatorName': settings.STORE_NAME,
            'defaultAggregator': True
Ejemplo n.º 14
0
from __future__ import unicode_literals

import sys

from django.conf import settings

from wstore.models import Context
from wstore.ordering.inventory_client import InventoryClient
from wstore.rss_adaptor.rss_manager import ProviderManager

testing = sys.argv[1:2] == ['test']

if not testing and Context.objects.all() and Context.objects.all(
)[0].local_site is not None:
    inventory = InventoryClient()
    inventory.create_inventory_subscription()

    # Create RSS default aggregator and provider
    credentials = {
        'user': settings.STORE_NAME,
        'roles': ['provider'],
        'email': settings.WSTOREMAIL
    }
    prov_manager = ProviderManager(credentials)

    try:
        prov_manager.register_aggregator({
            'aggregatorId': settings.WSTOREMAIL,
            'aggregatorName': settings.STORE_NAME,
            'defaultAggregator': True
Ejemplo n.º 15
0
class InventoryUpgrader(Thread):
    def __init__(self, asset):
        Thread.__init__(self)
        self._asset = asset
        self._client = InventoryClient()

        # Get product name
        try:
            prod_url = '{}/api/catalogManagement/v2/productSpecification/{}?fields=name'\
                .format(settings.CATALOG, self._asset.product_id)

            resp = requests.get(prod_url)
            resp.raise_for_status()

            self._product_name = resp.json()['name']
        except HTTPError:
            self._product_name = None

    def _save_failed(self, pending_off, pending_products):
        # The failed upgrades list may be upgraded by other threads or other server instances
        # In this case context must be accessed as a shared resource
        context_id = Context.objects.all()[0].pk

        lock = DocumentLock('wstore_context', context_id, 'ctx')
        lock.wait_document()

        # At this point only the current thread can modify the list of pending upgrades
        context = Context.objects.all()[0]
        context.failed_upgrades.append({
            'asset_id': self._asset.pk,
            'pending_offerings': pending_off,
            'pending_products': pending_products
        })
        context.save()

        lock.unlock_document()

    def _notify_user(self, patched_product):
        if self._product_name is not None:
            try:
                not_handler = NotificationsHandler()
                order = Order.objects.get(
                    order_id=patched_product['name'].split('=')[-1])

                not_handler.send_product_upgraded_notification(
                    order,
                    order.get_product_contract(unicode(patched_product['id'])),
                    self._product_name)

            except:
                # A failure in the email notification is not relevant
                pass

    def upgrade_products(self, product_ids, id_filter):
        def is_digital_char(characteristic):
            # Return whether a characteristics is defining asset info for the given one
            def is_product(id_):
                sp = id_.split(':')
                return len(sp) == 2 and sp[0] == 'product' and sp[
                    1] == self._asset.product_id

            def is_offering(id_):
                sp = id_.split(':')
                offerings = []

                if len(sp) == 2 and sp[0] == 'offering':
                    offerings = Offering.objects.filter(off_id=sp[1])

                return len(offerings) == 1 and \
                        (offerings[0].asset == self._asset or self._asset.pk in offerings[0].asset.bundled_assets)

            dig_char = False
            id_str = ''

            name = characteristic['name'].lower()

            if name.endswith('asset type') or name.endswith(
                    'media type') or name.endswith('location'):
                # There are several formats for asset characteristics within the inventory products depending on
                # the number and the structure of the involved bundles
                # name: Asset Type  , For single offering with single product
                # name: offering:123 Asset Type   , For bundle offering with single product
                # name: product:123 Asset Type    , For single offering with bundle product
                # name: offering:123 product:345 Asset Type  , For bundle offering with bundle product

                id_str = name.replace('asset type',
                                      '').replace('media type',
                                                  '').replace('location', '')
                bundle_ids = id_str.split(' ')

                dig_char = len(bundle_ids) == 1 or \
                           (len(bundle_ids) == 2 and (is_product(bundle_ids[0]) or is_offering(bundle_ids[0]))) or \
                           (len(bundle_ids) == 3 and is_offering(bundle_ids[0]) and is_product(bundle_ids[1]))

            return dig_char, id_str

        n_pages = int(math.ceil(len(product_ids) / PAGE_LEN))

        missing_upgrades = []
        for page in range(0, n_pages):
            # Get the ids related to the current product page
            offset = page * int(PAGE_LEN)

            page_ids = [
                unicode(id_filter(p_id))
                for p_id in product_ids[offset:offset + int(PAGE_LEN)]
            ]
            ids = ','.join(page_ids)

            # Get product characteristics field
            try:
                products = self._client.get_products(
                    query={
                        'id': ids,
                        'fields': 'id,productCharacteristic'
                    })
            except HTTPError:
                missing_upgrades.extend(page_ids)
                continue

            # Patch product to include new asset information
            for product in products:
                pre_ids = ''
                product_id = unicode(product['id'])

                new_characteristics = []
                for char in product['productCharacteristic']:
                    is_dig, ids_str = is_digital_char(char)
                    if not is_dig:
                        new_characteristics.append(char)
                    else:
                        pre_ids = ids_str

                new_characteristics.append({
                    'name':
                    '{}Media Type'.format(pre_ids),
                    'value':
                    self._asset.content_type
                })

                new_characteristics.append({
                    'name':
                    '{}Asset Type'.format(pre_ids),
                    'value':
                    self._asset.resource_type
                })

                new_characteristics.append({
                    'name': '{}Location'.format(pre_ids),
                    'value': self._asset.download_link
                })

                try:
                    # The inventory API returns the product after patching
                    patched_product = self._client.patch_product(
                        product_id,
                        {'productCharacteristic': new_characteristics})
                except HTTPError:
                    missing_upgrades.append(product_id)
                    continue

                self._notify_user(patched_product)

        return missing_upgrades

    def upgrade_asset_products(self, offering_ids):
        # Get all the product ids related to the given product offering
        missing_off = []
        missing_products = []
        for off_id in offering_ids:
            try:
                product_ids = self._client.get_products(query={
                    'productOffering.id': off_id,
                    'fields': 'id'
                })
            except HTTPError:
                # Failure reading the available product ids, all upgrades pending
                missing_off.append(off_id)
                continue

            missing_products.extend(
                self.upgrade_products(product_ids, lambda p_id: p_id['id']))

        return missing_off, missing_products

    def _get_providing_offerings(self):
        # Get product bundles that contain the included asset
        assets = [self._asset]
        assets.extend(Resource.objects.filter(bundled_assets=self._asset.pk))

        # Get all the offerings that include the asset or one of the bundles
        offerings = []
        for asset in assets:
            offerings.extend(Offering.objects.filter(asset=asset))

        # Get all the offering bundles which include the previous offerings
        bundles = []
        for off in offerings:
            bundles.extend(Offering.objects.filter(bundled_offerings=off.pk))

        offerings.extend(bundles)
        return offerings

    def run(self):
        # Get all the offerings that give access to the provided digital asset
        offerings = self._get_providing_offerings()

        # Upgrade all the products related to the provided asset
        missing_off, missing_products = self.upgrade_asset_products(
            [offering.off_id for offering in offerings])

        if len(missing_off) > 0 or len(missing_products) > 0:
            self._save_failed(missing_off, missing_products)