示例#1
0
def get_donation_status_change_text(donation):
    donation_url = reverse_with_site_url(
        'donations:my-renewals', kwargs={
            'id': donation.subscription.id
        }) if donation.is_recurring else reverse_with_site_url(
            'donations:my-onetime-donations')
    if donation.user:
        url_text = str(
            _('Sign into our support page (click "forgot password?" if you have trouble logging in) to view your updated donation(%(url)s).'
              ) % {'url': donation_url}) + "\n"
    else:
        url_text = ''
    return _("""ONE-OFF DONATION STATUS UPDATED\n
\n
Dear %(name)s,\n
%(url_text)s
Details of your donation:\n
\n
Transaction ID: %(transaction_id)s\n
Donation frequency: %(frequency)s\n
Payment method: %(gateway)s\n
Donation amount: %(amount)s\n
Payment status: %(status)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'name': donation.donor_name(),
        'url_text': url_text,
        'transaction_id': donation.transaction_id,
        'frequency': donation.donation_frequency,
        'gateway': displayGateway(donation),
        'amount': displayDonationAmountWithCurrency(donation),
        'status': donation.payment_status,
        'sitename': get_site_name()
    }
示例#2
0
def build_onetime_request_body(donation):
    """Method to create body with CAPTURE intent"""
    # returl_url or cancel_url params in application_context tested to be only useful if order not initiated by Javascript APK
    return \
    {
        "intent": "CAPTURE",
        "application_context": {
            "brand_name": get_site_name(),
            "landing_page": "NO_PREFERENCE",
            "shipping_preference": "NO_SHIPPING",
            "user_action": "PAY_NOW",
            "return_url": reverse_with_site_url('donations:return-from-paypal'),
            "cancel_url": reverse_with_site_url('donations:cancel-from-paypal')
        },
        "purchase_units": [
            {
                "description": get_site_name() + str(_(" Onetime Donation")),
                "custom_id": donation.id,
                "amount": {
                    "currency_code": donation.currency,
                    "value": str(donation.donation_amount)
                }
            }
        ]
    }
示例#3
0
def get_donation_receipt_text(donation):
    donation_url = reverse_with_site_url(
        'donations:my-recurring-donations'
    ) if donation.is_recurring else reverse_with_site_url(
        'donations:my-onetime-donations')
    if donation.user:
        url_text = str(
            _('Sign into our support page (click "forgot password?" if you have trouble logging in) to view your donation(%(url)s). Please email [email protected] if you have any further enquiries.'
              ) % {'url': donation_url})
    else:
        url_text = ''
    return _("""NEW ONE-OFF DONATION\n
\n
Dear %(name)s,\n
A big "thank you" for your kind %(amount)s donation - it is very much appreciated and it will go a long way in supporting our operations.\n
Your contribution will be well-spent, allowing us to invest more in original reporting and safeguard press freedom. Please check out HKFP's latest Annual Report(https://hongkongfp.com/hong-kong-free-press-annual-report-2020/) - it includes our yearly, audited Transparency Report(https://hongkongfp.com/hong-kong-free-press-transparency-report-2019/), so you can see how carefully we spend our income.\n
%(url_text)s\n
From all of us, thank you for helping us keep independent media alive in Hong Kong!\n
Details of your donation:\n
\n
Transaction ID: %(transaction_id)s\n
Donation frequency: %(frequency)s\n
Payment method: %(gateway)s\n
Donation amount: %(amount)s\n
Payment status: %(status)s\n
%(recurring_status)s
\n
Thank you,\n
%(sitename)s""") % {
        'name':
        donation.donor_name(),
        'url_text':
        url_text,
        'transaction_id':
        donation.transaction_id,
        'frequency':
        donation.donation_frequency,
        'gateway':
        displayGateway(donation),
        'amount':
        displayDonationAmountWithCurrency(donation),
        'status':
        donation.payment_status,
        'recurring_status':
        'Recurring Status: ' + donation.subscription.recurring_status +
        "\n" if donation.is_recurring and donation.subscription else '',
        'sitename':
        get_site_name()
    }
示例#4
0
def get_donation_error_admin_text(donation, error_title, error_description):
    return _("""A Donation Error has occurred.\n
\n
Hi Admins,\n
This email is to inform you that a donation error has occurred on your website:\n
%(url)s\n
\n
Donation transaction ID: %(order)s\n
Donor: %(name)s\n
Error title: %(error_title)s\n
Error description: %(error_description)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'url':
        reverse_with_site_url('donations_donation_modeladmin_inspect',
                              kwargs={'instance_pk': donation.id}),
        'order':
        donation.transaction_id,
        'name':
        donation.donor_name(),
        'error_title':
        error_title,
        'error_description':
        error_description,
        'sitename':
        get_site_name()
    }
示例#5
0
def get_recurring_cancelled_donor_text(subscription):
    return _("""DONATION CANCELLED\n
\n
Dear %(name)s,\n
Thanks very much for your recent support.\n
Your recurring donation to HKFP has been suspended at your request - no further payments will be processed.\n
Sign into our support page(%(siteurl)s) (click "forgot password?" if you have trouble logging in) if you wish to support us again in the future. Please email [email protected] if you have any further enquiries.\n
From all of us, thank you for backing our team and helping us keep independent media alive in Hong Kong!\n
Details of your recurring donation:\n
\n
Donor: %(name)s\n
Recurring donation identifier: %(profile_id)s\n
Payment method: %(gateway)s\n
Recurring donation amount: %(amount)s\n
Recurring Status: %(recurring_status)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'name': subscription.user.fullname,
        'siteurl': get_site_url(),
        'url': reverse_with_site_url('donations:my-recurring-donations'),
        'profile_id': subscription.profile_id,
        'gateway': displayGateway(subscription),
        'amount': displayRecurringAmountWithCurrency(subscription),
        'recurring_status': subscription.recurring_status,
        'sitename': get_site_name()
    }
示例#6
0
def get_recurring_cancel_request_admin_text(subscription):
    return _("""Cancellation to a Recurring Donation is requested\n
\n
Hi Admins,\n
This email is to inform you that a cancellation to a recurring donation has been requested on your website. Please complete the request and manually change the subscription status to Cancelled at the link below:\n
%(url)s\n
\n
Donor: %(name)s\n
Recurring donation identifier: %(profile_id)s\n
Payment method: %(gateway)s\n
Recurring donation amount: %(amount)s\n
Recurring Status: %(recurring_status)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'url':
        reverse_with_site_url('donations_subscription_modeladmin_inspect',
                              kwargs={'instance_pk': subscription.id}),
        'name':
        subscription.user.fullname,
        'profile_id':
        subscription.profile_id,
        'gateway':
        subscription.gateway,
        'amount':
        displayRecurringAmountWithCurrency(subscription),
        'recurring_status':
        subscription.recurring_status,
        'sitename':
        get_site_name()
    }
示例#7
0
def get_recurring_resumed_donor_text(subscription):
    return _("""DONATION RESUMED\n
\n
Dear %(name)s,\n
A big "thank you" for resuming your %(amount)s contribution - it is very much appreciated and it will go a long way in supporting our operations. Recurring donations, in particular, are vital to our sustainability.
As an HKFP Patron, your contribution will be well-spent, allowing us to invest more in original reporting and safeguard press freedom. Please check out HKFP's latest Annual Report(https://hongkongfp.com/hong-kong-free-press-annual-report-2020/) - it includes our yearly, audited Transparency Report(https://hongkongfp.com/hong-kong-free-press-transparency-report-2019/), so you can see how carefully we spend our income.\n
Sign into our support page (click "forgot password?" if you have trouble logging in) to adjust or suspend your donation(%(url)s). Please email [email protected] if you have any further enquiries.\n
From all of us, thank you for helping us keep independent media alive in Hong Kong!\n
Details of your recurring donation:\n
\n
Donor: %(name)s\n
Recurring donation identifier: %(profile_id)s\n
Payment method: %(gateway)s\n
Recurring donation amount: %(amount)s\n
Recurring Status: %(recurring_status)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'name': subscription.user.fullname,
        'url': reverse_with_site_url('donations:my-recurring-donations'),
        'profile_id': subscription.profile_id,
        'gateway': displayGateway(subscription),
        'amount': displayRecurringAmountWithCurrency(subscription),
        'recurring_status': subscription.recurring_status,
        'sitename': get_site_name()
    }
示例#8
0
def get_recurring_paused_admin_text(subscription):
    return _("""A Recurring Donation is paused\n
\n
Hi Admins,\n
This email is to inform you that a recurring donation has been paused on your website:\n
%(url)s\n
\n
Donor: %(name)s\n
Recurring donation identifier: %(profile_id)s\n
Payment method: %(gateway)s\n
Recurring donation amount: %(amount)s\n
Recurring Status: %(recurring_status)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'url':
        reverse_with_site_url('donations_subscription_modeladmin_inspect',
                              kwargs={'instance_pk': subscription.id}),
        'name':
        subscription.user.fullname,
        'profile_id':
        subscription.profile_id,
        'gateway':
        subscription.gateway,
        'amount':
        displayRecurringAmountWithCurrency(subscription),
        'recurring_status':
        subscription.recurring_status,
        'sitename':
        get_site_name()
    }
示例#9
0
def get_donation_revoked_donor_text(donation):
    donation_url = reverse_with_site_url(
        'donations:my-recurring-donations'
    ) if donation.is_recurring else reverse_with_site_url(
        'donations:my-onetime-donations')
    if donation.user:
        url_text = str(
            _('Go to %(url)s to view your donation on the website.') %
            {'url': donation_url})
    else:
        url_text = ''
    return _("""DONATION REVOKED\n
\n
Dear %(name)s,\n
Your donation is unfortunately revoked by the payment gateway. %(url_text)s\n
Here are the details of your donation:\n
\n
Transaction ID: %(transaction_id)s\n
Donation frequency: %(frequency)s\n
Payment method: %(gateway)s\n
Donation amount: %(amount)s\n
Payment status: %(status)s\n
%(recurring_status)s
\n
Thank you,\n
%(sitename)s""") % {
        'name':
        donation.donor_name(),
        'url_text':
        url_text,
        'transaction_id':
        donation.transaction_id,
        'frequency':
        donation.donation_frequency,
        'gateway':
        displayGateway(donation),
        'amount':
        displayDonationAmountWithCurrency(donation),
        'status':
        donation.payment_status,
        'recurring_status':
        'Recurring Status: ' + donation.subscription.recurring_status +
        "\n" if donation.is_recurring and donation.subscription else '',
        'sitename':
        get_site_name()
    }
示例#10
0
 def getUnsubscriptionLink(self, email):
     user = get_object_or_404(User, email=email)
     digest = generateIDSecretHash(user.id)
     return reverse_with_site_url('unsubscribe',
                                  kwargs={
                                      'email': email,
                                      'hash': digest
                                  })
示例#11
0
def createSubscription(session, plan_id, donation, is_test=False):
    checkAccessTokenExpiry(session)
    paypalSettings = getPayPalSettings()
    api_url = paypalSettings.api_url + '/v1/billing/subscriptions'
    subscription_dict = {
        "plan_id": plan_id,
        "quantity": "1",
        "subscriber": {
            "name": {
                "given_name": getUserGivenName(donation.user),
                "surname": donation.user.last_name or ''
            },
            "email_address": donation.user.email
        },
        "application_context": {
            "brand_name":
            get_site_name(),
            "locale":
            "en-US",
            "shipping_preference":
            "NO_SHIPPING",
            "user_action":
            "SUBSCRIBE_NOW",
            "payment_method": {
                "payer_selected": "PAYPAL",
                "payee_preferred": "IMMEDIATE_PAYMENT_REQUIRED"
            },
            "return_url":
            reverse_with_site_url('donations:return-from-paypal')
            if not is_test else 'http://example.com/return/',
            "cancel_url":
            reverse_with_site_url('donations:cancel-from-paypal')
            if not is_test else 'http://example.com/cancel/'
        },
        "custom_id": str(donation.id)
    }
    if paypalSettings.sandbox_mode and session.get(
            'negtest_createSubscription', None):
        subscription_dict['plan_id'] = session.get(
            'negtest_createSubscription')
    return curlPaypal(api_url,
                      common_headers(session['paypal_token']),
                      post_data=json.dumps(subscription_dict))
示例#12
0
def get_subscription_status_change_text(subscription):
    return _("""RECURRING DONATION STATUS UPDATED\n
\n
Dear %(name)s,\n
Sign into our support page (click "forgot password?" if you have trouble logging in) to view your updated recurring donation(%(url)s).\n
Details of your recurring donation:\n
\n
Profile ID: %(profile_id)s\n
Payment method: %(gateway)s\n
Recurring Amount: %(amount)s\n
Status: %(status)s\n
\n
Thank you,\n
%(sitename)s""") % {
        'name': subscription.user.fullname,
        'url': reverse_with_site_url('donations:my-recurring-donations'),
        'profile_id': subscription.profile_id,
        'gateway': displayGateway(subscription),
        'amount': displayRecurringAmountWithCurrency(subscription),
        'status': subscription.recurring_status,
        'sitename': get_site_name()
    }
示例#13
0
def get_donation_revoked_admin_text(donation):
    return _("""A Donation is revoked\n
\n
Hi Admins,\n
This email is to inform you that a donation has been revoked on your website:\n
%(url)s\n
\n
Donor: %(name)s\n
Transaction ID: %(transaction_id)s\n
Donation frequency: %(frequency)s\n
Payment method: %(gateway)s\n
Donation amount: %(amount)s\n
Payment status: %(status)s\n
%(recurring_status)s
\n
Thank you,\n
%(sitename)s""") % {
        'url':
        reverse_with_site_url('donations_donation_modeladmin_inspect',
                              kwargs={'instance_pk': donation.id}),
        'name':
        donation.donor_name(),
        'transaction_id':
        donation.transaction_id,
        'frequency':
        donation.donation_frequency,
        'gateway':
        donation.gateway,
        'amount':
        displayDonationAmountWithCurrency(donation),
        'status':
        donation.payment_status,
        'recurring_status':
        'Recurring Status: ' + donation.subscription.recurring_status +
        "\n" if donation.is_recurring and donation.subscription else '',
        'sitename':
        get_site_name()
    }
示例#14
0
    def redirect_to_gateway_url(self):
        """ 
        Overriding parent implementation as 2C2P has to receive a form post from client browser.
        See docs https://developer.2c2p.com/docs/payment-requestresponse-parameters on recurring parameters behavior
        """
        data = {}
        data['version'] = REDIRECT_API_VERSION
        data['merchant_id'] = self.settings.merchant_id
        data['order_id'] = self.donation.transaction_id
        data['currency'] = getCurrencyDictAt(self.donation.currency)['code']
        # Beware: self.donation.donation_amount param is str in type
        data['amount'] = format_payment_amount(self.donation.donation_amount,
                                               self.donation.currency)
        # Apr 20 Tested result_url_1/2 working (such that merchant portal no need manual setting) after follow up with 2C2P Sum (an internal 2C2P settings needs to be turned on by them)
        # todo: Apr 21 2C2P server is not firing back the request from the new recurring payments (need follow up with Sum again)
        data['result_url_1'] = reverse_with_site_url(
            'donations:return-from-2c2p')
        data['result_url_2'] = reverse_with_site_url(
            'donations:verify-2c2p-response')
        data['user_defined_1'] = str(self.donation.id)

        if self.donation.is_recurring:
            data['payment_description'] = _(
                'Recurring Donation for %(site)s') % {
                    'site': get_site_name()
                }
            data['request_3ds'] = 'Y'
            data['recurring'] = 'Y'
            data['order_prefix'] = gen_order_prefix_2c2p()
            data['recurring_amount'] = format_payment_amount(
                self.donation.donation_amount, self.donation.currency)
            data['allow_accumulate'] = 'N'
            data['recurring_count'] = 0
            data['payment_option'] = 'A'
            # - daily recurring for testing
            data['recurring_interval'] = 1
            data['charge_next_date'] = getNextDateFromRecurringInterval(
                data['recurring_interval'], '%d%m%Y')
            # - monthly recurring(normal behavior)
            # getRecurringDateNextMonth requires the superuser to set the timezone first
            # data['charge_on_date'] = getRecurringDateNextMonth('%d%m')

            # append order_prefix to donation metas for distinguishment
            # dpmeta = DonationPaymentMeta(
            #     donation=self.donation, field_key='order_prefix', field_value=data['order_prefix'])
            # dpmeta.save()
        else:
            data['payment_description'] = _(
                'Onetime Donation for %(site)s') % {
                    'site': get_site_name()
                }

        params = ''
        for key in getRequestParamOrder():
            if key in data.keys():
                params += str(data[key])

        # python 3.6 code
        data['hash_value'] = hmac.new(bytes(self.settings.secret_key, 'utf-8'),
                                      bytes(params, 'utf-8'),
                                      hashlib.sha256).hexdigest()

        return render(self.request, 'donations/redirection_2c2p_form.html', {
            'action': self.base_gateway_redirect_url(),
            'data': data
        })
示例#15
0
def create_checkout_session(request):
    """ When the user reaches last step after confirming the donation,
        user is redirected via gatewayManager.redirect_to_gateway_url(), which renders redirection_stripe.html
        
        This function calls to Stripe Api to create a Stripe Session object,
        then this function returns the stripe session id to the stripe js api 'stripe.redirectToCheckout({ sessionId: session.id })' for the redirection to Stripe's checkout page

        Sample (JSON) request: {
            'csrfmiddlewaretoken': 'LZSpOsb364pn9R3gEPXdw2nN3dBEi7RWtMCBeaCse2QawCFIndu93fD3yv9wy0ij'
        }
        
        @todo: revise error handling, avoid catching all exceptions at the end
    """

    errorObj = {"issue": "Exception", "description": ""}
    try:
        initStripeApiKey()
        stripeSettings = getStripeSettings()

        donation_id = request.session.pop('donation_id', None)
        if not donation_id:
            raise ValueError(_("No donation_id in session"))

        # might throw DoesNotExist error
        donation = Donation.objects.get(pk=donation_id)

        # init session_kwargs with common parameters
        session_kwargs = {
            'payment_method_types': ['card'],
            'metadata': {
                'donation_id': donation.id
            },
            'success_url':
            reverse_with_site_url('donations:return-from-stripe') +
            '?stripe_session_id={CHECKOUT_SESSION_ID}',
            'cancel_url':
            reverse_with_site_url('donations:cancel-from-stripe') +
            '?stripe_session_id={CHECKOUT_SESSION_ID}',
            'idempotency_key': uuid4_str()
        }

        # try to get existing stripe customer
        donor_email = donation.user.email if donation.user else donation.guest_email
        customers = stripe.Customer.list(email=donor_email, limit=1)
        if len(customers['data']) > 0:
            session_kwargs['customer'] = customers['data'][0]['id']
        else:
            session_kwargs['customer_email'] = donor_email

        # Product should have been created by admin manually at the dashboard
        # todo: make sure the product_id in site_settings has been set by some kind of configuration enforcement before site is launched
        # stripe.error.InvalidRequestError would be raised if the product_id is either not found or empty/None
        product = stripe.Product.retrieve(stripeSettings.product_id)

        # ad-hoc price is used
        amount_str = formatDonationAmount(donation.donation_amount,
                                          donation.currency)
        adhoc_price = {
            'unit_amount_decimal': amount_str,
            'currency': donation.currency.lower(),
            'product': product.id
        }
        if donation.is_recurring:
            adhoc_price['recurring'] = {
                'interval': 'month',
                'interval_count': 1
            }
        session_kwargs['line_items'] = [{
            'price_data': adhoc_price,
            'quantity': 1,
        }]

        # set session mode
        session_mode = 'payment'
        if donation.is_recurring:
            session_mode = 'subscription'
        session_kwargs['mode'] = session_mode

        # set metadata
        if donation.is_recurring:
            session_kwargs['subscription_data'] = {
                'metadata': {
                    'donation_id': donation.id
                }
            }
        else:
            session_kwargs['payment_intent_data'] = {
                'metadata': {
                    'donation_id': donation.id
                }
            }

        session = stripe.checkout.Session.create(**session_kwargs)

        # save payment_intent id for recognition purposes when receiving the payment_intent.succeeded webhook for onetime donations
        if session.payment_intent:
            dpm = DonationPaymentMeta(donation=donation,
                                      field_key='stripe_payment_intent_id',
                                      field_value=session.payment_intent)
            dpm.save()

        return JsonResponse({'id': session.id})
    except ValueError as e:
        _exception(str(e))
        errorObj['issue'] = "ValueError"
        errorObj['description'] = str(e)
        return JsonResponse(object_to_json(errorObj), status=500)
    except Donation.DoesNotExist:
        _exception("Donation.DoesNotExist")
        errorObj['issue'] = "Donation.DoesNotExist"
        errorObj['description'] = str(
            _("Donation object not found by id: %(id)s") % {'id': donation_id})
        return JsonResponse(object_to_json(errorObj), status=500)
    except (stripe.error.RateLimitError, stripe.error.InvalidRequestError,
            stripe.error.AuthenticationError, stripe.error.APIConnectionError,
            stripe.error.StripeError) as e:
        _exception(
            "Stripe API Error({}): Status({}), Code({}), Param({}), Message({})"
            .format(
                type(e).__name__, e.http_status, e.code, e.param,
                e.user_message))
        errorObj['issue'] = type(e).__name__
        errorObj['description'] = 'Message is: %s' % e.user_message
        return JsonResponse(object_to_json(errorObj),
                            status=int(e.http_status))
    except Exception as e:
        errorObj['description'] = str(e)
        _exception(errorObj["description"])
        return JsonResponse(object_to_json(errorObj), status=500)