mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-21 05:45:13 +00:00
199 lines
8.5 KiB
Python
Executable file
199 lines
8.5 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
from django.conf import settings
|
|
from django.template import RequestContext
|
|
from django.shortcuts import render_to_response
|
|
from django.http import HttpResponseRedirect
|
|
from django.utils.http import urlencode
|
|
|
|
from paypal.pro.forms import PaymentForm, ConfirmForm
|
|
from paypal.pro.helpers import PayPalWPP
|
|
from paypal.pro.exceptions import PayPalFailure
|
|
|
|
# PayPal Edit IPN URL:
|
|
# https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_profile-ipn-notify
|
|
EXPRESS_ENDPOINT = "https://www.paypal.com/webscr?cmd=_express-checkout&%s"
|
|
SANDBOX_EXPRESS_ENDPOINT = "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&%s"
|
|
|
|
|
|
class PayPalPro(object):
|
|
"""
|
|
This class-based view takes care of PayPal WebsitePaymentsPro (WPP).
|
|
PayPalPro has two separate flows - DirectPayment and ExpressPayFlow. In
|
|
DirectPayment the user buys on your site. In ExpressPayFlow the user is
|
|
direct to PayPal to confirm their purchase. PayPalPro implements both
|
|
flows. To it create an instance using the these parameters:
|
|
|
|
item: a dictionary that holds information about the item being purchased.
|
|
|
|
For single item purchase (pay once):
|
|
|
|
Required Keys:
|
|
* amt: Float amount of the item.
|
|
|
|
Optional Keys:
|
|
* custom: You can set this to help you identify a transaction.
|
|
* invnum: Unique ID that identifies this transaction.
|
|
|
|
For recurring billing:
|
|
|
|
Required Keys:
|
|
* amt: Float amount for each billing cycle.
|
|
* billingperiod: String unit of measure for the billing cycle (Day|Week|SemiMonth|Month|Year)
|
|
* billingfrequency: Integer number of periods that make up a cycle.
|
|
* profilestartdate: The date to begin billing. "2008-08-05T17:00:00Z" UTC/GMT
|
|
* desc: Description of what you're billing for.
|
|
|
|
Optional Keys:
|
|
* trialbillingperiod: String unit of measure for trial cycle (Day|Week|SemiMonth|Month|Year)
|
|
* trialbillingfrequency: Integer # of periods in a cycle.
|
|
* trialamt: Float amount to bill for the trial period.
|
|
* trialtotalbillingcycles: Integer # of cycles for the trial payment period.
|
|
* failedinitamtaction: set to continue on failure (ContinueOnFailure / CancelOnFailure)
|
|
* maxfailedpayments: number of payments before profile is suspended.
|
|
* autobilloutamt: automatically bill outstanding amount.
|
|
* subscribername: Full name of the person who paid.
|
|
* profilereference: Unique reference or invoice number.
|
|
* taxamt: How much tax.
|
|
* initamt: Initial non-recurring payment due upon creation.
|
|
* currencycode: defaults to USD
|
|
* + a bunch of shipping fields
|
|
|
|
payment_form_cls: form class that will be used to display the payment form.
|
|
It should inherit from `paypal.pro.forms.PaymentForm` if you're adding more.
|
|
|
|
payment_template: template used to ask the dude for monies. To comply with
|
|
PayPal standards it must include a link to PayPal Express Checkout.
|
|
|
|
confirm_form_cls: form class that will be used to display the confirmation form.
|
|
It should inherit from `paypal.pro.forms.ConfirmForm`. It is only used in the Express flow.
|
|
|
|
success_url / fail_url: URLs to be redirected to when the payment successful or fails.
|
|
"""
|
|
errors = {
|
|
"processing": "There was an error processing your payment. Check your information and try again.",
|
|
"form": "Please correct the errors below and try again.",
|
|
"paypal": "There was a problem contacting PayPal. Please try again later."
|
|
}
|
|
|
|
def __init__(self, item=None, payment_form_cls=PaymentForm,
|
|
payment_template="pro/payment.html", confirm_form_cls=ConfirmForm,
|
|
confirm_template="pro/confirm.html", success_url="?success",
|
|
fail_url=None, context=None, form_context_name="form"):
|
|
self.item = item
|
|
self.payment_form_cls = payment_form_cls
|
|
self.payment_template = payment_template
|
|
self.confirm_form_cls = confirm_form_cls
|
|
self.confirm_template = confirm_template
|
|
self.success_url = success_url
|
|
self.fail_url = fail_url
|
|
self.context = context or {}
|
|
self.form_context_name = form_context_name
|
|
|
|
def __call__(self, request):
|
|
"""Return the appropriate response for the state of the transaction."""
|
|
self.request = request
|
|
if request.method == "GET":
|
|
if self.should_redirect_to_express():
|
|
return self.redirect_to_express()
|
|
elif self.should_render_confirm_form():
|
|
return self.render_confirm_form()
|
|
elif self.should_render_payment_form():
|
|
return self.render_payment_form()
|
|
else:
|
|
if self.should_validate_confirm_form():
|
|
return self.validate_confirm_form()
|
|
elif self.should_validate_payment_form():
|
|
return self.validate_payment_form()
|
|
|
|
# Default to the rendering the payment form.
|
|
return self.render_payment_form()
|
|
|
|
def is_recurring(self):
|
|
return self.item is not None and 'billingperiod' in self.item
|
|
|
|
def should_redirect_to_express(self):
|
|
return 'express' in self.request.GET
|
|
|
|
def should_render_confirm_form(self):
|
|
return 'token' in self.request.GET and 'PayerID' in self.request.GET
|
|
|
|
def should_render_payment_form(self):
|
|
return True
|
|
|
|
def should_validate_confirm_form(self):
|
|
return 'token' in self.request.POST and 'PayerID' in self.request.POST
|
|
|
|
def should_validate_payment_form(self):
|
|
return True
|
|
|
|
def render_payment_form(self):
|
|
"""Display the DirectPayment for entering payment information."""
|
|
self.context[self.form_context_name] = self.payment_form_cls()
|
|
return render_to_response(self.payment_template, self.context, RequestContext(self.request))
|
|
|
|
def validate_payment_form(self):
|
|
"""Try to validate and then process the DirectPayment form."""
|
|
form = self.payment_form_cls(self.request.POST)
|
|
if form.is_valid():
|
|
success = form.process(self.request, self.item)
|
|
if success:
|
|
return HttpResponseRedirect(self.success_url)
|
|
else:
|
|
self.context['errors'] = self.errors['processing']
|
|
|
|
self.context[self.form_context_name] = form
|
|
self.context.setdefault("errors", self.errors['form'])
|
|
return render_to_response(self.payment_template, self.context, RequestContext(self.request))
|
|
|
|
def get_endpoint(self):
|
|
if getattr(settings, 'PAYPAL_TEST', True):
|
|
return SANDBOX_EXPRESS_ENDPOINT
|
|
else:
|
|
return EXPRESS_ENDPOINT
|
|
|
|
def redirect_to_express(self):
|
|
"""
|
|
First step of ExpressCheckout. Redirect the request to PayPal using the
|
|
data returned from setExpressCheckout.
|
|
"""
|
|
wpp = PayPalWPP(self.request)
|
|
try:
|
|
nvp_obj = wpp.setExpressCheckout(self.item)
|
|
except PayPalFailure:
|
|
self.context['errors'] = self.errors['paypal']
|
|
return self.render_payment_form()
|
|
else:
|
|
pp_params = dict(token=nvp_obj.token)
|
|
pp_url = self.get_endpoint() % urlencode(pp_params)
|
|
return HttpResponseRedirect(pp_url)
|
|
|
|
def render_confirm_form(self):
|
|
"""
|
|
Second step of ExpressCheckout. Display an order confirmation form which
|
|
contains hidden fields with the token / PayerID from PayPal.
|
|
"""
|
|
initial = dict(token=self.request.GET['token'], PayerID=self.request.GET['PayerID'])
|
|
self.context[self.form_context_name] = self.confirm_form_cls(initial=initial)
|
|
return render_to_response(self.confirm_template, self.context, RequestContext(self.request))
|
|
|
|
def validate_confirm_form(self):
|
|
"""
|
|
Third and final step of ExpressCheckout. Request has pressed the confirmation but
|
|
and we can send the final confirmation to PayPal using the data from the POST'ed form.
|
|
"""
|
|
wpp = PayPalWPP(self.request)
|
|
pp_data = dict(token=self.request.POST['token'], payerid=self.request.POST['PayerID'])
|
|
self.item.update(pp_data)
|
|
|
|
# @@@ This check and call could be moved into PayPalWPP.
|
|
try:
|
|
if self.is_recurring():
|
|
wpp.createRecurringPaymentsProfile(self.item)
|
|
else:
|
|
wpp.doExpressCheckoutPayment(self.item)
|
|
except PayPalFailure:
|
|
self.context['errors'] = self.errors['processing']
|
|
return self.render_payment_form()
|
|
else:
|
|
return HttpResponseRedirect(self.success_url)
|