class PaymentMethodSerializer(serializers.HyperlinkedModelSerializer):
    url = PaymentMethodUrl(view_name='payment-method-detail',
                           source="*",
                           read_only=True)
    transactions = PaymentMethodTransactionsUrl(
        view_name='payment-method-transaction-list', source='*')
    payment_processor_name = serializers.ModelField(
        model_field=PaymentMethod()._meta.get_field('payment_processor'),
        source="payment_processor",
        label="Payment Processor")
    payment_processor = serializers.SerializerMethodField()

    def get_payment_processor(self, obj):
        return PaymentProcessorSerializer(obj.get_payment_processor(),
                                          context=self.context).data

    class Meta:
        model = PaymentMethod
        fields = ('url', 'transactions', 'customer', 'payment_processor_name',
                  'payment_processor', 'added_at', 'verified', 'canceled',
                  'valid_until', 'display_info')
        extra_kwargs = {
            'added_at': {
                'read_only': True
            },
            'customer': {
                'read_only': True,
                'lookup_url_kwarg': 'customer_pk'
            }
        }

    def validate(self, attrs):
        attrs = super(PaymentMethodSerializer, self).validate(attrs)

        if self.instance:
            if self.instance.canceled:
                raise ValidationError(
                    'You cannot update a canceled payment method.')

            # Run model clean and handle ValidationErrors
            try:
                # Use the existing instance to avoid unique field errors
                payment_method = self.instance
                payment_method_dict = payment_method.__dict__.copy()

                for attribute, value in attrs.items():
                    setattr(payment_method, attribute, value)

                payment_method.full_clean()

                # Revert changes to existing instance
                payment_method.__dict__ = payment_method_dict
            except ValidationError as e:
                errors = e.error_dict
                non_field_errors = errors.pop(NON_FIELD_ERRORS, None)
                if non_field_errors:
                    errors['non_field_errors'] = [
                        error for sublist in non_field_errors
                        for error in sublist
                    ]
                raise serializers.ValidationError(errors)

        return attrs

    def validate_payment_processor_name(self, value):
        if value not in PaymentMethod.PaymentProcessors.as_list():
            raise serializers.ValidationError('"{}" is not a valid '
                                              'choice'.format(value))
        if self.instance and value != self.instance.payment_processor:
            message = "This field may not be modified."
            raise serializers.ValidationError(message)

        return value

    def validate_verified(self, value):
        if self.instance and not value and self.instance.verified:
            message = "You cannot unverify a payment method."
            raise serializers.ValidationError(message)

        return value
Ejemplo n.º 2
0
    def post(self, request, *args, **kwargs):
        """ Create a one off transaction by way of creating an invoice,
        payment method and document, and return the created transaction.
        """

        rq = request.data

        customer_one_off_defaults = {
            "currency": "USD",
        }

        ## Customer creation
        #
        # Check to see if we're getting an `account_id`, and try to
        # retrieve that customer first.
        #
        customer = None
        has_uuid = rq.get('customer', {}).get('account_id', False)

        if has_uuid:
            _u = uuid.UUID(has_uuid)
            # This will raise Customer.DoesNotExist if an invalid UUID is given
            customer = Customer.objects.get(account_id=_u)

        # If we have no customer and no exceptions were raised, create a
        # new customer
        #
        if customer == None:
            new_customer = customer_one_off_defaults
            new_customer.update(**rq.get('customer'))
            customer = Customer(**new_customer)
            customer.save()

        ## Create a customer payment method
        #
        payment_processor_name = rq.get("payment_processor", "manual")

        # Check if a method for the customer with this payment_processor
        # exists already
        #
        try:
            has_method = PaymentMethod.objects.get(
                customer=customer, payment_processor=payment_processor_name)
        except PaymentMethod.DoesNotExist:
            has_method = False

        # Create a new method
        #
        if not has_method:
            # TODO: what are our sensible defaults?
            customer_default_payment_method = {
                "customer":
                customer,
                "payment_processor":
                payment_processor_name,
                "verified":
                True,
                "canceled":
                False,
                "valid_until":
                dt.now() + timedelta(days=7),
                "display_info":
                "pytest",
                "data":
                json.dumps({
                    "attempt_retries_after": 2,
                    "stop_retry_attempts": 5
                })
            }
            new_pm = PaymentMethod(**customer_default_payment_method)
            new_pm.save()
        else:
            new_pm = has_method

        ## Get a provider

        # First we'll try to get our internal provider, otherwise create
        # it if it doesn't exist.
        #
        # TODO: add a request parameter for the provider name or
        # something.
        #
        provider = Provider.objects.filter(
            invoice_series="BPInvoiceSeries").first()
        if provider is None:
            prv = {
                "name": "Internal Billing Provider",
                "company": "Internal Billing Provider",
                "invoice_series": "BPInvoiceSeries",
                "flow": "invoice",
                "email": "",
                "address_1": "1 Mulberry Lane",
                "address_2": "",
                "city": "Pacoima",
                "state": "CA",
                "zip_code": "",
                "country": "US",
                "invoice_starting_number": 1
            }
            provider = Provider(**prv)
            provider.save()

        ## Create an invoice

        # Some defaults to save effort from the client user
        #
        invoice_one_off_defaults = {
            "provider": provider,
            "series": provider.invoice_series,
            "customer": customer,
            "transaction_currency": "USD",
            "transaction_xe_rate": Decimal('1.0000'),
            "currency": "USD",
            "state": "draft",
        }

        invoice_entry_defaults = {
            "quantity": 1.0,
            "unit_price": rq.get("amount", 0.0),
            "start_date": None,
            "end_date": None,
            "prorated": False,
            "product_code": None
        }

        # Override these with any request data
        #
        new_entry = invoice_entry_defaults.copy()
        new_invoice = invoice_one_off_defaults.copy()
        new_invoice.update(**rq.get("invoice", {}))
        if 'issue_date' in new_invoice:
            new_invoice['issue_date'] = dtparse(
                new_invoice.get('issue_date')).date()

        # Create the invoice
        inv = Invoice(**new_invoice)
        inv.save()

        # Add the entry (save first, or else)
        entr = DocumentEntry(**new_entry)
        entr.save()
        inv.invoice_entries.add(entr)
        inv.save()

        # Issue the invoice to generate transactions.
        inv.issue()
        inv.save()

        transaction = Transaction.objects.filter(invoice=inv).first()

        _ser_kwargs = {"context": {"request": request}}

        return Response({
            "customer":
            CustomerSerializer(customer, **_ser_kwargs).data,
            "payment_method":
            PaymentMethodSerializer(new_pm, **_ser_kwargs).data,
            "provider":
            ProviderSerializer(provider, **_ser_kwargs).data,
            "invoice":
            InvoiceSerializer(inv, **_ser_kwargs).data,
            "transaction":
            TransactionSerializer(transaction, **_ser_kwargs).data,
        })