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])
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])
def update(self, request, reference): purchase = Purchase.objects.get(ref=reference) data = json.loads(request.raw_post_data) try: if data['method'] == 'paypal': charging_engine = ChargingEngine(purchase, payment_method='paypal') elif data['method'] == 'credit_card': # Get the payment info if 'credit_card' in data: credit_card = data['credit_card'] else: if purchase.organization_owned: credit_card = purchase.owner_organization.payment_info else: credit_card = purchase.customer.userprofile.payment_info charging_engine = ChargingEngine(purchase, payment_method='credit_card', credit_card=credit_card) charging_engine.resolve_charging() except: # Refresh the purchase info purchase = Purchase.objects.get(ref=reference) rollback(purchase) return build_response(request, 400, 'Invalid JSON content') return build_response(request, 200, 'OK')
def _process_add_items(self, items, order_id, description, terms_accepted): new_contracts = [self._build_contract(item) for item in items] terms_found = False for c in new_contracts: off = Offering.objects.get(pk=ObjectId(c.offering)) if off.asset is not None and off.asset.has_terms: terms_found = True if terms_found and not terms_accepted: raise OrderingError('You must accept the terms and conditions of the offering to acquire it') current_org = self._customer.userprofile.current_organization order = Order.objects.create( order_id=order_id, customer=self._customer, owner_organization=current_org, date=datetime.utcnow(), state='pending', tax_address=self._get_billing_address(items), contracts=new_contracts, description=description ) self.rollback_logger['models'].append(order) charging_engine = ChargingEngine(order) return charging_engine.resolve_charging()
def resolve_purchase_usage(self, purchase): # Get payment info if purchase.organization_owned: org = purchase.owner_organization payment_info = org.payment_info else: payment_info = purchase.customer.userprofile.payment_info charging = ChargingEngine(purchase, payment_method='credit_card', credit_card=payment_info) charging.resolve_charging(type_='use')
def create(self, request): try: task = json.loads(request.body) except: return build_response(request, 400, 'The provided data is not a valid JSON object') # Check the products to be renovated if 'name' not in task or 'id' not in task or 'priceType' not in task: return build_response(request, 400, 'Missing required field, must contain name, id and priceType fields') # Parse oid from product name parsed_name = task['name'].split('=') try: order = Order.objects.get(order_id=parsed_name[1]) except: return build_response(request, 404, 'The oid specified in the product name is not valid') # Get contract to renovate if isinstance(task['id'], int): task['id'] = unicode(task['id']) try: contract = order.get_product_contract(task['id']) except: return build_response(request, 404, 'The specified product id is not valid') # Refresh accounting information on_usage_refreshed(order, contract) # Build charging engine charging_engine = ChargingEngine(order) if task['priceType'].lower() not in ['recurring', 'usage']: return build_response(request, 400, 'Invalid priceType only recurring and usage types can be renovated') try: redirect_url = charging_engine.resolve_charging(type_=task['priceType'].lower(), related_contracts=[contract]) except ValueError as e: return build_response(request, 400, unicode(e)) except OrderingError as e: return build_response(request, 422, unicode(e)) except: return build_response(request, 500, 'An unexpected event prevented your payment to be created') response = build_response(request, 200, 'OK') # Include redirection header if needed if redirect_url is not None: response['X-Redirect-URL'] = redirect_url return response
def create(self, request, reference): try: # Extract SDR document from the HTTP request data = json.loads(request.raw_post_data) # Validate SDR structure if 'offering' not in data or 'customer' not in data or 'time_stamp' not in data \ or 'correlation_number' not in data or 'record_type' not in data or'unit' not in data \ or 'value' not in data or 'component_label' not in data: raise Exception('Invalid JSON content') # Get the purchase purchase = Purchase.objects.get(ref=reference) # Call the charging engine core with the SDR charging_engine = ChargingEngine(purchase) charging_engine.include_sdr(data) except Exception, e: return build_response(request, 400, e.message)
def _process_add_items(self, items, order_id, description): new_contracts = [self._build_contract(item) for item in items] current_org = self._customer.userprofile.current_organization order = Order.objects.create( order_id=order_id, customer=self._customer, owner_organization=current_org, date=datetime.utcnow(), state='pending', tax_address=self._get_billing_address(items), contracts=new_contracts, description=description) self.rollback_logger['models'].append(order) charging_engine = ChargingEngine(order) return charging_engine.resolve_charging()
def _process_add_items(self, items, order_id, description): new_contracts = [self._build_contract(item) for item in items] current_org = self._customer.userprofile.current_organization order = Order.objects.create( order_id=order_id, customer=self._customer, owner_organization=current_org, date=datetime.utcnow(), state='pending', tax_address=self._get_billing_address(items), contracts=new_contracts, description=description ) self.rollback_logger['models'].append(order) charging_engine = ChargingEngine(order) return charging_engine.resolve_charging()
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 handle(self, *args, **options): """ This method is used to perform the charging process of the offerings that have pending SDR for more than a month """ now = time.mktime(datetime.now().timetuple()) if len(args) == 0: # Get contracts for contract in Contract.objects.all(): pending_sdrs = contract.pending_sdrs # If there are subscriptions the renovations are used as triggers if len(pending_sdrs) > 0 and (not 'subscription' in contract.pricing_model): time_stamp = time.mktime(pending_sdrs[0]['time_stamp'].timetuple()) if (time_stamp + 2592000) <= now: # A month # Get the related payment info purchase = contract.purchase if purchase.organization_owned: org = purchase.owner_organization payment_info = org.payment_info else: payment_info = purchase.customer.userprofile.payment_info charging = ChargingEngine(purchase, payment_method='credit_card', credit_card=payment_info) charging.resolve_charging(sdr=True) elif len(args) == 1: # Get the purchase try: purchase = Purchase.objects.get(ref=args[0]) except: raise Exception('The provided purchase does not exists') # Get the contract contract = purchase.contract # Check if there are pending SDRs if (len(contract.pending_sdrs) > 0): # Get payment info if purchase.organization_owned: org = purchase.owner_organization payment_info = org.payment_info else: payment_info = purchase.customer.userprofile.payment_info charging = ChargingEngine(purchase, payment_method='credit_card', credit_card=payment_info) charging.resolve_charging(sdr=True) else: raise Exception('No accounting info in the provided purchase') else: raise Exception('Invalid number of arguments')
def create_purchase(user, offering, org_owned=False, payment_info=None): if offering.state != 'published': raise PermissionDenied("This offering can't be purchased") if offering.open: raise PermissionDenied('Open offerings cannot be purchased') if accepted_needed(offering) and not payment_info['accepted']: raise PermissionDenied('You must accept the terms and conditions of the offering to acquire it') profile = UserProfile.objects.get(user=user) # Check if the offering is already purchased if (org_owned and offering.pk in profile.current_organization.offerings_purchased) \ or (not org_owned and offering.pk in profile.offerings_purchased): raise PermissionDenied('The offering has been already purchased') organization = profile.current_organization plan = None # Check the selected plan if payment_info and 'plan' in payment_info: plan = payment_info['plan'] # Get the effective tax address if 'tax_address' not in payment_info: if org_owned: tax = organization.tax_address else: tax = profile.tax_address # Check that the customer has a tax address if 'street' not in tax: raise ValueError('The customer does not have a tax address') else: tax = payment_info['tax_address'] # Check tax_address fields if ('street' not in tax) or ('postal' not in tax)\ or ('city' not in tax) or ('country' not in tax): raise ValueError('The tax address is not valid') # Check the payment method before purchase creation in order to avoid # an inconsistent state in the database credit_card_info = None if payment_info['payment_method'] == 'credit_card': if 'credit_card' in payment_info: # Check credit card info if (not ('number' in payment_info['credit_card'])) or (not ('type' in payment_info['credit_card']))\ or (not ('expire_year' in payment_info['credit_card'])) or (not ('expire_month' in payment_info['credit_card']))\ or (not ('cvv2' in payment_info['credit_card'])): raise ValueError('Invalid credit card info') credit_card_info = payment_info['credit_card'] else: if org_owned: credit_card_info = organization.payment_info else: credit_card_info = profile.payment_info # Check the credit card info if 'number' not in credit_card_info: raise Exception('The customer does not have payment info') elif payment_info['payment_method'] != 'paypal': raise ValueError('Invalid payment method') # Check if a purchase object exists old_purchase = Purchase.objects.filter(owner_organization=organization, offering=offering) if len(old_purchase) > 0: for p in old_purchase: p.delete() # Create the purchase purchase = Purchase.objects.create( customer=user, date=datetime.now(), offering=offering, organization_owned=org_owned, state='pending', tax_address=tax, owner_organization=organization ) # Load ref purchase.ref = purchase.pk purchase.save() if credit_card_info is not None: charging_engine = ChargingEngine(purchase, payment_method='credit_card', credit_card=credit_card_info, plan=plan) else: charging_engine = ChargingEngine(purchase, payment_method='paypal', plan=plan) redirect_url = charging_engine.resolve_charging(new_purchase=True) if redirect_url is None: result = purchase # If no redirect URL is provided the purchase has ended so the user profile # info is updated if org_owned: organization.offerings_purchased.append(offering.pk) organization.save() else: profile.offerings_purchased.append(offering.pk) profile.save() notify_provider(purchase) else: result = redirect_url # Update offering indexes index_path = os.path.join(settings.BASEDIR, 'wstore') index_path = os.path.join(index_path, 'search') index_path = os.path.join(index_path, 'indexes') se = SearchEngine(index_path) se.update_index(offering) return result
def test_purchase_offering_update_exception(self): # Test view exceptions # Create the request, The user has not purchased a previous version # of the offering, so she is not allowed to purchase the offering data = { 'offering': { 'organization': 'test_organization', 'name': 'test_offering', 'version': '1.1' }, 'plan_label': 'update', 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment': { 'method': 'paypal' } } request = self.factory.post( '/api/contracting/', json.dumps(data), HTTP_ACCEPT='application/json; charset=utf-8', content_type='application/json; charset=utf-8') request.user = self._user purchase_collection = views.PurchaseCollection( permitted_methods=('POST', )) response = purchase_collection.create(request) # Check response body_response = json.loads(response.content) self.assertEquals(body_response['result'], 'error') self.assertEquals(body_response['message'], 'Forbidden') self.assertEquals(response.status_code, 403) # Test Create contract exceptions offering = Offering.objects.get(pk="71000aba8e05ac2115f022ff") offering.offering_description = { 'pricing': { 'price_plans': [{ 'title': 'Plan 1', 'label': 'update', 'price_components': [] }, { 'title': 'Plan 1', 'label': 'regular', 'price_components': [] }] } } offering.save() from datetime import datetime purchase = Purchase.objects.create( customer=self._user, date=datetime.now(), offering=offering, organization_owned=False, state='paid', tax_address={ 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, bill=['/media/bills/11111111111.pdf']) from wstore.charging_engine.charging_engine import ChargingEngine # Check exceptions that can occur with multiple price plans when # creating the related purchase contract errors = { 'The price plan label is required to identify the plan': None, 'The specified plan does not exist': 'unexisting' } for err in errors: error = False msg = None try: charging = ChargingEngine(purchase, payment_method='paypal', plan=errors[err]) charging._create_purchase_contract() except Exception, e: error = True msg = e.message self.assertTrue(error) self.assertEquals(msg, err)
def test_purchase_offering_update_payment(self): current_org = Organization.objects.get(pk="91000aba8e06ac2199999999") current_org.offerings_purchased.append('61000aba8e05ac2115f022f9') self._user.userprofile.current_organization = current_org self._user.userprofile.get_current_roles = MagicMock() self._user.userprofile.get_current_roles.return_value = ['customer'] self._user.userprofile.save() # Create the request data = { 'offering': { 'organization': 'test_organization', 'name': 'test_offering', 'version': '1.1' }, 'plan_label': 'update', 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment': { 'method': 'paypal' } } request = self.factory.post( '/api/contracting/', json.dumps(data), HTTP_ACCEPT='application/json; charset=utf-8', content_type='application/json; charset=utf-8' ) request.user = self._user # Test purchase view views.create_purchase = MagicMock(name='create_purchase') offering = Offering.objects.get(pk="71000aba8e05ac2115f022ff") from datetime import datetime purchase = Purchase.objects.create( customer=self._user, date=datetime.now(), offering=offering, organization_owned=True, state='paid', tax_address={ 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, bill=['/media/bills/11111111111.pdf'] ) views.create_purchase.return_value = purchase views.get_current_site = MagicMock(name='get_current_site') views.get_current_site.return_value = Site.objects.get(name='antares') views.Context.objects.get = MagicMock(name='get') context = MagicMock() context.user_refs = [] views.Context.objects.get.return_value = context purchase_collection = views.PurchaseCollection(permitted_methods=('POST',)) response = purchase_collection.create(request) # Check response body_response = json.loads(response.content) self.assertEquals(len(body_response['bill']), 1) self.assertEquals(body_response['bill'][0], '/media/bills/11111111111.pdf') payment_info = { 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment_method': 'paypal', 'plan': 'update' } views.create_purchase.assert_called_once_with(self._user, offering, org_owned=True, payment_info=payment_info) # Test Contract creation # Load usdl info f = open('wstore/test/test_usdl2.ttl', 'rb') g = rdflib.Graph() g.parse(data=f.read(), format='n3') f.close() offering.offering_description = json.loads(g.serialize(format='json-ld', auto_compact=True)) offering.save() from wstore.charging_engine.charging_engine import ChargingEngine charging = ChargingEngine(purchase, payment_method='paypal', plan='update') charging._create_purchase_contract() # Refresh purchase purchase = Purchase.objects.get(pk=purchase.pk) contract = purchase.contract # Check contract pricing model self.assertTrue('single_payment' in contract.pricing_model) self.assertEquals(len(contract.pricing_model['single_payment']), 1) payment = contract.pricing_model['single_payment'][0] self.assertEquals(payment['title'], 'Price component update') self.assertEquals(payment['value'], '1.0') self.assertFalse('subscription' in contract.pricing_model) self.assertFalse('pay_per_use' in contract.pricing_model)
def test_purchase_offering_update_exception(self): # Test view exceptions # Create the request, The user has not purchased a previous version # of the offering, so she is not allowed to purchase the offering data = { 'offering': { 'organization': 'test_organization', 'name': 'test_offering', 'version': '1.1' }, 'plan_label': 'update', 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment': { 'method': 'paypal' } } request = self.factory.post( '/api/contracting/', json.dumps(data), HTTP_ACCEPT='application/json; charset=utf-8', content_type='application/json; charset=utf-8' ) request.user = self._user purchase_collection = views.PurchaseCollection(permitted_methods=('POST',)) response = purchase_collection.create(request) # Check response body_response = json.loads(response.content) self.assertEquals(body_response['result'], 'error') self.assertEquals(body_response['message'], 'Forbidden') self.assertEquals(response.status_code, 403) # Test Create contract exceptions # Load usdl info f = open('wstore/test/test_usdl2.ttl', 'rb') g = rdflib.Graph() g.parse(data=f.read(), format='n3') f.close() offering = Offering.objects.get(pk="71000aba8e05ac2115f022ff") offering.offering_description = json.loads(g.serialize(format='json-ld', auto_compact=True)) offering.save() from datetime import datetime purchase = Purchase.objects.create( customer=self._user, date=datetime.now(), offering=offering, organization_owned=False, state='paid', tax_address={ 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, bill=['/media/bills/11111111111.pdf'] ) from wstore.charging_engine.charging_engine import ChargingEngine # Check exceptions that can occur with multiple price plans when # creating the related purchase contract errors = { 'The price plan label is required to identify the plan': None, 'The specified plan does not exist': 'unexisting' } for err in errors: error = False msg = None try: charging = ChargingEngine(purchase, payment_method='paypal', plan=errors[err]) charging._create_purchase_contract() except Exception, e: error = True msg = e.message self.assertTrue(error) self.assertEquals(msg, err)
def test_basic_purchase_offering_update(self): # Get context cnt = Context.objects.all()[0] cnt.allowed_currencies = { "default": "EUR", "allowed": [{ "currency": "EUR", "in_use": True }] } cnt.save() self._user.userprofile.offerings_purchased.append('61000aba8e05ac2115f022f9') # Create the request data = { 'offering': { 'organization': 'test_organization', 'name': 'test_offering', 'version': '1.1' }, 'plan_label': 'update', 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment': { 'method': 'paypal' } } request = self.factory.post( '/api/contracting/', json.dumps(data), HTTP_ACCEPT='application/json; charset=utf-8', content_type='application/json; charset=utf-8' ) request.user = self._user # Test purchase view views.create_purchase = MagicMock(name='create_purchase') offering = Offering.objects.get(pk="71000aba8e05ac2115f022ff") from datetime import datetime purchase = Purchase.objects.create( customer=self._user, date=datetime.now(), offering=offering, organization_owned=False, state='paid', tax_address={ 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, bill=['/media/bills/11111111111.pdf'] ) views.create_purchase.return_value = purchase views.get_current_site = MagicMock(name='get_current_site') views.get_current_site.return_value = Site.objects.get(name='antares') views.Context.objects.get = MagicMock(name='get') context = MagicMock() context.user_refs = [] views.Context.objects.get.return_value = context purchase_collection = views.PurchaseCollection(permitted_methods=('POST',)) response = purchase_collection.create(request) # Check response body_response = json.loads(response.content) self.assertEquals(len(body_response['bill']), 1) self.assertEquals(body_response['bill'][0], '/media/bills/11111111111.pdf') payment_info = { 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment_method': 'paypal', 'plan': 'update', 'accepted': False } views.create_purchase.assert_called_once_with(self._user, offering, org_owned=False, payment_info=payment_info) offering.offering_description = { 'pricing': { 'price_plans': [] } } offering.save() from wstore.charging_engine.charging_engine import ChargingEngine charging = ChargingEngine(purchase, payment_method='paypal', plan='update') charging._create_purchase_contract() # Refresh purchase purchase = Purchase.objects.get(pk=purchase.pk) contract = purchase.contract self.assertFalse('single_payment' in contract.pricing_model) self.assertFalse('subscription' in contract.pricing_model) self.assertFalse('pay_per_use' in contract.pricing_model)
def create(self, request): order = None concept = None self.ordering_client = OrderingClient() try: # Extract payment information data = json.loads(request.body) if 'reference' not in data or 'paymentId' not in data or 'payerId' not in data: raise ValueError('Missing required field. It must contain reference, paymentId, and payerId') reference = data['reference'] token = data['paymentId'] payer_id = data['payerId'] if not Order.objects.filter(pk=reference): raise ValueError('The provided reference does not identify a valid order') 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(reference)}, {'$set': {'_lock': True}} ) # If the value of _lock before setting it to true was true, means # that the time out function has acquired it previously so the # view ends if not pre_value or '_lock' in pre_value and pre_value['_lock']: raise PaymentError('The timeout set to process the payment has finished') order = Order.objects.get(pk=reference) raw_order = self.ordering_client.get_order(order.order_id) pending_info = order.pending_payment concept = pending_info.concept # If the order state value is different from pending means that # the timeout function has completely ended before acquiring the resource # so _lock is set to false and the view ends if pre_value['state'] != 'pending': db.wstore_order.find_one_and_update( {'_id': ObjectId(reference)}, {'$set': {'_lock': False}} ) raise PaymentError('The timeout set to process the payment has finished') # Check that the request user is authorized to end the payment if request.user.userprofile.current_organization != order.owner_organization or request.user != order.customer: raise PaymentError('You are not authorized to execute the payment') transactions = pending_info.transactions # Get the payment client # 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(order) order.sales_ids = client.end_redirection_payment(token, payer_id) order.save() charging_engine = ChargingEngine(order) charging_engine.end_charging(transactions, pending_info.free_contracts, concept) except Exception as e: # Rollback the purchase if existing if order is not None and raw_order is not None: if concept == 'initial': # Set the order to failed in the ordering API # Set all items as Failed, mark the whole order as failed self.ordering_client.update_items_state(raw_order, 'Failed') order.delete() else: order.state = 'paid' order.pending_payment = None order.save() expl = ' due to an unexpected error' err_code = 500 if isinstance(e, PaymentError) or isinstance(e, ValueError): expl = ': ' + unicode(e) err_code = 403 msg = 'The payment has been canceled' + expl return build_response(request, err_code, msg) # Change states of TMForum resources (orderItems, products, etc) # depending on the concept of the payment states_processors = { 'initial': self._set_initial_states, 'recurring': self._set_renovation_states, 'usage': self._set_renovation_states } # Include the free contracts as transactions in order to activate them ext_transactions = deepcopy(transactions) ext_transactions.extend([{'item': contract.item_id} for contract in pending_info.free_contracts]) states_processors[concept](ext_transactions, raw_order, order) # _lock is set to false db.wstore_order.find_one_and_update( {'_id': ObjectId(reference)}, {'$set': {'_lock': False}} ) return build_response(request, 200, 'Ok')
def handle(self, *args, **options): """ This method is used to perform the charging process of the offerings that have pending SDR for more than a month """ now = time.mktime(datetime.now().timetuple()) if len(args) == 0: # Get contracts for contract in Contract.objects.all(): pending_sdrs = contract.pending_sdrs # If there are subscriptions the renovations are used as triggers if len(pending_sdrs) > 0 and (not 'subscription' in contract.pricing_model): time_stamp = time.mktime( pending_sdrs[0]['time_stamp'].timetuple()) if (time_stamp + 2592000) <= now: # A month # Get the related payment info purchase = contract.purchase if purchase.organization_owned: org = purchase.owner_organization payment_info = org.payment_info else: payment_info = purchase.customer.userprofile.payment_info charging = ChargingEngine(purchase, payment_method='credit_card', credit_card=payment_info) charging.resolve_charging(sdr=True) elif len(args) == 1: # Get the purchase try: purchase = Purchase.objects.get(ref=args[0]) except: raise Exception('The provided purchase does not exists') # Get the contract contract = purchase.contract # Check if there are pending SDRs if (len(contract.pending_sdrs) > 0): # Get payment info if purchase.organization_owned: org = purchase.owner_organization payment_info = org.payment_info else: payment_info = purchase.customer.userprofile.payment_info charging = ChargingEngine(purchase, payment_method='credit_card', credit_card=payment_info) charging.resolve_charging(sdr=True) else: raise Exception('No accounting info in the provided purchase') else: raise Exception('Invalid number of arguments')
def create(self, request): order = None concept = None self.ordering_client = OrderingClient() try: # Extract payment information data = json.loads(request.body) if 'reference' not in data or 'paymentId' not in data or 'payerId' not in data: raise ValueError('Missing required field. It must contain reference, paymentId, and payerId') reference = data['reference'] token = data['paymentId'] payer_id = data['payerId'] if not Order.objects.filter(pk=reference): raise ValueError('The provided reference does not identify a valid order') 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(reference)}, {'$set': {'_lock': True}} ) # If the value of _lock before setting it to true was true, means # that the time out function has acquired it previously so the # view ends if not pre_value or '_lock' in pre_value and pre_value['_lock']: raise PaymentError('The timeout set to process the payment has finished') order = Order.objects.get(pk=reference) raw_order = self.ordering_client.get_order(order.order_id) pending_info = order.pending_payment concept = pending_info['concept'] # If the order state value is different from pending means that # the timeout function has completely ended before acquiring the resource # so _lock is set to false and the view ends if pre_value['state'] != 'pending': db.wstore_order.find_one_and_update( {'_id': ObjectId(reference)}, {'$set': {'_lock': False}} ) raise PaymentError('The timeout set to process the payment has finished') # Check that the request user is authorized to end the payment if request.user.userprofile.current_organization != order.owner_organization: raise PaymentError('You are not authorized to execute the payment') transactions = pending_info['transactions'] # Get the payment client # 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(order) order.sales_ids = client.end_redirection_payment(token, payer_id) order.save() charging_engine = ChargingEngine(order) charging_engine.end_charging(transactions, concept) except Exception as e: # Rollback the purchase if existing if order is not None and raw_order is not None: if concept == 'initial': # Set the order to failed in the ordering API # Set all items as Failed, mark the whole order as failed self.ordering_client.update_items_state(raw_order, 'Failed') order.delete() else: order.state = 'paid' order.pending_payment = {} order.save() expl = ' due to an unexpected error' err_code = 500 if isinstance(e, PaymentError) or isinstance(e, ValueError): expl = ': ' + unicode(e) err_code = 403 msg = 'The payment has been canceled' + expl return build_response(request, err_code, msg) # Change states of TMForum resources (orderItems, products, etc) # depending on the concept of the payment states_processors = { 'initial': self._set_initial_states, 'renovation': self._set_renovation_states, 'use': self._set_renovation_states } states_processors[concept](transactions, raw_order, order) # _lock is set to false db.wstore_order.find_one_and_update( {'_id': ObjectId(reference)}, {'$set': {'_lock': False}} ) return build_response(request, 200, 'Ok')
def read(self, request, reference): purchase = None try: token = request.GET.get('token') payer_id = request.GET.get('PayerID', '') db = get_database_connection() # Uses an atomic operation to get and set the _lock value in the purchase # document pre_value = db.wstore_purchase.find_and_modify( query={'_id': ObjectId(reference)}, update={'$set': {'_lock': True}} ) # If the value of _lock before setting it to true was true, means # that the time out function has acquired it previously so the # view ends if '_lock' in pre_value and pre_value['_lock']: raise Exception('') purchase = Purchase.objects.get(ref=reference) # Check that the request user is authorized to end the payment if request.user.userprofile.current_organization != purchase.owner_organization: raise Exception() # If the purchase state value is different from pending means that # the timeout function has completely ended before acquire the resource # so _lock is set to false and the view ends if purchase.state != 'pending': db.wstore_purchase.find_and_modify( query={'_id': ObjectId(reference)}, update={'$set': {'_lock': False}} ) raise Exception('') pending_info = purchase.contract.pending_payment # Get the payment client # Load payment client cln_str = settings.PAYMENT_CLIENT client_class = cln_str.split('.')[-1] client_package = cln_str.partition('.' + client_class)[0] payment_client = getattr(__import__(client_package, globals(), locals(), [client_class], -1), client_class) # build the payment client client = payment_client(purchase) client.end_redirection_payment(token, payer_id) charging_engine = ChargingEngine(purchase) accounting = None if 'accounting' in pending_info: accounting = pending_info['accounting'] charging_engine.end_charging(pending_info['price'], pending_info['concept'], pending_info['related_model'], accounting) except: # Rollback the purchase if existing if purchase is not None: rollback(purchase) context = { 'title': 'Payment Canceled', 'message': 'Your payment has been canceled. An error occurs or the timeout has finished, if you want to acquire the offering purchase it again in WStore.' } return render(request, 'err_msg.html', context) # Check if is the first payment if len(purchase.contract.charges) == 1: if purchase.organization_owned: org = purchase.owner_organization org.offerings_purchased.append(purchase.offering.pk) org.save() else: # Add the offering to the user profile user_profile = UserProfile.objects.get(user=purchase.customer) user_profile.offerings_purchased.append(purchase.offering.pk) user_profile.save() notify_provider(purchase) # _lock is set to false db.wstore_purchase.find_and_modify( query={'_id': reference}, update={'$set': {'_lock': False}} ) # Return the confirmation web page context = { 'title': 'Payment Confirmed', 'message': 'Your payment has been received. To download the resources and the invoice go to the offering details page.' } return render(request, 'err_msg.html', context)
def test_purchase_offering_update_payment(self): current_org = Organization.objects.get(pk="91000aba8e06ac2199999999") current_org.offerings_purchased.append('61000aba8e05ac2115f022f9') self._user.userprofile.current_organization = current_org self._user.userprofile.get_current_roles = MagicMock() self._user.userprofile.get_current_roles.return_value = ['customer'] self._user.userprofile.save() # Create the request data = { 'offering': { 'organization': 'test_organization', 'name': 'test_offering', 'version': '1.1' }, 'plan_label': 'update', 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment': { 'method': 'paypal' } } request = self.factory.post( '/api/contracting/', json.dumps(data), HTTP_ACCEPT='application/json; charset=utf-8', content_type='application/json; charset=utf-8') request.user = self._user # Test purchase view views.create_purchase = MagicMock(name='create_purchase') offering = Offering.objects.get(pk="71000aba8e05ac2115f022ff") from datetime import datetime purchase = Purchase.objects.create( customer=self._user, date=datetime.now(), offering=offering, organization_owned=True, state='paid', tax_address={ 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, bill=['/media/bills/11111111111.pdf']) views.create_purchase.return_value = purchase views.get_current_site = MagicMock(name='get_current_site') views.get_current_site.return_value = Site.objects.get(name='antares') views.Context.objects.get = MagicMock(name='get') context = MagicMock() context.user_refs = [] views.Context.objects.get.return_value = context purchase_collection = views.PurchaseCollection( permitted_methods=('POST', )) response = purchase_collection.create(request) # Check response body_response = json.loads(response.content) self.assertEquals(len(body_response['bill']), 1) self.assertEquals(body_response['bill'][0], '/media/bills/11111111111.pdf') payment_info = { 'tax_address': { 'street': 'test street', 'postal': '28000', 'city': 'test city', 'country': 'test country' }, 'payment_method': 'paypal', 'plan': 'update', 'accepted': False } views.create_purchase.assert_called_once_with( self._user, offering, org_owned=True, payment_info=payment_info) # Test Contract creation offering.offering_description = { 'pricing': { 'price_plans': [{ 'title': 'Price plan', 'currency': 'EUR', 'price_components': [{ 'label': 'Price component update', 'unit': 'single payment', 'value': '1.0' }] }] } } offering.save() from wstore.charging_engine.charging_engine import ChargingEngine charging = ChargingEngine(purchase, payment_method='paypal', plan='update') charging._create_purchase_contract() # Refresh purchase purchase = Purchase.objects.get(pk=purchase.pk) contract = purchase.contract # Check contract pricing model self.assertTrue('single_payment' in contract.pricing_model) self.assertEquals(len(contract.pricing_model['single_payment']), 1) payment = contract.pricing_model['single_payment'][0] self.assertEquals(payment['label'], 'Price component update') self.assertEquals(payment['value'], '1.0') self.assertFalse('subscription' in contract.pricing_model) self.assertFalse('pay_per_use' in contract.pricing_model)
def read(self, request, reference): purchase = None try: token = request.GET.get('token') payer_id = request.GET.get('PayerID', '') db = get_database_connection() # Uses an atomic operation to get and set the _lock value in the purchase # document pre_value = db.wstore_purchase.find_and_modify( query={'_id': ObjectId(reference)}, update={'$set': { '_lock': True }}) # If the value of _lock before setting it to true was true, means # that the time out function has acquired it previously so the # view ends if '_lock' in pre_value and pre_value['_lock']: raise Exception('') purchase = Purchase.objects.get(ref=reference) # Check that the request user is authorized to end the payment if request.user.userprofile.current_organization != purchase.owner_organization: raise Exception() # If the purchase state value is different from pending means that # the timeout function has completely ended before acquire the resource # so _lock is set to false and the view ends if purchase.state != 'pending': db.wstore_purchase.find_and_modify( query={'_id': ObjectId(reference)}, update={'$set': { '_lock': False }}) raise Exception('') pending_info = purchase.contract.pending_payment # Get the payment client # Load payment client cln_str = settings.PAYMENT_CLIENT client_class = cln_str.split('.')[-1] client_package = cln_str.partition('.' + client_class)[0] payment_client = getattr( __import__(client_package, globals(), locals(), [client_class], -1), client_class) # build the payment client client = payment_client(purchase) client.end_redirection_payment(token, payer_id) charging_engine = ChargingEngine(purchase) accounting = None if 'accounting' in pending_info: accounting = pending_info['accounting'] charging_engine.end_charging(pending_info['price'], pending_info['concept'], pending_info['related_model'], accounting) except: # Rollback the purchase if existing if purchase is not None: rollback(purchase) context = { 'title': 'Payment Canceled', 'message': 'Your payment has been canceled. An error occurs or the timeout has finished, if you want to acquire the offering purchase it again in WStore.' } return render(request, 'err_msg.html', context) # Check if is the first payment if len(purchase.contract.charges) == 1: if purchase.organization_owned: org = purchase.owner_organization org.offerings_purchased.append(purchase.offering.pk) org.save() else: # Add the offering to the user profile user_profile = UserProfile.objects.get(user=purchase.customer) user_profile.offerings_purchased.append(purchase.offering.pk) user_profile.save() notify_provider(purchase) # _lock is set to false db.wstore_purchase.find_and_modify(query={'_id': reference}, update={'$set': { '_lock': False }}) # Return the confirmation web page context = { 'title': 'Payment Confirmed', 'message': 'Your payment has been received. To download the resources and the invoice go to the offering details page.' } return render(request, 'err_msg.html', context)