Пример #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_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>')
Пример #3
0
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>'
        )
Пример #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)

            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,
        })
Пример #5
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,
    })
Пример #6
0
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)
Пример #7
0
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"))
Пример #8
0
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'))
Пример #9
0
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'))
Пример #10
0
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>",
        )
Пример #11
0
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>'
        )