Example #1
0
    def normalize_processor_response(self, response):
        # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling
        # and logging the exception.
        try:
            decision = Decision(response['decision'].upper())
        except ValueError:
            decision = response['decision']

        _response = UnhandledCybersourceResponse(
            decision=decision,
            duplicate_payment=(decision == Decision.error
                               and int(response['reason_code']) == 104),
            partial_authorization=(
                'auth_amount' in response and response['auth_amount']
                and response['auth_amount'] != response['req_amount']),
            currency=response['req_currency'],
            total=Decimal(response['req_amount']),
            card_number=response['req_card_number'],
            card_type=CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type']),
            transaction_id=response.get(
                'transaction_id',
                ''),  # Error Notifications do not include a transaction id.
            order_id=response['req_reference_number'],
            raw_json=self.serialize_order_completion(response),
        )
        return _response
Example #2
0
    def handle_processor_response(self, response, basket=None):
        """
        Handle a response (i.e., "merchant notification") from CyberSource.

        Arguments:
            response (dict): Dictionary of parameters received from the payment processor.

        Keyword Arguments:
            basket (Basket): Basket being purchased via the payment processor.

        Raises:
            UserCancelled: Indicates the user cancelled payment.
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
            InvalidCyberSourceDecision: Indicates an unknown decision value.
                Known values are ACCEPT, CANCEL, DECLINE, ERROR.
            PartialAuthorizationError: Indicates only a portion of the requested amount was authorized.

        Returns:
            HandledProcessorResponse
        """

        # Validate the signature
        if not self.is_signature_valid(response):
            raise InvalidSignatureError

        # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling
        # and logging the exception.
        decision = response['decision'].lower()
        if decision != 'accept':
            reason_code = int(response['reason_code'])

            if decision == 'error' and reason_code == 104:
                raise DuplicateReferenceNumber

            raise {
                'cancel': UserCancelled,
                'decline': TransactionDeclined,
                'error': GatewayError
            }.get(decision, InvalidCybersourceDecision)

        # Raise an exception if the authorized amount differs from the requested amount.
        # Note (CCB): We should never reach this point in production since partial authorization is disabled
        # for our account, and should remain that way until we have a proper solution to allowing users to
        # complete authorization for the entire order.
        if response['auth_amount'] != response['req_amount']:
            raise PartialAuthorizationError

        currency = response['req_currency']
        total = Decimal(response['req_amount'])
        transaction_id = response['transaction_id']
        card_number = response['req_card_number']
        card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type'])

        return HandledProcessorResponse(transaction_id=transaction_id,
                                        total=total,
                                        currency=currency,
                                        card_number=card_number,
                                        card_type=card_type)
Example #3
0
    def handle_processor_response(self, response, basket=None):
        """
        Handle a response (i.e., "merchant notification") from CyberSource.

        Arguments:
            response (dict): Dictionary of parameters received from the payment processor.

        Keyword Arguments:
            basket (Basket): Basket being purchased via the payment processor.

        Raises:
            UserCancelled: Indicates the user cancelled payment.
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
            InvalidCyberSourceDecision: Indicates an unknown decision value.
                Known values are ACCEPT, CANCEL, DECLINE, ERROR.
            PartialAuthorizationError: Indicates only a portion of the requested amount was authorized.

        Returns:
            HandledProcessorResponse
        """

        # Validate the signature
        if not self.is_signature_valid(response):
            raise InvalidSignatureError

        # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling
        # and logging the exception.
        decision = response['decision'].lower()
        if decision != 'accept':
            exception = {
                'cancel': UserCancelled,
                'decline': TransactionDeclined,
                'error': GatewayError
            }.get(decision, InvalidCybersourceDecision)

            raise exception

        # Raise an exception if the authorized amount differs from the requested amount.
        # Note (CCB): We should never reach this point in production since partial authorization is disabled
        # for our account, and should remain that way until we have a proper solution to allowing users to
        # complete authorization for the entire order.
        if response['auth_amount'] != response['req_amount']:
            raise PartialAuthorizationError

        currency = response['req_currency']
        total = Decimal(response['req_amount'])
        transaction_id = response['transaction_id']
        card_number = response['req_card_number']
        card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type'])

        return HandledProcessorResponse(
            transaction_id=transaction_id,
            total=total,
            currency=currency,
            card_number=card_number,
            card_type=card_type
        )
Example #4
0
    def normalize_processor_response(self, response):
        decision_map = {
            'AUTHORIZED': Decision.accept,
            'PARTIAL_AUTHORIZED': Decision.decline,
            'AUTHORIZED_PENDING_REVIEW': Decision.review,
            'AUTHORIZED_RISK_DECLINED': Decision.decline,
            'PENDING_AUTHENTICATION': Decision.decline,
            'PENDING_REVIEW': Decision.review,
            'DECLINED': Decision.decline,
            'INVALID_REQUEST': Decision.error,
        }
        response_json = self.serialize_order_completion(response)

        if isinstance(response, ApiException):
            decision = decision_map.get(response_json.get('status'),
                                        response_json.get('status'))

            return UnhandledCybersourceResponse(
                decision=decision,
                duplicate_payment=(decision == Decision.error
                                   and response_json.get('reason')
                                   == 'DUPLICATE_REQUEST'),
                partial_authorization=False,
                currency=None,
                total=None,
                card_number=None,
                card_type=None,
                transaction_id=None,
                order_id=None,
                raw_json=response_json,
            )

        decoded_capture_context = jwt.decode(self.capture_context['key_id'],
                                             verify=False)
        jwk = RSAAlgorithm.from_jwk(
            json.dumps(decoded_capture_context['flx']['jwk']))
        decoded_payment_token = jwt.decode(self.transient_token_jwt,
                                           key=jwk,
                                           algorithms=['RS256'])
        decision = decision_map.get(response.status, response.status)

        return UnhandledCybersourceResponse(
            decision=decision,
            duplicate_payment=(decision == Decision.error
                               and response.reason == 'DUPLICATE_REQUEST'),
            partial_authorization=response.status == 'PARTIAL_AUTHORIZED',
            currency=response.order_information.amount_details.currency,
            total=Decimal(
                response.order_information.amount_details.total_amount),
            card_number=decoded_payment_token['data']['number'],
            card_type=CYBERSOURCE_CARD_TYPE_MAP.get(
                response.payment_information.tokenized_card.type),
            transaction_id=response.processor_information.transaction_id,
            order_id=response.client_reference_information.code,
            raw_json=response_json,
        )
Example #5
0
    def request_apple_pay_authorization(self, basket, billing_address,
                                        payment_token):
        """
        Authorizes an Apple Pay payment.

        For details on the process, see the CyberSource Simple Order API documentation at
        https://www.cybersource.com/developers/integration_methods/apple_pay/.

        Args:
            basket (Basket)
            billing_address (BillingAddress)
            payment_token (dict)

        Returns:
            HandledProcessorResponse

        Raises:
            GatewayError
        """
        try:
            client = Client(self.soap_api_url,
                            wsse=UsernameToken(self.merchant_id,
                                               self.transaction_key))
            card_type = APPLE_PAY_CYBERSOURCE_CARD_TYPE_MAP[
                payment_token['paymentMethod']['network'].lower()]
            bill_to = {
                'firstName': billing_address.first_name,
                'lastName': billing_address.last_name,
                'street1': billing_address.line1,
                'street2': billing_address.line2,
                'city': billing_address.line4,
                'state': billing_address.state,
                'postalCode': billing_address.postcode,
                'country': billing_address.country.iso_3166_1_a2,
                'email': basket.owner.email,
            }
            purchase_totals = {
                'currency': basket.currency,
                'grandTotalAmount': str(basket.total_incl_tax),
            }
            encrypted_payment = {
                'descriptor': 'RklEPUNPTU1PTi5BUFBMRS5JTkFQUC5QQVlNRU5U',
                'data':
                base64.b64encode(json.dumps(payment_token['paymentData'])),
                'encoding': 'Base64',
            }
            card = {
                'cardType': card_type,
            }
            auth_service = {
                'run': 'true',
            }
            capture_service = {
                'run': 'true',
            }
            # Enable Export Compliance for SDN validation, amongst other checks.
            # See https://www.cybersource.com/products/fraud_management/export_compliance/
            export_service = {
                'run': 'true',
            }
            item = [{
                'id': index,
                'productCode': line.product.get_product_class().slug,
                'productName': clean_field_value(line.product.title),
                'quantity': line.quantity,
                'productSKU': line.stockrecord.partner_sku,
                'taxAmount': str(line.line_tax),
                'unitPrice': str(line.unit_price_incl_tax),
            } for index, line in enumerate(basket.all_lines())]

            response = client.service.runTransaction(
                merchantID=self.merchant_id,
                merchantReferenceCode=basket.order_number,
                billTo=bill_to,
                purchaseTotals=purchase_totals,
                encryptedPayment=encrypted_payment,
                card=card,
                ccAuthService=auth_service,
                ccCaptureService=capture_service,
                exportService=export_service,
                paymentSolution='001',
                item=item,
            )

        except:
            msg = 'An error occurred while authorizing an Apple Pay (via CyberSource) for basket [{}]'.format(
                basket.id)
            logger.exception(msg)
            raise GatewayError(msg)

        request_id = response.requestID
        ppr = self.record_processor_response(serialize_object(response),
                                             transaction_id=request_id,
                                             basket=basket)

        if response.decision == 'ACCEPT':
            currency = basket.currency
            total = basket.total_incl_tax
            transaction_id = request_id

            return HandledProcessorResponse(
                transaction_id=transaction_id,
                total=total,
                currency=currency,
                card_number='Apple Pay',
                card_type=CYBERSOURCE_CARD_TYPE_MAP.get(card_type))
        else:
            msg = (
                'CyberSource rejected an Apple Pay authorization request for basket [{basket_id}]. '
                'Complete response has been recorded in entry [{response_id}]')
            msg = msg.format(basket_id=basket.id, response_id=ppr.id)
            logger.warning(msg)
        raise GatewayError(msg)
Example #6
0
    def handle_processor_response(self, response, basket=None):
        """
        Handle a response (i.e., "merchant notification") from CyberSource.

        Arguments:
            response (dict): Dictionary of parameters received from the payment processor.

        Keyword Arguments:
            basket (Basket): Basket being purchased via the payment processor.

        Raises:
            AuthorizationError: Authorization was declined.
            UserCancelled: Indicates the user cancelled payment.
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
            InvalidCyberSourceDecision: Indicates an unknown decision value.
                Known values are ACCEPT, CANCEL, DECLINE, ERROR, REVIEW.
            PartialAuthorizationError: Indicates only a portion of the requested amount was authorized.

        Returns:
            HandledProcessorResponse
        """

        # Validate the signature
        if not self.is_signature_valid(response):
            raise InvalidSignatureError

        # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling
        # and logging the exception.
        decision = response['decision'].lower()
        if decision != 'accept':
            reason_code = int(response['reason_code'])

            if decision == 'error' and reason_code == 104:
                # This means user submitted payment request twice within 15 min.
                # We need to check if user first payment notification was handled successfuly and user has an order
                # if user has an order we can raise DuplicateReferenceNumber exception else we need to continue
                # the order creation process. to upgrade user in correct course mode.
                if Order.objects.filter(
                        number=response['req_reference_number']).exists():
                    raise DuplicateReferenceNumber
                else:
                    logger.info(
                        'Received duplicate CyberSource payment notification for basket [%d] which is not associated '
                        'with any existing order. Continuing to validation and order creation processes.',
                        basket.id,
                    )
            else:
                raise {
                    'cancel': UserCancelled,
                    'decline': TransactionDeclined,
                    'error': GatewayError,
                    'review': AuthorizationError,
                }.get(decision, InvalidCybersourceDecision)

        # Raise an exception if the authorized amount differs from the requested amount.
        # Note (CCB): We should never reach this point in production since partial authorization is disabled
        # for our account, and should remain that way until we have a proper solution to allowing users to
        # complete authorization for the entire order
        if response['auth_amount'] and response['auth_amount'] != response[
                'req_amount']:
            raise PartialAuthorizationError

        currency = response['req_currency']
        total = Decimal(response['req_amount'])
        transaction_id = response.get(
            'transaction_id',
            None)  # Error Notifications does not include a transaction id.
        card_number = response['req_card_number']
        card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type'])

        return HandledProcessorResponse(transaction_id=transaction_id,
                                        total=total,
                                        currency=currency,
                                        card_number=card_number,
                                        card_type=card_type)
Example #7
0
    def handle_processor_response(self, response, basket=None):
        """
        Handle a response (i.e., "merchant notification") from CyberSource.

        This method does the following:
            1. Verify the validity of the response.
            2. Create PaymentEvents and Sources for successful payments.

        Arguments:
            response (dict): Dictionary of parameters received from the payment processor.

        Keyword Arguments:
            basket (Basket): Basket being purchased via the payment processor.

        Raises:
            UserCancelled: Indicates the user cancelled payment.
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
            InvalidCyberSourceDecision: Indicates an unknown decision value.
                Known values are ACCEPT, CANCEL, DECLINE, ERROR.
            PartialAuthorizationError: Indicates only a portion of the requested amount was authorized.
        """

        # Validate the signature
        if not self.is_signature_valid(response):
            raise InvalidSignatureError

        # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling
        # and logging the exception.
        decision = response['decision'].lower()
        if decision != 'accept':
            exception = {
                'cancel': UserCancelled,
                'decline': TransactionDeclined,
                'error': GatewayError
            }.get(decision, InvalidCybersourceDecision)

            raise exception

        # Raise an exception if the authorized amount differs from the requested amount.
        # Note (CCB): We should never reach this point in production since partial authorization is disabled
        # for our account, and should remain that way until we have a proper solution to allowing users to
        # complete authorization for the entire order.
        if response['auth_amount'] != response['req_amount']:
            raise PartialAuthorizationError

        # Create Source to track all transactions related to this processor and order
        source_type, __ = SourceType.objects.get_or_create(name=self.NAME)
        currency = response['req_currency']
        total = Decimal(response['req_amount'])
        transaction_id = response['transaction_id']
        req_card_number = response['req_card_number']
        card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type'])

        source = Source(source_type=source_type,
                        currency=currency,
                        amount_allocated=total,
                        amount_debited=total,
                        reference=transaction_id,
                        label=req_card_number,
                        card_type=card_type)

        # Create PaymentEvent to track
        event_type, __ = PaymentEventType.objects.get_or_create(name=PaymentEventTypeName.PAID)
        event = PaymentEvent(event_type=event_type, amount=total, reference=transaction_id, processor_name=self.NAME)

        return source, event
Example #8
0
    def fulfill_basket(self, basket_id, site):

        logger.info('Trying to complete order for frozen basket %d', basket_id)

        basket = self.get_valid_basket(basket_id)

        if not basket:
            return False

        # We need to check if an order for basket exists.
        try:
            order = Order.objects.get(basket=basket)
            logger.info('Basket %d does have a existing order %s', basket.id,
                        basket.order_number)
        except Order.DoesNotExist:
            order = None

        # if no order exists we need to create a new order.
        if not order:
            basket.strategy = strategy.Default()

            # Need to handle the case that applied voucher has been expired.
            # This will create the order  with out discount but subsequently
            # run the fulfillment to update course mode.
            try:
                Applicator().apply(basket, user=basket.owner)
            except ValueError:
                basket.clear_vouchers()

            payment_notification = self.get_payment_notification(basket)
            if not payment_notification:
                return False

            if payment_notification.transaction_id.startswith('PAY'):
                card_number = 'Paypal Account'
                card_type = None
            else:
                card_number = payment_notification.response['req_card_number']
                card_type = CYBERSOURCE_CARD_TYPE_MAP.get(
                    payment_notification.response['req_card_type'])

            self.payment_processor = _get_payment_processor(
                site, payment_notification.processor_name)
            # Create handled response
            handled_response = HandledProcessorResponse(
                transaction_id=payment_notification.transaction_id,
                total=basket.total_excl_tax,
                currency=basket.currency,
                card_number=card_number,
                card_type=card_type)

            # Record Payment and try to place order
            try:
                self.record_payment(
                    basket=basket, handled_processor_response=handled_response)

                shipping_method = NoShippingRequired()
                shipping_charge = shipping_method.calculate(basket)
                order_total = OrderTotalCalculator().calculate(
                    basket, shipping_charge)

                user = basket.owner
                # Given a basket, order number generation is idempotent. Although we've already
                # generated this order number once before, it's faster to generate it again
                # than to retrieve an invoice number from PayPal.
                order_number = basket.order_number

                self.handle_order_placement(
                    order_number=order_number,
                    user=user,
                    basket=basket,
                    shipping_address=None,
                    shipping_method=shipping_method,
                    shipping_charge=shipping_charge,
                    billing_address=None,
                    order_total=order_total,
                )
                logger.info('Successfully created order for basket %d',
                            basket.id)
                return True
            except:  # pylint: disable=bare-except
                logger.exception('Unable to create order for basket %d',
                                 basket.id)
                return False

        # Start order fulfillment if order exists but is not fulfilled.
        elif order.is_fulfillable:
            order_lines = order.lines.all()
            line_quantities = [line.quantity for line in order_lines]

            shipping_event, __ = ShippingEventType.objects.get_or_create(
                name=SHIPPING_EVENT_NAME)
            EventHandler().handle_shipping_event(order, shipping_event,
                                                 order_lines, line_quantities)

            if order.is_fulfillable:
                logger.error('Unable to fulfill order for basket %d',
                             basket.id)
                return False
        return True
Example #9
0
    def handle_processor_response(self, response, basket=None):
        """
        Handle a response (i.e., "merchant notification") from CyberSource.

        This method does the following:
            1. Verify the validity of the response.
            2. Create PaymentEvents and Sources for successful payments.

        Arguments:
            response (dict): Dictionary of parameters received from the payment processor.

        Keyword Arguments:
            basket (Basket): Basket being purchased via the payment processor.

        Raises:
            UserCancelled: Indicates the user cancelled payment.
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
            InvalidCyberSourceDecision: Indicates an unknown decision value.
                Known values are ACCEPT, CANCEL, DECLINE, ERROR.
            PartialAuthorizationError: Indicates only a portion of the requested amount was authorized.
        """

        # Validate the signature
        if not self.is_signature_valid(response):
            raise InvalidSignatureError

        # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling
        # and logging the exception.
        decision = response[u'decision'].lower()
        if decision != u'accept':
            exception = {
                u'cancel': UserCancelled,
                u'decline': TransactionDeclined,
                u'error': GatewayError
            }.get(decision, InvalidCybersourceDecision)

            raise exception

        # Raise an exception if the authorized amount differs from the requested amount.
        # Note (CCB): We should never reach this point in production since partial authorization is disabled
        # for our account, and should remain that way until we have a proper solution to allowing users to
        # complete authorization for the entire order.
        if response[u'auth_amount'] != response[u'req_amount']:
            raise PartialAuthorizationError

        # Create Source to track all transactions related to this processor and order
        source_type, __ = SourceType.objects.get_or_create(name=self.NAME)
        currency = response[u'req_currency']
        total = Decimal(response[u'req_amount'])
        transaction_id = response[u'transaction_id']
        req_card_number = response[u'req_card_number']
        card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response[u'req_card_type'])

        source = Source(source_type=source_type,
                        currency=currency,
                        amount_allocated=total,
                        amount_debited=total,
                        reference=transaction_id,
                        label=req_card_number,
                        card_type=card_type)

        # Create PaymentEvent to track
        event_type, __ = PaymentEventType.objects.get_or_create(
            name=PaymentEventTypeName.PAID)
        event = PaymentEvent(event_type=event_type,
                             amount=total,
                             reference=transaction_id,
                             processor_name=self.NAME)

        return source, event
Example #10
0
    def fulfill_basket(self, basket_id, site):

        logger.info('Trying to create order for frozen basket %d', basket_id)

        try:
            basket = Basket.objects.get(id=basket_id)
        except Basket.DoesNotExist:
            logger.info('Basket %d does not exist', basket_id)
            return False

        basket.strategy = strategy.Default()
        Applicator().apply(basket, user=basket.owner)

        transaction_responses = basket.paymentprocessorresponse_set.filter(
            transaction_id__isnull=False)
        unique_transaction_ids = set(
            [response.transaction_id for response in transaction_responses])

        if len(unique_transaction_ids) > 1:
            logger.info(
                'Basket %d has more than one transaction id, not Fulfilling',
                basket_id)
            return False

        response = transaction_responses[0]
        if response.transaction_id.startswith('PAY'):
            card_number = 'PayPal Account'
            card_type = None
            self.payment_processor = _get_payment_processor(site, 'paypal')
        else:
            card_number = response.response['req_card_number']
            card_type = CYBERSOURCE_CARD_TYPE_MAP.get(
                response.response['req_card_type'])
            self.payment_processor = _get_payment_processor(
                site, 'cybersource')

        handled_response = HandledProcessorResponse(
            transaction_id=response.transaction_id,
            total=basket.total_excl_tax,
            currency=basket.currency,
            card_number=card_number,
            card_type=card_type)

        try:
            self.record_payment(basket=basket,
                                handled_processor_response=handled_response)

            shipping_method = NoShippingRequired()
            shipping_charge = shipping_method.calculate(basket)
            order_total = OrderTotalCalculator().calculate(
                basket, shipping_charge)

            user = basket.owner
            # Given a basket, order number generation is idempotent. Although we've already
            # generated this order number once before, it's faster to generate it again
            # than to retrieve an invoice number from PayPal.
            order_number = basket.order_number

            self.handle_order_placement(
                order_number=order_number,
                user=user,
                basket=basket,
                shipping_address=None,
                shipping_method=shipping_method,
                shipping_charge=shipping_charge,
                billing_address=None,
                order_total=order_total,
            )

            return True
        except:  # pylint: disable=bare-except
            logger.exception('Unable to fulfill basket %d', basket.id)
            return False
Example #11
0
    def normalize_processor_response(self,
                                     response) -> UnhandledCybersourceResponse:
        decision_map = {
            'AUTHORIZED': Decision.accept,
            'PARTIAL_AUTHORIZED': Decision.decline,
            'AUTHORIZED_PENDING_REVIEW': Decision.authorized_pending_review,
            'AUTHORIZED_RISK_DECLINED': Decision.decline,
            'PENDING_AUTHENTICATION': Decision.decline,
            'PENDING_REVIEW': Decision.review,
            'DECLINED': Decision.decline,
            'INVALID_REQUEST': Decision.error,
        }
        response_json = self.serialize_order_completion(response)

        if isinstance(response, ApiException):
            decision = decision_map.get(response_json.get('status'),
                                        response_json.get('status'))

            return UnhandledCybersourceResponse(
                decision=decision,
                duplicate_payment=(decision == Decision.error
                                   and response_json.get('reason')
                                   == 'DUPLICATE_REQUEST'),
                partial_authorization=False,
                currency=None,
                total=None,
                card_number=None,
                card_type=None,
                transaction_id=None,
                order_id=None,
                raw_json=response_json,
            )

        decision = decision_map.get(response.status, response.status)

        currency = None
        total = None
        amount_details = response.order_information and response.order_information.amount_details
        if amount_details:
            currency = amount_details.currency
            total = (amount_details.total_amount
                     or amount_details.authorized_amount)
        if total:
            total = Decimal(total)

        card = response.payment_information and response.payment_information.tokenized_card
        card_type = card and CYBERSOURCE_CARD_TYPE_MAP.get(card.type)

        return UnhandledCybersourceResponse(
            decision=decision,
            duplicate_payment=(decision == Decision.error
                               and response.reason == 'DUPLICATE_REQUEST'),
            partial_authorization=response.status == 'PARTIAL_AUTHORIZED',
            currency=currency,
            total=total,
            card_number=response.decoded_payment_token['data']['number'],
            card_type=card_type,
            transaction_id=response.id,
            order_id=response.client_reference_information.code,
            raw_json=response_json,
        )
Example #12
0
    def fulfill_basket(self, basket_id, site):

        logger.info('Trying to complete order for frozen basket %d', basket_id)

        # Validate the basket.
        try:
            basket = Basket.objects.get(id=basket_id)
        except Basket.DoesNotExist:
            logger.info('Basket %d does not exist', basket_id)
            return False

        # Make sure basket is Frozen which means payment was done.
        if basket.status != basket.FROZEN:
            return False

        # We need to check if an order for basket exists.
        try:
            order = Order.objects.get(basket=basket)
            logger.info('Basket %d does have a existing order %s', basket.id,
                        basket.order_number)
        except Order.DoesNotExist:
            order = None

        # if no order exists we need to create a new order.
        if not order:
            basket.strategy = strategy.Default()

            # Need to handle the case that applied voucher has been expired.
            # This will create the order  with out discount but subsequently
            # run the fulfillment to update course mode.
            try:
                Applicator().apply(basket, user=basket.owner)
            except ValueError:
                basket.clear_vouchers()

            # Filter the successful payment processor response which in case
            # of Cybersource includes "u'decision': u'ACCEPT'" and in case of
            # Paypal includes "u'state': u'approved'".
            successful_transaction = basket.paymentprocessorresponse_set.filter(
                Q(response__contains='ACCEPT')
                | Q(response__contains='approved'))

            # In case of no successful transactions log and stop the process.
            if not successful_transaction:
                logger.info(
                    'Basket %d does not have any successful payment response',
                    basket_id)
                return False

            unique_transaction_ids = set([
                response.transaction_id for response in successful_transaction
            ])
            if len(unique_transaction_ids) > 1:
                logger.warning(
                    'Basket %d has more than one successful transaction id, using the first one',
                    basket_id)

            successful_payment_notification = successful_transaction[0]

            self.payment_processor = _get_payment_processor(
                site, successful_payment_notification.processor_name)
            # Create handled response
            handled_response = HandledProcessorResponse(
                transaction_id=successful_payment_notification.transaction_id,
                total=basket.total_excl_tax,
                currency=basket.currency,
                card_number=json.loads(
                    successful_payment_notification.response).get(
                        'req_card_number', 'Paypal Account'),
                card_type=CYBERSOURCE_CARD_TYPE_MAP.get(
                    json.loads(successful_payment_notification.response).get(
                        'req_card_type'), None))

            # Record Payment and try to place order
            try:
                self.record_payment(
                    basket=basket, handled_processor_response=handled_response)

                shipping_method = NoShippingRequired()
                shipping_charge = shipping_method.calculate(basket)
                order_total = OrderTotalCalculator().calculate(
                    basket, shipping_charge)

                user = basket.owner
                # Given a basket, order number generation is idempotent. Although we've already
                # generated this order number once before, it's faster to generate it again
                # than to retrieve an invoice number from PayPal.
                order_number = basket.order_number

                self.handle_order_placement(
                    order_number=order_number,
                    user=user,
                    basket=basket,
                    shipping_address=None,
                    shipping_method=shipping_method,
                    shipping_charge=shipping_charge,
                    billing_address=None,
                    order_total=order_total,
                )
                logger.info('Successfully created order for basket %d',
                            basket.id)
                return True
            except:  # pylint: disable=bare-except
                logger.exception('Unable to create order for basket %d',
                                 basket.id)
                return False

        # Start order fulfillment if order exists but is not fulfilled.
        elif order.is_fulfillable:
            order_lines = order.lines.all()
            line_quantities = [line.quantity for line in order_lines]

            shipping_event, __ = ShippingEventType.objects.get_or_create(
                name=SHIPPING_EVENT_NAME)
            EventHandler().handle_shipping_event(order, shipping_event,
                                                 order_lines, line_quantities)

            if order.is_fulfillable:
                logger.error('Unable to fulfill order for basket %d',
                             basket.id)
                return False
        return True