def end_redirection_payment(self, token, payer_id): payment = paypalrestsdk.Payment.find(token) if not payment.execute({"payer_id": payer_id}): raise PaymentError("The payment cannot be executed: " + payment.error) sales_ids = [] response = payment.to_dict() for t in response['transactions']: for r in t['related_resources']: sales_ids.append(r['sale']['id']) return sales_ids
def start_redirection_payment(self, transactions): # Build URL url = Context.objects.all()[0].site.domain if url[-1] != '/': url += '/' # Build payment object payment = paypalrestsdk.Payment({ 'intent': 'sale', 'payer': { 'payment_method': 'paypal' }, 'redirect_urls': { 'return_url': url + 'payment?action=accept&ref=' + self._order.pk, 'cancel_url': url + 'payment?action=cancel&ref=' + self._order.pk }, 'transactions': [{ 'amount': { 'total': unicode(t['price']), 'currency': t['currency'] }, 'description': t['description'] } for t in transactions] }) # Create Payment if not payment.create(): # Check if the error is due to a problem supporting multiple transactions details = payment.error['details'] if len(transactions) > 1 and len(details) == 1 and \ details[0]['issue'] == 'Only single payment transaction currently supported': # Aggregate transactions in a single payment if possible current_curr = transactions[0]['currency'] total = Decimal('0') items = '' for t in transactions: # Only if all the transactions have the same currency they can be aggregated if t['currency'] != current_curr: break total += Decimal(t['price']) items += t['item'] + ':' + t['price'] + '<' + t['description'] + '>' else: msg = 'All your order items have been aggregated, since PayPal is not able ' msg += 'to process multiple transactions in this moment. ' msg += 'Order composed of the following items ' + items self.start_redirection_payment([{ 'price': unicode(total), 'currency': current_curr, 'description': msg }]) return raise PaymentError("The payment cannot be created: " + details[0]["issue"]) # Extract URL where redirecting the customer response = payment.to_dict() for l in response['links']: if l['rel'] == 'approval_url': self._checkout_url = l['href'] break
def refund(self, sale_id): sale = paypalrestsdk.Sale.find(sale_id) if not sale.refund({}): raise PaymentError("The refund cannot be completed: " + sale.error)
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')