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_stripe_callback(request): """Always return 200 message or else the webhook will try again ~200 times and then send us an email. """ if request.method == 'POST': # Stripe hits us with a callback, and their security model is for us # to use the ID from that to hit their API. It's analogous to when you # get a random call and you call them back to make sure it's legit. event_id = json.loads(request.body)['id'] # Now use the API to call back. stripe.api_key = settings.STRIPE_SECRET_KEY event = json.loads(str(stripe.Event.retrieve(event_id))) logger.info( 'Stripe callback triggered. See webhook documentation for details.' ) if event['type'].startswith('charge') and \ event['livemode'] != settings.PAYMENT_TESTING_MODE: charge = event['data']['object'] try: d = Donation.objects.get(payment_id=charge['id']) except Donation.DoesNotExist: d = None # See: https://stripe.com/docs/api#event_types if event['type'].endswith('succeeded'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 4 send_thank_you_email(d) elif event['type'].endswith('failed'): if not d: return HttpResponse('<h1>200: No matching object in the ' 'database. No action needed.</h1>') d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 1 elif event['type'].endswith('refunded'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 7 elif event['type'].endswith('captured'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 8 elif event['type'].endswith('dispute.created'): logger.critical("Somebody has created a dispute in " "Stripe: %s" % charge['id']) elif event['type'].endswith('dispute.updated'): logger.critical("The Stripe dispute on charge %s has been " "updated." % charge['id']) elif event['type'].endswith('dispute.closed'): logger.critical("The Stripe dispute on charge %s has been " "closed." % charge['id']) d.save() return HttpResponse('<h1>200: OK</h1>') else: return HttpResponseNotAllowed( permitted_methods={'POST'}, content='<h1>405: This is a callback endpoint for a payment ' 'provider. Only POST methods are allowed.</h1>')
def process_stripe_callback(request): """Always return 200 message or else the webhook will try again ~200 times and then send us an email. """ if request.method == 'POST': # Stripe hits us with a callback, and their security model is for us # to use the ID from that to hit their API. It's analogous to when you # get a random call and you call them back to make sure it's legit. event_id = json.loads(request.body)['id'] # Now use the API to call back. stripe.api_key = settings.STRIPE_SECRET_KEY event = json.loads(str(stripe.Event.retrieve(event_id))) logger.info('Stripe callback triggered. See webhook documentation for details.') if event['type'].startswith('charge') and \ event['livemode'] != settings.PAYMENT_TESTING_MODE: charge = event['data']['object'] try: d = Donation.objects.get(payment_id=charge['id']) except Donation.DoesNotExist: d = None # See: https://stripe.com/docs/api#event_types if event['type'].endswith('succeeded'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 4 send_thank_you_email(d) elif event['type'].endswith('failed'): if not d: return HttpResponse('<h1>200: No matching object in the ' 'database. No action needed.</h1>') d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 1 elif event['type'].endswith('refunded'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 7 elif event['type'].endswith('captured'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = 8 elif event['type'].endswith('dispute.created'): logger.critical("Somebody has created a dispute in " "Stripe: %s" % charge['id']) elif event['type'].endswith('dispute.updated'): logger.critical("The Stripe dispute on charge %s has been " "updated." % charge['id']) elif event['type'].endswith('dispute.closed'): logger.critical("The Stripe dispute on charge %s has been " "closed." % charge['id']) d.save() return HttpResponse('<h1>200: OK</h1>') else: return HttpResponseNotAllowed( permitted_methods={'POST'}, content='<h1>405: This is a callback endpoint for a payment ' 'provider. Only POST methods are allowed.</h1>' )
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 send_thank_you_if_needed(d: Donation, charge: StripeChargeObject) -> None: """Send a thank you to the user if called for :param d: The donation object :param charge: The charge from the stripe event :return: None """ if charge["application"] == settings.XERO_APPLICATION_ID: # Don't send thank you's for Xero invoices return payment_type = charge["metadata"]["type"] recurring = charge["metadata"].get("recurring", False) send_thank_you_email(d, payment_type, recurring=recurring)
def process_paypal_callback(request: HttpRequest) -> HttpResponse: """Process the GET request that PayPal uses. After a transaction is completed, PayPal sends the user back to a page on our site. This could be our "Thanks" page, but since that page is seen by all the payment providers, instead this is an intermediate page, where we grab the correct things from the URL, process the item, and then shuttle the user off to the normal "Thanks" page. The other providers do this via a POST rather than a GET, so that's why this one is a bit of an oddball. """ try: access_token = get_paypal_access_token() except PaymentFailureException as e: logger.info(f"Unable to get PayPal access token. Message was: {e}") return HttpResponse(status=HTTP_503_SERVICE_UNAVAILABLE) d = Donation.objects.get(transaction_id=request.GET["token"]) r = requests.post( "%s/v1/payments/payment/%s/execute/" % (settings.PAYPAL_ENDPOINT, d.payment_id), headers={ "Content-Type": "application/json", "Authorization": "Bearer %s" % access_token, }, data=json.dumps({"payer_id": request.GET["PayerID"]}), timeout=30, ) if r.status_code == HTTP_200_OK: d.clearing_date = now() # Technically, this should be d.status = 2 (Completed, awaiting # processing) and we should await a webhook to tell us that the # processing completed successfully (4). Alas, PayPal is so terrible # that I can't figure that out, so we just assume that if it gets # completed (2), it'll get processed (4). d.status = Donation.PROCESSED d.save() send_thank_you_email(d, payment_type=PAYMENT_TYPES.DONATION) else: logger.critical( "Unable to execute PayPal transaction. Status code %s " "with data: %s" % (r.status_code, r.text) ) d.status = Donation.UNKNOWN_ERROR d.save() # Finally, show them the thank you page return HttpResponseRedirect(reverse("donate_complete"))
def process_paypal_callback(request): """Process the GET request that PayPal uses. After a transaction is completed, PayPal sends the user back to a page on our site. This could be our "Thanks" page, but since that page is seen by all the payment providers, instead this is an intermediate page, where we grab the correct things from the URL, process the item, and then shuttle the user off to the normal "Thanks" page. The other providers do this via a POST rather than a GET, so that's why this one is a bit of an oddball. """ access_token = get_paypal_access_token() d = Donation.objects.get(transaction_id=request.GET['token']) r = requests.post( '%s/v1/payments/payment/%s/execute/' % ( settings.PAYPAL_ENDPOINT, d.payment_id ), headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % access_token }, data=json.dumps({'payer_id': request.GET['PayerID']}), ) if r.status_code == HTTP_200_OK: d.clearing_date = now() # Technically, this should be d.status = 2 (Completed, awaiting # processing) and we should await a webhook to tell us that the # processing completed successfully (4). Alas, PayPal is so terrible # that I can't figure that out, so we just assume that if it gets # completed (2), it'll get processed (4). d.status = Donation.PROCESSED d.save() send_thank_you_email(d, payment_type=PAYMENT_TYPES.DONATION) else: logger.critical("Unable to execute PayPal transaction. Status code %s " "with data: %s" % (r.status_code, r.content)) d.status = Donation.UNKNOWN_ERROR d.save() # Finally, show them the thank you page return HttpResponseRedirect(reverse('donate_complete'))
def process_paypal_callback(request): """Process the GET request that PayPal uses. After a transaction is completed, PayPal sends the user back to a page on our site. This could be our "Thanks" page, but since that page is seen by all the payment providers, instead this is an intermediate page, where we grab the correct things from the URL, process the item, and then shuttle the user off to the normal "Thanks" page. The other providers do this via a POST rather than a GET, so that's why this one is a bit of an oddball. """ access_token = get_paypal_access_token() d = Donation.objects.get(transaction_id=request.GET['token']) r = requests.post( '%s/v1/payments/payment/%s/execute/' % (settings.PAYPAL_ENDPOINT, d.payment_id), headers={ 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % access_token }, data=json.dumps({'payer_id': request.GET['PayerID']}), ) if r.status_code == 200: d.clearing_date = now() # Technically, this should be d.status = 2 (Completed, awaiting # processing) and we should await a webhook to tell us that the # processing completed successfully (4). Alas, PayPal is so terrible # that I can't figure that out, so we just assume that if it gets # completed (2), it'll get processed (4). d.status = Donation.PROCESSED d.save() send_thank_you_email(d) else: logger.critical("Unable to execute PayPal transaction. Status code %s " "with data: %s" % (r.status_code, r.content)) d.status = Donation.UNKNOWN_ERROR d.save() # Finally, show them the thank you page return HttpResponseRedirect(reverse('paypal_complete'))
def process_stripe_callback(request): """Always return 200 message or else the webhook will try again ~200 times and then send us an email. """ if request.method == "POST": # Stripe hits us with a callback, and their security model is for us # to use the ID from that to hit their API. It's analogous to when you # get a random call and you call them back to make sure it's legit. event_id = json.loads(request.body)["id"] # Now use the API to call back. stripe.api_key = settings.STRIPE_SECRET_KEY event = json.loads(str(stripe.Event.retrieve(event_id))) logger.info( "Stripe callback triggered with event id of %s. See " "webhook documentation for details.", event_id, ) if ( event["type"].startswith("charge") and event["livemode"] != settings.PAYMENT_TESTING_MODE ): charge = event["data"]["object"] if charge["application"] == settings.XERO_APPLICATION_ID: handle_xero_payment(charge) # Sometimes stripe can process a transaction and call our callback # faster than we can even save things to our own DB. If that # happens wait a second up to five times until it works. retry_count = 5 d = None while retry_count > 0: try: d = Donation.objects.get(payment_id=charge["id"]) except Donation.DoesNotExist: time.sleep(1) retry_count -= 1 else: break # See: https://stripe.com/docs/api#event_types if event["type"].endswith("succeeded"): d.clearing_date = datetime.utcfromtimestamp( charge["created"] ).replace(tzinfo=utc) d.status = Donation.PROCESSED if charge["application"] == settings.XERO_APPLICATION_ID: # Don't send thank you's for Xero invoices pass else: payment_type = charge["metadata"]["type"] if charge["metadata"].get("recurring"): send_thank_you_email(d, payment_type, recurring=True) else: send_thank_you_email(d, payment_type) elif event["type"].endswith("failed"): if not d: return HttpResponse( "<h1>200: No matching object in the " "database. No action needed.</h1>" ) d.clearing_date = datetime.utcfromtimestamp( charge["created"] ).replace(tzinfo=utc) d.status = Donation.AWAITING_PAYMENT elif event["type"].endswith("refunded"): d.clearing_date = datetime.utcfromtimestamp( charge["created"] ).replace(tzinfo=utc) d.status = Donation.RECLAIMED_REFUNDED elif event["type"].endswith("captured"): d.clearing_date = datetime.utcfromtimestamp( charge["created"] ).replace(tzinfo=utc) d.status = Donation.CAPTURED elif event["type"].endswith("dispute.created"): logger.critical( "Somebody has created a dispute in " "Stripe: %s" % charge["id"] ) elif event["type"].endswith("dispute.updated"): logger.critical( "The Stripe dispute on charge %s has been " "updated." % charge["id"] ) elif event["type"].endswith("dispute.closed"): logger.critical( "The Stripe dispute on charge %s has been " "closed." % charge["id"] ) d.save() return HttpResponse("<h1>200: OK</h1>") else: return HttpResponseNotAllowed( permitted_methods={"POST"}, content="<h1>405: This is a callback endpoint for a payment " "provider. Only POST methods are allowed.</h1>", )
def process_stripe_callback(request): """Always return 200 message or else the webhook will try again ~200 times and then send us an email. """ if request.method == 'POST': # Stripe hits us with a callback, and their security model is for us # to use the ID from that to hit their API. It's analogous to when you # get a random call and you call them back to make sure it's legit. event_id = json.loads(request.body)['id'] # Now use the API to call back. stripe.api_key = settings.STRIPE_SECRET_KEY event = json.loads(str(stripe.Event.retrieve(event_id))) logger.info('Stripe callback triggered with event id of %s. See ' 'webhook documentation for details.', event_id) if event['type'].startswith('charge') and \ event['livemode'] != settings.PAYMENT_TESTING_MODE: charge = event['data']['object'] # Sometimes stripe can process a transaction and call our callback # faster than we can even save things to our own DB. If that # happens wait a second up to five times until it works. retry_count = 5 d = None while retry_count > 0: try: d = Donation.objects.get(payment_id=charge['id']) except Donation.DoesNotExist: time.sleep(1) retry_count -= 1 else: break # See: https://stripe.com/docs/api#event_types if event['type'].endswith('succeeded'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = Donation.PROCESSED payment_type = charge['metadata']['type'] if charge['metadata'].get('recurring'): send_thank_you_email(d, payment_type, recurring=True) else: send_thank_you_email(d, payment_type) elif event['type'].endswith('failed'): if not d: return HttpResponse('<h1>200: No matching object in the ' 'database. No action needed.</h1>') d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = Donation.AWAITING_PAYMENT elif event['type'].endswith('refunded'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = Donation.RECLAIMED_REFUNDED elif event['type'].endswith('captured'): d.clearing_date = datetime.utcfromtimestamp( charge['created']).replace(tzinfo=utc) d.status = Donation.CAPTURED elif event['type'].endswith('dispute.created'): logger.critical("Somebody has created a dispute in " "Stripe: %s" % charge['id']) elif event['type'].endswith('dispute.updated'): logger.critical("The Stripe dispute on charge %s has been " "updated." % charge['id']) elif event['type'].endswith('dispute.closed'): logger.critical("The Stripe dispute on charge %s has been " "closed." % charge['id']) d.save() return HttpResponse('<h1>200: OK</h1>') else: return HttpResponseNotAllowed( permitted_methods={'POST'}, content='<h1>405: This is a callback endpoint for a payment ' 'provider. Only POST methods are allowed.</h1>' )