예제 #1
0
def make_check_donation(request):
    """A page for admins to use to input check donations manually."""
    if request.method == "POST":
        data = request.POST.copy()
        data.update({
            "payment_provider": PROVIDERS.CHECK,
            "amount": "other",
        })
        donation_form = DonationForm(data)
        # Get the user, if we can. Else, set up the form to create a new user.
        try:
            email = request.POST.get("email").strip()
            user = User.objects.get(email__iexact=email)
        except User.DoesNotExist:
            user = None
            user_form = UserForm(request.POST)
            profile_form = ProfileForm(request.POST)
        else:
            user_form = UserForm(request.POST, instance=user)
            profile_form = ProfileForm(request.POST, instance=user.profile)

        if all([
                donation_form.is_valid(),
                user_form.is_valid(),
                profile_form.is_valid(),
        ]):
            cd_user_form = user_form.cleaned_data
            cd_profile_form = profile_form.cleaned_data
            if user is not None:
                user = user_form.save()
                profile_form.save()
            else:
                user, profile = create_stub_account(cd_user_form,
                                                    cd_profile_form)
                user.save()
                profile.save()

            d = donation_form.save(commit=False)
            d.status = Donation.PROCESSED
            d.donor = user
            d.save()
            if user.email:
                send_thank_you_email(d, PAYMENT_TYPES.DONATION)

            return HttpResponseRedirect(reverse("donate_complete"))
    else:
        donation_form = DonationForm()
        user_form = UserForm()
        profile_form = ProfileForm()

    return render(
        request,
        "check_donation.html",
        {
            "donation_form": donation_form,
            "profile_form": profile_form,
            "user_form": user_form,
            "private": True,
        },
    )
예제 #2
0
def process_donation_forms(request, template_name, stripe_redirect_url,
                           context, payment_type):
    donation_form = context["donation_form"]
    user_form = context["user_form"]
    profile_form = context["profile_form"]
    if all([
            donation_form.is_valid(),
            user_form.is_valid(),
            profile_form.is_valid(),
    ]):
        # Process the data in form.cleaned_data
        cd_donation_form = donation_form.cleaned_data
        cd_user_form = user_form.cleaned_data
        cd_profile_form = profile_form.cleaned_data
        frequency = request.POST.get("frequency")

        # Route the payment to a payment provider
        payment_provider = cd_donation_form["payment_provider"]
        try:
            response, customer = route_and_process_payment(
                request,
                cd_donation_form,
                cd_user_form,
                payment_provider,
                frequency,
                stripe_redirect_url,
                payment_type,
            )
        except PaymentFailureException as e:
            logger.info("Payment failed. Message was: %s", e.message)
            context["message"] = e.message
            response = {"status": "Failed"}

        logger.info("Payment routed with response: %s", response)
        if response["status"] == Donation.AWAITING_PAYMENT:
            if request.user.is_anonymous and not context["stub_account"]:
                # Create a stub account with an unusable password
                user, profile = create_stub_account(cd_user_form,
                                                    cd_profile_form)
                user.save()
                profile.save()
            else:
                # Logged in user or an existing stub account.
                user = user_form.save()
                profile_form.save()

            donation = donation_form.save(commit=False)
            donation.status = response["status"]
            donation.payment_id = response["payment_id"]
            # Will only work for Paypal:
            donation.transaction_id = response.get("transaction_id")
            donation.donor = user
            donation.save()

            if frequency == FREQUENCIES.MONTHLY and customer:
                add_monthly_donations(cd_donation_form, user, customer)

            return HttpResponseRedirect(response["redirect"])

    return render(request, template_name, context)
예제 #3
0
def make_check_donation(request):
    """A page for admins to use to input check donations manually."""
    if request.method == 'POST':
        data = request.POST.copy()
        data.update({
            'payment_provider': PROVIDERS.CHECK,
            'amount': 'other',
        })
        donation_form = DonationForm(data)
        # Get the user, if we can. Else, set up the form to create a new user.
        try:
            email = request.POST.get('email').strip()
            user = User.objects.get(email__iexact=email)
        except User.DoesNotExist:
            user = None
            user_form = UserForm(request.POST)
            profile_form = ProfileForm(request.POST)
        else:
            user_form = UserForm(request.POST, instance=user)
            profile_form = ProfileForm(request.POST, instance=user.profile)

        if all([
                donation_form.is_valid(),
                user_form.is_valid(),
                profile_form.is_valid()
        ]):
            cd_user_form = user_form.cleaned_data
            cd_profile_form = profile_form.cleaned_data
            if user is not None:
                user = user_form.save()
                profile_form.save()
            else:
                user, profile = create_stub_account(cd_user_form,
                                                    cd_profile_form)
                user.save()
                profile.save()

            d = donation_form.save(commit=False)
            d.status = Donation.PROCESSED
            d.donor = user
            d.save()
            if user.email:
                send_thank_you_email(d)

            return HttpResponseRedirect(reverse('check_complete'))
    else:
        donation_form = DonationForm()
        user_form = UserForm()
        profile_form = ProfileForm()

    return render(
        request, 'check_donation.html', {
            'donation_form': donation_form,
            'profile_form': profile_form,
            'user_form': user_form,
            'private': True,
        })
예제 #4
0
def make_check_donation(request):
    """A page for admins to use to input check donations manually."""
    if request.method == 'POST':
        data = request.POST.copy()
        data.update({
            'payment_provider': PROVIDERS.CHECK,
            'amount': 'other',
        })
        donation_form = DonationForm(data)
        # Get the user, if we can. Else, set up the form to create a new user.
        try:
            email = request.POST.get('email').strip()
            user = User.objects.get(email__iexact=email)
        except User.DoesNotExist:
            user = None
            user_form = UserForm(request.POST)
            profile_form = ProfileForm(request.POST)
        else:
            user_form = UserForm(request.POST, instance=user)
            profile_form = ProfileForm(request.POST, instance=user.profile)

        if all([donation_form.is_valid(),
                user_form.is_valid(),
                profile_form.is_valid()]):
            cd_user_form = user_form.cleaned_data
            cd_profile_form = profile_form.cleaned_data
            if user is not None:
                user = user_form.save()
                profile_form.save()
            else:
                user, profile = create_stub_account(cd_user_form,
                                                    cd_profile_form)
                user.save()
                profile.save()

            d = donation_form.save(commit=False)
            d.status = Donation.PROCESSED
            d.donor = user
            d.save()
            if user.email:
                send_thank_you_email(d, PAYMENT_TYPES.DONATION)

            return HttpResponseRedirect(reverse('donate_complete'))
    else:
        donation_form = DonationForm()
        user_form = UserForm()
        profile_form = ProfileForm()

    return render(request, 'check_donation.html', {
        'donation_form': donation_form,
        'profile_form': profile_form,
        'user_form': user_form,
        'private': True,
    })
예제 #5
0
def process_donation_forms(request, template_name, stripe_redirect_url,
                           context, payment_type):
    donation_form = context['donation_form']
    user_form = context['user_form']
    profile_form = context['profile_form']
    if all([donation_form.is_valid(), user_form.is_valid(),
            profile_form.is_valid()]):
        # Process the data in form.cleaned_data
        cd_donation_form = donation_form.cleaned_data
        cd_user_form = user_form.cleaned_data
        cd_profile_form = profile_form.cleaned_data
        frequency = request.POST.get('frequency')

        # Route the payment to a payment provider
        payment_provider = cd_donation_form['payment_provider']
        try:
            response, customer = route_and_process_payment(
                request, cd_donation_form, cd_user_form, payment_provider,
                frequency, stripe_redirect_url, payment_type)
        except PaymentFailureException as e:
            logger.critical("Payment failed. Message was: %s", e.message)
            context['message'] = e.message
            response = {'status': 'Failed'}
        else:
            logger.info("Payment routed with response: %s", response)

        if response['status'] == Donation.AWAITING_PAYMENT:
            if request.user.is_anonymous and not context['stub_account']:
                # Create a stub account with an unusable password
                user, profile = create_stub_account(cd_user_form,
                                                    cd_profile_form)
                user.save()
                profile.save()
            else:
                # Logged in user or an existing stub account.
                user = user_form.save()
                profile_form.save()

            donation = donation_form.save(commit=False)
            donation.status = response['status']
            donation.payment_id = response['payment_id']
            # Will only work for Paypal:
            donation.transaction_id = response.get('transaction_id')
            donation.donor = user
            donation.save()

            if frequency == FREQUENCIES.MONTHLY:
                add_monthly_donations(cd_donation_form, user, customer)

            return HttpResponseRedirect(response['redirect'])

    return render(request, template_name, context)
예제 #6
0
def handle_xero_payment(charge):
    """Gather data from a callback triggered by a payment in Xero

    When we send invoices to folks via Xero, they now have the option to make
    a payment via Stripe. When they do, it triggers our callback, but when that
    happens we don't know anything about the charge.

    To address this, gather data from the Stripe charge, add a user and a
    donation to the database.

    :param charge: A Stripe charge object: https://stripe.com/docs/api/charges
    :return: None
    """
    billing_details = charge["billing_details"]
    email = billing_details["email"]
    try:
        user = User.objects.get(email__iexact=email)
    except User.DoesNotExist:
        user, _ = create_stub_account(
            {
                "email": email,
                # Stripe doesn't split up first/last name (smart), but we
                # do (doh). Just stuff it in the first_name field.
                "first_name": billing_details["name"],
                "last_name": "",
            },
            {
                "address1": billing_details["address"]["line1"],
                "address2": billing_details["address"]["line2"],
                "city": billing_details["address"]["city"],
                "state": billing_details["address"]["state"],
                "zip_code": billing_details["address"]["postal_code"],
                "wants_newsletter": False,
            },
        )
    if Donation.objects.filter(payment_id=charge["id"]).exists():
        # Don't create a payment if we already have one.
        return

    Donation.objects.create(
        donor=user,
        amount=float(charge["amount"]) / 100,  # Stripe does pennies.
        payment_provider=PROVIDERS.CREDIT_CARD,
        payment_id=charge["id"],
        status=Donation.AWAITING_PAYMENT,
        referrer="XERO invoice number: %s"
        % charge["metadata"]["Invoice number"],
    )
예제 #7
0
def handle_xero_payment(charge):
    """Gather data from a callback triggered by a payment in Xero

    When we send invoices to folks via Xero, they now have the option to make
    a payment via Stripe. When they do, it triggers our callback, but when that
    happens we don't know anything about the charge.

    To address this, gather data from the Stripe charge, add a user and a
    donation to the database.

    :param charge: A Stripe charge object: https://stripe.com/docs/api/charges
    :return: None
    """
    billing_details = charge['billing_details']
    email = billing_details['email']
    try:
        user = User.objects.get(email__iexact=email)
    except User.DoesNotExist:
        user, _ = create_stub_account(
            {
                'email': email,
                # Stripe doesn't split up first/last name (smart), but we
                # do (doh). Just stuff it in the first_name field.
                'first_name': billing_details['name'],
                'last_name': '',
            },
            {
                'address1': billing_details['address']['line1'],
                'address2': billing_details['address']['line2'],
                'city': billing_details['address']['city'],
                'state': billing_details['address']['state'],
                'zip_code': billing_details['address']['postal_code'],
                'wants_newsletter': False,
            })
    Donation.objects.create(
        donor=user,
        amount=float(charge['amount']) / 100,  # Stripe does pennies.
        payment_provider=PROVIDERS.CREDIT_CARD,
        payment_id=charge['id'],
        status=Donation.AWAITING_PAYMENT,
        referrer='XERO invoice number: %s' %
        charge['metadata']['Invoice number'],
    )
예제 #8
0
def donate(request):
    """Load the donate page or process a submitted donation.

    This page has several branches. The logic is as follows:
        if GET:
            --> Load the page
        elif POST:
            if user is anonymous:
                if email address on record as a stub account:
                    --> Use it.
                elif new email address or a non-stub account:
                    --> We cannot allow anonymous people to update real
                        accounts, or this is a new email address, so create a
                        new stub account.
            elif user is logged in:
                --> associate with account.

            We now have an account. Process the payment and associate it.
    """

    message = None
    if request.method == 'POST':
        donation_form = DonationForm(request.POST)

        if request.user.is_anonymous():
            # Either this is a new account, a stubbed one, or a user that's
            # simply not logged into their account
            try:
                stub_account = User.objects.filter(
                    profile__stub_account=True).get(
                        email__iexact=request.POST.get('email'))
            except User.DoesNotExist:
                # Either a regular account or an email address we've never
                # seen before. Create a new user from the POST data.
                stub_account = False
                user_form = UserForm(request.POST)
                profile_form = ProfileForm(request.POST)
            else:
                # We use the stub account and anonymous users even are allowed
                # to update it. This is OK, because we don't care too much
                # about the accuracy of this data. Later if/when this becomes
                # a real account, anonymous users won't be able to update this
                # information -- that's what matters.
                user_form = UserForm(request.POST, instance=stub_account)
                profile_form = ProfileForm(request.POST,
                                           instance=stub_account.profile)

        else:
            user_form = UserForm(request.POST, instance=request.user)
            profile_form = ProfileForm(request.POST,
                                       instance=request.user.profile)

        if all([
                donation_form.is_valid(),
                user_form.is_valid(),
                profile_form.is_valid()
        ]):
            # Process the data in form.cleaned_data
            cd_donation_form = donation_form.cleaned_data
            cd_user_form = user_form.cleaned_data
            cd_profile_form = profile_form.cleaned_data
            stripe_token = request.POST.get('stripeToken')
            frequency = request.POST.get('frequency')

            # Route the payment to a payment provider
            try:
                if frequency == 'once':
                    response = route_and_process_donation(
                        cd_donation_form, cd_user_form, {'card': stripe_token})
                elif frequency == 'monthly':
                    customer = create_stripe_customer(stripe_token,
                                                      cd_user_form['email'])
                    response = route_and_process_donation(
                        cd_donation_form,
                        cd_user_form,
                        {
                            'customer': customer.id,
                            'metadata': {
                                'recurring': True
                            }
                        },
                    )
            except PaymentFailureException as e:
                logger.critical("Payment failed. Message was: %s", e.message)
                message = e.message
                response = {'status': 'Failed'}
            else:
                logger.info("Payment routed with response: %s", response)

            if response['status'] == Donation.AWAITING_PAYMENT:
                if request.user.is_anonymous() and not stub_account:
                    # Create a stub account with an unusable password
                    user, profile = create_stub_account(
                        cd_user_form, cd_profile_form)
                    user.save()
                    profile.save()
                else:
                    # Logged in user or an existing stub account.
                    user = user_form.save()
                    profile_form.save()

                donation = donation_form.save(commit=False)
                donation.status = response['status']
                donation.payment_id = response['payment_id']
                # Will only work for Paypal:
                donation.transaction_id = response.get('transaction_id')
                donation.donor = user
                donation.save()

                if frequency == 'monthly':
                    add_monthly_donations(cd_donation_form, user, customer)

                return HttpResponseRedirect(response['redirect'])
    else:
        # Loading the page...
        try:
            donation_form = DonationForm(
                initial={'referrer': request.GET.get('referrer')})
            user_form = UserForm(
                initial={
                    'first_name': request.user.first_name,
                    'last_name': request.user.last_name,
                    'email': request.user.email,
                })
            up = request.user.profile
            profile_form = ProfileForm(
                initial={
                    'address1': up.address1,
                    'address2': up.address2,
                    'city': up.city,
                    'state': up.state,
                    'zip_code': up.zip_code,
                    'wants_newsletter': up.wants_newsletter
                })
        except AttributeError:
            # for anonymous users, who lack profile info
            user_form = UserForm()
            profile_form = ProfileForm()

    return render(
        request, 'donate.html', {
            'donation_form': donation_form,
            'user_form': user_form,
            'profile_form': profile_form,
            'private': False,
            'message': message,
            'stripe_public_key': settings.STRIPE_PUBLIC_KEY
        })
예제 #9
0
def handle_external_payment_if_needed(charge: StripeChargeObject) -> None:
    """Gather data from a callback triggered by an external payment source

    When we send invoices to folks via Xero, they now have the option to make
    a payment via Stripe. When they do, it triggers our callback, but when that
    happens we don't know anything about the charge.

    Similarly, some people can only pay with Amex, which our website doesn't
    support, or only have an address outside the U.S. When this happens, we
    send them an invoice directly from Stripe itself. Since we do that, they
    lack a charge in our DB.

    Inspect the charge to see if these things are happening. If so, use the
    Stripe charge to add a user and a donation to the database.

    :param charge: A Stripe charge object: https://stripe.com/docs/api/charges
    :return: None
    """
    # Folks paying Xero invoices:
    xero_source = charge.get("application") == settings.XERO_APPLICATION_ID

    # Folks paying via Stripe invoices (these are useful for Amex or people
    # without an American address)
    stripe_source = charge["metadata"].get("type") == "invoice"
    if not any([xero_source, stripe_source]):
        # Just an average payment. Do the regular thing.
        return

    #
    # It's a weird source like xero.
    # Add a user and donation if needed.
    #
    billing_details = charge["billing_details"]
    email = billing_details["email"]
    if not email and stripe_source:
        # Webhooks triggered by Stripe invoices don't provide user information,
        # so we just file these all under the same fake user. 🤮
        email = "*****@*****.**"
    try:
        user = User.objects.get(email__iexact=email)
    except User.DoesNotExist:
        user, _ = create_stub_account(
            {
                "email": email,
                # Stripe doesn't split up first/last name (smart), but we
                # do (doh). Just stuff it in the first_name field.
                "first_name": billing_details["name"],
                "last_name": "",
            },
            {
                "address1": billing_details["address"]["line1"],
                "address2": billing_details["address"]["line2"],
                "city": billing_details["address"]["city"],
                "state": billing_details["address"]["state"],
                "zip_code": billing_details["address"]["postal_code"],
                "wants_newsletter": False,
            },
        )
    if Donation.objects.filter(payment_id=charge["id"]).exists():
        # Don't create a payment if we already have one.
        return

    if xero_source:
        invoice_number = charge["metadata"]["Invoice number"]
        referrer = f"XERO invoice number: {invoice_number}"
    elif stripe_source:
        referrer = "Stripe invoice"
    else:
        raise NotImplementedError("Unknown source.")
    Donation.objects.create(
        donor=user,
        amount=float(charge["amount"]) / 100,  # Stripe does pennies.
        payment_provider=PROVIDERS.CREDIT_CARD,
        payment_id=charge["id"],
        status=Donation.AWAITING_PAYMENT,
        referrer=referrer,
    )
예제 #10
0
def donate(request):
    """Load the donate page or process a submitted donation.

    This page has several branches. The logic is as follows:
        if GET:
            --> Load the page
        elif POST:
            if user is anonymous:
                if email address on record as a stub account:
                    --> Use it.
                elif new email address or a non-stub account:
                    --> We cannot allow anonymous people to update real
                        accounts, or this is a new email address, so create a
                        new stub account.
            elif user is logged in:
                --> associate with account.

            We now have an account. Process the payment and associate it.
    """

    message = None
    if request.method == 'POST':
        donation_form = DonationForm(request.POST)

        if request.user.is_anonymous():
            # Either this is a new account, a stubbed one, or a user that's
            # simply not logged into their account
            try:
                stub_account = User.objects.filter(
                    profile__stub_account=True
                ).get(
                    email__iexact=request.POST.get('email')
                )
            except User.DoesNotExist:
                # Either a regular account or an email address we've never
                # seen before. Create a new user from the POST data.
                stub_account = False
                user_form = UserForm(request.POST)
                profile_form = ProfileForm(request.POST)
            else:
                # We use the stub account and anonymous users even are allowed
                # to update it. This is OK, because we don't care too much
                # about the accuracy of this data. Later if/when this becomes
                # a real account, anonymous users won't be able to update this
                # information -- that's what matters.
                user_form = UserForm(request.POST, instance=stub_account)
                profile_form = ProfileForm(request.POST,
                                           instance=stub_account.profile)

        else:
            user_form = UserForm(request.POST, instance=request.user)
            profile_form = ProfileForm(request.POST,
                                       instance=request.user.profile)

        if all([donation_form.is_valid(),
                user_form.is_valid(),
                profile_form.is_valid()]):
            # Process the data in form.cleaned_data
            cd_donation_form = donation_form.cleaned_data
            cd_user_form = user_form.cleaned_data
            cd_profile_form = profile_form.cleaned_data
            stripe_token = request.POST.get('stripeToken')

            # Route the payment to a payment provider
            response = route_and_process_donation(
                cd_donation_form,
                cd_user_form,
                stripe_token
            )
            logger.info("Payment routed with response: %s" % response)

            if response['status'] == Donation.AWAITING_PAYMENT:
                if request.user.is_anonymous() and not stub_account:
                    # Create a stub account with an unusable password
                    user, profile = create_stub_account(cd_user_form,
                                                        cd_profile_form)
                    user.save()
                    profile.save()
                else:
                    # Logged in user or an existing stub account.
                    user = user_form.save()
                    profile_form.save()

                donation = donation_form.save(commit=False)
                donation.status = response['status']
                donation.payment_id = response['payment_id']
                # Will only work for Paypal:
                donation.transaction_id = response.get('transaction_id')
                donation.donor = user
                donation.save()

                return HttpResponseRedirect(response['redirect'])

            else:
                logger.critical("Got back status of %s when making initial "
                                "request of API. Message was:\n%s" %
                                (response['status'], response['message']))
                message = response['message']
    else:
        # Loading the page...
        try:
            donation_form = DonationForm(initial={
                'referrer': request.GET.get('referrer')
            })
            user_form = UserForm(initial={
                'first_name': request.user.first_name,
                'last_name': request.user.last_name,
                'email': request.user.email,
            })
            up = request.user.profile
            profile_form = ProfileForm(initial={
                'address1': up.address1,
                'address2': up.address2,
                'city': up.city,
                'state': up.state,
                'zip_code': up.zip_code,
                'wants_newsletter': up.wants_newsletter
            })
        except AttributeError:
            # for anonymous users, who lack profile info
            user_form = UserForm()
            profile_form = ProfileForm()

    return render(request, 'donate.html', {
        'donation_form': donation_form,
        'user_form': user_form,
        'profile_form': profile_form,
        'private': False,
        'message': message,
        'stripe_public_key': settings.STRIPE_PUBLIC_KEY
    })