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, }, )
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)
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, })
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, })
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)
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"], )
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'], )
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 })
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, )
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 })