Ejemplo n.º 1
0
    def handle_processor_response(self, response, basket=None):
        """
        Executes an approved Paystack transaction. This method will record payment processor
        response for future usage.

        Arguments:
            response: Transaction response received from Paystack after successfull payment.
            basket (Basket): Basket being purchased via the payment processor.

        Returns:
            HandledProcessorResponse
        """
        authorization = response.get('authorization')
        transaction_id = response.get('id')

        self.record_processor_response(authorization,
                                       transaction_id=transaction_id,
                                       basket=basket)
        logger.info(
            "Successfully executed Paystack payment [%s] for basket: %d.",
            transaction_id, basket.id)

        currency = response.get('currency')
        total = basket.total_incl_tax

        return HandledProcessorResponse(
            transaction_id=transaction_id,
            total=total,
            currency=currency,
            card_number=authorization.get('last4'),
            card_type=authorization.get('card_type'))
Ejemplo n.º 2
0
    def handle_processor_response(self, transaction_response, basket=None):
        """
            Execute an approved AuthorizeNet transaction. This method will record payment processor
            response for future usage.

            Arguments:
                transaction_response: Transaction details received from authorizeNet after successfull payment
                basket (Basket): Basket being purchased via the payment processor.

            Returns:
                HandledProcessorResponse
        """
        transaction_id = transaction_response.transaction.transId
        transaction_dict = LxmlObjectJsonEncoder().encode(transaction_response)

        self.record_processor_response(transaction_dict, transaction_id=transaction_id, basket=basket)
        logger.info("Successfully executed AuthorizeNet payment [%s] for basket [%d].", transaction_id, basket.id)

        currency = basket.currency
        total = float(transaction_response.transaction.settleAmount)
        card_info = transaction_response.transaction.payment.creditCard

        return HandledProcessorResponse(
            transaction_id=transaction_id,
            total=total,
            currency=currency,
            card_number=card_info.cardNumber,
            card_type=card_info.cardType
        )
Ejemplo n.º 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':
            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)
Ejemplo n.º 4
0
 def handle_processor_response(self, response, basket=None):
     return HandledProcessorResponse(
         transaction_id=basket.id,
         total=basket.total_incl_tax,
         currency=basket.currency,
         card_number=basket.owner.username,
         card_type='Visa'
     )
Ejemplo n.º 5
0
    def handle_processor_response(self, response, basket=None):
        """
        Handle a response (i.e., "merchant notification") from FOMO Pay.

        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:
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
        """

        # 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['result']
        if decision != self.PAYMENT_APPROVED:
            # FOMO Pay is not explicit to say what is the cause of the error,
            # it is necessary that we make our own checks.

            # Check if the user made a payment request twice for the same order.
            if Order.objects.filter(number=response['transaction']).exists():
                raise DuplicateReferenceNumber

            # Raise an exception if the authorized amount differs from the requested amount.
            if response.get('cash_amount') != basket.total_incl_tax:
                raise PartialAuthorizationError

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

        currency = response.get('cash_currency', basket.currency)
        total = Decimal(response.get('cash_amount', basket.total_incl_tax))
        transaction_id = response.get('payment_id')

        return HandledProcessorResponse(transaction_id=transaction_id,
                                        total=total,
                                        currency=currency,
                                        card_type='WeChat QR Payment',
                                        card_number='.')
Ejemplo n.º 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
        """
        
        data = response['data']
        status = response['status']
        message = response['message']
        try:
            chargecode = response['data']['chargecode']
        except:
            chargecode = None
            
        if chargecode == "00" or chargecode == "0":
            pass
        else:
            logger.error("Transaction failed: response", str(response))
            raise TransactionDeclined

        currency = response['data']['currency']
        total = Decimal(response['data']['amount'])
        try:
            transaction_id = response['data']['txid']  # Error Notifications does not include a transaction id.
        except:
            transaction_id = None
        card_number = response['data']['card']['last4digits']
        card_type = response['data']['card']['brand']

        return HandledProcessorResponse(
            transaction_id=transaction_id,
            total=total,
            currency=currency,
            card_number=card_number,
            card_type=card_type
        )
Ejemplo n.º 7
0
    def handle_processor_response(self, response, basket=None):  # pylint: disable=unused-argument
        """
        Handle a response (i.e., "merchant notification").

        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:
            TransactionDeclined: Indicates the payment was declined by the processor.
            GatewayError: Indicates a general error on the part of the processor.
            InvalidEdnxDecision: Indicates an unknown decision value

        Returns:
            HandleProcessorResponse: Payment info (transaction_id, total, currency, card_number and card_type)
        """

        # 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.
        transaction_state = response['state_pol']

        if transaction_state != self.TRANSACTION_ACCEPTED:
            exception = {
                self.TRANSACTION_DECLINED: TransactionDeclined,
                self.TRANSACTION_ERROR: GatewayError,
                self.TRANSACTION_PENDING: TransactionPending,
            }.get(transaction_state, InvalidEdnxDecision)

            raise exception

        currency = response.get('currency')
        total = Decimal(response.get('value'))
        transaction_id = response.get('transaction_id')
        card_number = response.get('cc_number', '')
        card_type = response.get('lapPaymentMethod', '')

        return HandledProcessorResponse(
            transaction_id=transaction_id,
            total=total,
            currency=currency,
            card_number=card_number,
            card_type=card_type,
        )
Ejemplo n.º 8
0
    def handle_processor_response(self, response, basket=None):
        """
        Verify that the payment was successfully processed -- because Trust but Verify.
        https://saferpay.github.io/jsonapi/index.html#Payment_v1_PaymentPage_Assert
        If payment did not succeed, raise GatewayError and log error.

        Once payment has been verified, call Capture to finalize the payment
        https://saferpay.github.io/jsonapi/index.html#Payment_v1_Transaction_Capture
        The generated CaptureId will be used to generate refunds.

        Args:
            response (str): this is actually the request token.
        """
        token = response
        assert_request_data = {"Token": token}

        # Check payment was successful (this will raise in case of invalid payment)
        assert_data = self.make_api_json_request(
            "Payment/v1/PaymentPage/Assert",
            method="POST",
            data=assert_request_data,
            basket=basket,
        )
        total = int(assert_data["Transaction"]["Amount"]["Value"]) / 100.0
        currency = assert_data["Transaction"]["Amount"]["CurrencyCode"]
        card_number = assert_data["PaymentMeans"]["Card"]["MaskedNumber"]
        card_type = assert_data["PaymentMeans"]["Brand"]["PaymentMethod"]
        transaction_id = assert_data["Transaction"]["Id"]

        # Finalize payment
        capture_request_data = {
            "TransactionReference": {
                "TransactionId": transaction_id
            }
        }
        capture_data = self.make_api_json_request(
            "Payment/v1/Transaction/Capture",
            method="POST",
            data=capture_request_data,
            basket=basket,
        )
        capture_id = capture_data["CaptureId"]

        return HandledProcessorResponse(
            transaction_id=capture_id,
            total=total,
            currency=currency,
            card_number=card_number,
            card_type=card_type,
        )
Ejemplo n.º 9
0
    def handle_processor_response(self, response, basket=None):
        token = response
        logger.info(
            'Response received from handle_processor_response:RazorPay [%s]',
            str(response))
        order_number = basket.order_number
        currency = basket.currency

        return HandledProcessorResponse(
            transaction_id=None,  # transaction_id,
            total=None,  # total,
            currency=currency,
            card_number=None,  # card_number,
            card_type=None  #card_type
        )
Ejemplo n.º 10
0
    def handle_processor_response(self, response, basket=None):
        transaction_id = response.get('out_trade_no')
        PaymentProcessorResponse.objects.filter(
            processor_name=self.NAME,
            transaction_id=transaction_id).update(response=response,
                                                  basket=basket)

        total = Decimal(response.get('total_fee'))
        email = response.get('buyer_email')
        label = 'PayPal ({})'.format(email) if email else 'PayPal Account'
        return HandledProcessorResponse(transaction_id=transaction_id,
                                        total=total,
                                        currency='CNY',
                                        card_number=label,
                                        card_type=None)
Ejemplo n.º 11
0
    def handle_processor_response(self, response, basket=None):
        token = response
        order_number = basket.order_number
        currency = basket.currency

        # NOTE: In the future we may want to get/create a Customer. See https://stripe.com/docs/api#customers.
        try:
            charge = stripe.Charge.create(
                amount=self._get_basket_amount(basket),
                currency=currency,
                source=token,
                description=order_number,
                metadata={'order_number': order_number})
            transaction_id = charge.id

            # NOTE: Charge objects subclass the dict class so there is no need to do any data transformation
            # before storing the response in the database.
            self.record_processor_response(charge,
                                           transaction_id=transaction_id,
                                           basket=basket)
            logger.info(
                'Successfully created Stripe charge [%s] for basket [%d].',
                transaction_id, basket.id)
        except stripe.error.CardError as ex:
            base_message = "Stripe payment for basket [%d] declined with HTTP status [%d]"
            exception_format_string = "{}: %s".format(base_message)
            body = ex.json_body
            logger.exception(exception_format_string, basket.id,
                             ex.http_status, body)
            self.record_processor_response(body, basket=basket)
            raise TransactionDeclined(base_message, basket.id,
                                      ex.http_status) from ex

        total = basket.total_incl_tax
        card_number = charge.source.last4
        card_type = STRIPE_CARD_TYPE_MAP.get(charge.source.brand)

        return HandledProcessorResponse(transaction_id=transaction_id,
                                        total=total,
                                        currency=currency,
                                        card_number=card_number,
                                        card_type=card_type)
Ejemplo n.º 12
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)
Ejemplo n.º 13
0
    def handle_processor_response(self, response, basket=None):
        """
        Execute an approved PayPal payment.

        This method creates PaymentEvents and Sources for approved payments.

        Arguments:
            response (dict): Dictionary of parameters returned by PayPal in the `return_url` query string.

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

        Raises:
            GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented
                an approved payment from being executed.

        Returns:
            HandledProcessorResponse
        """
        data = {'payer_id': response.get('PayerID')}

        # By default PayPal payment will be executed only once.
        available_attempts = 1

        # Add retry attempts (provided in the configuration)
        # if the waffle switch 'ENABLE_PAYPAL_RETRY' is set
        if waffle.switch_is_active('PAYPAL_RETRY_ATTEMPTS'):
            available_attempts = available_attempts + self.retry_attempts

        for attempt_count in range(1, available_attempts + 1):
            payment = paypalrestsdk.Payment.find(response.get('paymentId'),
                                                 api=self.paypal_api)
            payment.execute(data)

            if payment.success():
                # On success break the loop.
                break

            # Raise an exception for payments that were not successfully executed. Consuming code is
            # responsible for handling the exception
            error = self._get_error(payment)
            # pylint: disable=unsubscriptable-object
            entry = self.record_processor_response(
                error, transaction_id=error['debug_id'], basket=basket)

            logger.warning(
                "Failed to execute PayPal payment on attempt [%d]. "
                "PayPal's response was recorded in entry [%d].", attempt_count,
                entry.id)

            # After utilizing all retry attempts, raise the exception 'GatewayError'
            if attempt_count == available_attempts:
                logger.error(
                    "Failed to execute PayPal payment [%s]. "
                    "PayPal's response was recorded in entry [%d].",
                    payment.id, entry.id)
                raise GatewayError

        self.record_processor_response(payment.to_dict(),
                                       transaction_id=payment.id,
                                       basket=basket)
        logger.info(
            "Successfully executed PayPal payment [%s] for basket [%d].",
            payment.id, basket.id)

        currency = payment.transactions[0].amount.currency
        total = Decimal(payment.transactions[0].amount.total)
        transaction_id = payment.id
        # payer_info.email may be None, see:
        # http://stackoverflow.com/questions/24090460/paypal-rest-api-return-empty-payer-info-for-non-us-accounts
        email = payment.payer.payer_info.email
        label = 'PayPal ({})'.format(email) if email else 'PayPal Account'

        return HandledProcessorResponse(transaction_id=transaction_id,
                                        total=total,
                                        currency=currency,
                                        card_number=label,
                                        card_type=None)
Ejemplo n.º 14
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
Ejemplo n.º 15
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
Ejemplo n.º 16
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
Ejemplo n.º 17
0
    def handle_processor_response(self,
                                  response,
                                  basket=None,
                                  forMobile=False):
        token = response
        order_number = basket.order_number
        currency = basket.currency
        basket_id = json.dumps(basket.id)
        # NOTE: In the future we may want to get/create a Customer. See https://stripe.com/docs/api#customers.
        tracking_context = basket.owner.tracking_context or {}

        if tracking_context.get('customer_id') and not forMobile:
            token_data = stripe.Token.retrieve(token, )
            selected_card = tracking_context.get('selected_payment_card_id',
                                                 None)

            if selected_card:
                card_retrieve = stripe.Customer.retrieve_source(
                    tracking_context.get('customer_id'),
                    selected_card,
                )
                customer = stripe.Customer.modify(
                    tracking_context.get('customer_id'),
                    default_source=selected_card)

            else:
                src = stripe.Customer.create_source(
                    tracking_context.get('customer_id'), source=token)
                customer = stripe.Customer.modify(
                    tracking_context.get('customer_id'),
                    default_source=src["id"])

            customer_id = customer['id']
            basket.owner.tracking_context.update({'token': token})
            basket.owner.save()

        if token is None:
            customer_id = tracking_context.get('customer_id')
        elif not tracking_context.get('customer_id', None):
            billing_address = self.get_address_from_token(token)
            address = {
                'city': billing_address.line4,
                'country': billing_address.country,
                'line1': billing_address.line1,
                'line2': billing_address.line2,
                'postal_code': billing_address.postcode,
                'state': billing_address.state
            }
            customer = stripe.Customer.create(source=token,
                                              email=basket.owner.email,
                                              address=address,
                                              name=basket.owner.full_name)
            customer_id = customer['id']
            basket.owner.tracking_context = basket.owner.tracking_context or {}
            basket.owner.tracking_context.update({
                'customer_id': customer_id,
                'token': token
            })
            basket.owner.save()

        else:
            customer_id = tracking_context.get('customer_id')

        if not forMobile:
            try:
                charge = stripe.Charge.create(
                    amount=self._get_basket_amount(basket),
                    currency=currency,
                    customer=customer_id,
                    description=order_number,
                    metadata={
                        'order_number': order_number,
                        'basket_id': basket_id
                    })
                transaction_id = charge.id

                # NOTE: Charge objects subclass the dict class so there is no need to do any data transformation
                # before storing the response in the database.
                self.record_processor_response(charge,
                                               transaction_id=transaction_id,
                                               basket=basket)
                logger.info(
                    'Successfully created Stripe charge [%s] for basket [%d].',
                    transaction_id, basket.id)
            except stripe.error.CardError as ex:
                base_message = "Stripe payment for basket [%d] declined with HTTP status [%d]"
                exception_format_string = "{}: %s".format(base_message)
                body = ex.json_body
                logger.exception(exception_format_string, basket.id,
                                 ex.http_status, body)
                self.record_processor_response(body, basket=basket)
                raise TransactionDeclined(base_message, basket.id,
                                          ex.http_status)

            total = basket.total_incl_tax
            card_number = charge.source.last4
            card_type = STRIPE_CARD_TYPE_MAP.get(charge.source.brand)

            return HandledProcessorResponse(transaction_id=transaction_id,
                                            total=total,
                                            currency=currency,
                                            card_number=card_number,
                                            card_type=card_type)

        else:

            payment_intent = stripe.PaymentIntent.create(
                amount=self._get_basket_amount(basket),
                currency=currency,
                customer=customer_id,
                description=order_number,
                metadata={
                    'order_number': order_number,
                    'basket_id': basket_id
                })

            client_secret = payment_intent.client_secret
            total = basket.total_incl_tax
            card_number = ""
            card_type = ""

            return HandledMobileProcessorResponse(
                transaction_id=payment_intent.id,
                total=total,
                currency=currency,
                client_secret=client_secret,
                card_number=card_number,
                card_type=card_type,
            )
Ejemplo n.º 18
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)
Ejemplo n.º 19
0
    def handle_processor_response(self, response, basket):
        """
        Handle a response (i.e., "merchant notification") from redsys.
        """
        payment = RedsysResponseForm(response)
        self.order_number = basket.order_number

        #alphanumeric_characters = re.compile('[^a-zA-Z0-9]')
        #self.order_number = '%s%s' % (self.order_number_prefix,re.sub(alphanumeric_characters, '', basket.order_number))

        if payment.is_valid():
            logger.debug('processing payment gateway response for payment %s' %
                         self.order_number)
            # REAL
            # signature = compute_signature(self.order_number,
            #                               payment.cleaned_data['Ds_MerchantParameters'].encode(),
            #                               self.shared_secret)
            # TEST
            signature = compute_signature(
                self.order_number,
                payment.cleaned_data['Ds_MerchantParameters'].encode(),
                Redsys.shared_secret_test)
            logger.debug('received signature: %s' %
                         payment.cleaned_data['Ds_Signature'])
            logger.debug('calculated signature: %s' % signature.decode())
            if not compare_signatures(signature.decode(),
                                      payment.cleaned_data['Ds_Signature']):
                logger.debug('signature mismatch - possible attack')
                return HttpResponse()

            binary_merchant_parameters = base64.b64decode(
                payment.cleaned_data['Ds_MerchantParameters'])
            merchant_parameters = json.loads(
                binary_merchant_parameters.decode())
            transaction_type = merchant_parameters['Ds_TransactionType']
            response_code = int(merchant_parameters['Ds_Response'])

            if response_code < 100:
                # Authorised transaction for payments
                if transaction_type == '0':
                    #captured_amount = int(merchant_parameters['Ds_Amount']) / 100
                    transaction_id = merchant_parameters['Ds_Order']
                    extra_data = merchant_parameters
                    self.record_processor_response(
                        response,
                        transaction_id=self.order_number,
                        basket=basket)
                    logger.info('payment %s confirmed' % self.order_number)
                    logger.info('basket %s' % basket)
                    logger.info('mparams %s' % merchant_parameters)
                    return HandledProcessorResponse(
                        transaction_id=transaction_id,
                        total=basket.total_incl_tax,
                        currency=basket.currency,
                        # card_number=basket.label,
                        card_number=merchant_parameters[
                            'Ds_AuthorisationCode'],
                        card_type=None)
                else:
                    logger.debug(
                        'authorised payment response but unrecognised transaction type %s'
                        % transaction_type)

            if response_code == 900:
                # Authorised transaction for refunds and confirmations
                if transaction_type == '3':
                    extra_data = merchant_parameters
                    logger.debug('payment %s automatic refund' %
                                 self.order_number)
                else:
                    logger.debug(
                        'authorised refund response but unrecognised transaction type %s'
                        % transaction_type)

            if response_code > 100 and response_code != 900:
                # any of a long list of errors/rejections
                extra_data = merchant_parameters
                # perhaps import and raise PaymentError from django-payments
                logger.debug('rejected: %s' %
                             binary_merchant_parameters.decode())
Ejemplo n.º 20
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.raw_json):
            raise InvalidSignatureError

        if response.decision != Decision.accept:
            if response.duplicate_payment:
                # 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.order_id).exists():
                    raise DuplicateReferenceNumber

                # If we failed to capture a successful payment, and then the user submits payment again within a 15
                # minute window, then we get a duplicate payment message with no amount attached. We can't create an
                # order in that case.
                if response.total is None:
                    raise DuplicateReferenceNumber

                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 {
                    Decision.cancel: UserCancelled,
                    Decision.decline: TransactionDeclined,
                    Decision.error: GatewayError,
                    Decision.review: AuthorizationError,
                }.get(response.decision,
                      InvalidCybersourceDecision(response.decision))

        transaction_id = response.transaction_id
        if transaction_id and response.decision == Decision.accept:
            if Order.objects.filter(number=response.order_id).exists():
                if PaymentProcessorResponse.objects.filter(
                        transaction_id=transaction_id).exists():
                    raise RedundantPaymentNotificationError
                raise ExcessivePaymentForOrderError

        if response.partial_authorization:
            # 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
            raise PartialAuthorizationError

        return HandledProcessorResponse(transaction_id=response.transaction_id,
                                        total=response.total,
                                        currency=response.currency,
                                        card_number=response.card_number,
                                        card_type=response.card_type)