NewsBlur-viq/vendor/paypal/pro/helpers.py

332 lines
12 KiB
Python
Raw Normal View History

2010-10-16 18:59:02 -04:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
import datetime
2014-11-07 16:22:19 -08:00
import logging
2010-10-16 18:59:02 -04:00
import pprint
import time
from django.conf import settings
from django.forms.models import fields_for_model
2014-11-07 16:22:19 -08:00
from django.http import QueryDict
from django.utils.functional import cached_property
2010-10-16 18:59:02 -04:00
from django.utils.http import urlencode
2014-11-07 16:22:19 -08:00
from six.moves.urllib.request import urlopen
2010-10-16 18:59:02 -04:00
2014-11-07 16:22:19 -08:00
from paypal.pro.signals import payment_was_successful, recurring_cancel, recurring_suspend, recurring_reactivate, payment_profile_created
from paypal.pro.models import PayPalNVP
from paypal.pro.exceptions import PayPalFailure
2010-10-16 18:59:02 -04:00
2014-11-07 16:22:19 -08:00
USER = settings.PAYPAL_WPP_USER
2010-10-16 18:59:02 -04:00
PASSWORD = settings.PAYPAL_WPP_PASSWORD
SIGNATURE = settings.PAYPAL_WPP_SIGNATURE
2014-11-07 16:22:19 -08:00
VERSION = 116.0
BASE_PARAMS = dict(USER=USER, PWD=PASSWORD, SIGNATURE=SIGNATURE, VERSION=VERSION)
2010-10-16 18:59:02 -04:00
ENDPOINT = "https://api-3t.paypal.com/nvp"
SANDBOX_ENDPOINT = "https://api-3t.sandbox.paypal.com/nvp"
2014-11-07 16:22:19 -08:00
log = logging.getLogger(__file__)
2010-10-16 18:59:02 -04:00
def paypal_time(time_obj=None):
"""Returns a time suitable for PayPal time fields."""
if time_obj is None:
time_obj = time.gmtime()
return time.strftime(PayPalNVP.TIMESTAMP_FORMAT, time_obj)
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
def paypaltime2datetime(s):
"""Convert a PayPal time string to a DateTime."""
return datetime.datetime(*(time.strptime(s, PayPalNVP.TIMESTAMP_FORMAT)[:6]))
class PayPalError(TypeError):
"""Error thrown when something be wrong."""
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
class PayPalWPP(object):
"""
Wrapper class for the PayPal Website Payments Pro.
Website Payments Pro Integration Guide:
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_WPP_IntegrationGuide.pdf
Name-Value Pair API Developer Guide and Reference:
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_NVPAPI_DeveloperGuide.pdf
"""
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
def __init__(self, request, params=BASE_PARAMS):
"""Required - USER / PWD / SIGNATURE / VERSION"""
self.request = request
2014-11-07 16:22:19 -08:00
if getattr(settings, 'PAYPAL_TEST', True):
2010-10-16 18:59:02 -04:00
self.endpoint = SANDBOX_ENDPOINT
else:
self.endpoint = ENDPOINT
self.signature_values = params
self.signature = urlencode(self.signature_values) + "&"
2014-11-07 16:22:19 -08:00
@cached_property
def NVP_FIELDS(self):
# Put this onto class and load lazily, because in some cases there is an
# import order problem if we put it at module level.
return list(fields_for_model(PayPalNVP).keys())
2010-10-16 18:59:02 -04:00
def doDirectPayment(self, params):
"""Call PayPal DoDirectPayment method."""
defaults = {"method": "DoDirectPayment", "paymentaction": "Sale"}
2014-11-07 16:22:19 -08:00
required = ["creditcardtype",
"acct",
"expdate",
"cvv2",
"ipaddress",
"firstname",
"lastname",
"street",
"city",
"state",
"countrycode",
"zip",
"amt",
]
2010-10-16 18:59:02 -04:00
nvp_obj = self._fetch(params, required, defaults)
2014-11-07 16:22:19 -08:00
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
payment_was_successful.send(sender=nvp_obj, **params)
2010-10-16 18:59:02 -04:00
# @@@ Could check cvv2match / avscode are both 'X' or '0'
# qd = django.http.QueryDict(nvp_obj.response)
# if qd.get('cvv2match') not in ['X', '0']:
# nvp_obj.set_flag("Invalid cvv2match: %s" % qd.get('cvv2match')
# if qd.get('avscode') not in ['X', '0']:
# nvp_obj.set_flag("Invalid avscode: %s" % qd.get('avscode')
2014-11-07 16:22:19 -08:00
return nvp_obj
2010-10-16 18:59:02 -04:00
def setExpressCheckout(self, params):
"""
Initiates an Express Checkout transaction.
Optionally, the SetExpressCheckout API operation can set up billing agreements for
reference transactions and recurring payments.
Returns a NVP instance - check for token and payerid to continue!
"""
2014-11-07 16:22:19 -08:00
if "amt" in params:
import warnings
warnings.warn("'amt' has been deprecated. 'paymentrequest_0_amt' "
"should be used instead.", DeprecationWarning)
# Make a copy so we don't change things unexpectedly
params = params.copy()
params.update({'paymentrequest_0_amt': params['amt']})
del params['amt']
2010-10-16 18:59:02 -04:00
if self._is_recurring(params):
params = self._recurring_setExpressCheckout_adapter(params)
defaults = {"method": "SetExpressCheckout", "noshipping": 1}
2014-11-07 16:22:19 -08:00
required = ["returnurl", "cancelurl", "paymentrequest_0_amt"]
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def doExpressCheckoutPayment(self, params):
"""
Check the dude out:
"""
2014-11-07 16:22:19 -08:00
if "amt" in params:
import warnings
warnings.warn("'amt' has been deprecated. 'paymentrequest_0_amt' "
"should be used instead.", DeprecationWarning)
# Make a copy so we don't change things unexpectedly
params = params.copy()
params.update({'paymentrequest_0_amt': params['amt']})
del params['amt']
2010-10-16 18:59:02 -04:00
defaults = {"method": "DoExpressCheckoutPayment", "paymentaction": "Sale"}
2014-11-07 16:22:19 -08:00
required = ["returnurl", "cancelurl", "paymentrequest_0_amt", "token", "payerid"]
2010-10-16 18:59:02 -04:00
nvp_obj = self._fetch(params, required, defaults)
2014-11-07 16:22:19 -08:00
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
payment_was_successful.send(sender=nvp_obj, **params)
return nvp_obj
2010-10-16 18:59:02 -04:00
def createRecurringPaymentsProfile(self, params, direct=False):
"""
Set direct to True to indicate that this is being called as a directPayment.
Returns True PayPal successfully creates the profile otherwise False.
"""
defaults = {"method": "CreateRecurringPaymentsProfile"}
2014-11-07 16:22:19 -08:00
required = ["profilestartdate", "billingperiod", "billingfrequency", "amt"]
2010-10-16 18:59:02 -04:00
# Direct payments require CC data
if direct:
2014-11-07 16:22:19 -08:00
required + ["creditcardtype", "acct", "expdate", "firstname", "lastname"]
2010-10-16 18:59:02 -04:00
else:
2014-11-07 16:22:19 -08:00
required + ["token", "payerid"]
2010-10-16 18:59:02 -04:00
nvp_obj = self._fetch(params, required, defaults)
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
# Flag if profile_type != ActiveProfile
2014-11-07 16:22:19 -08:00
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
payment_profile_created.send(sender=nvp_obj, **params)
return nvp_obj
2010-10-16 18:59:02 -04:00
def getExpressCheckoutDetails(self, params):
2014-11-07 16:22:19 -08:00
defaults = {"method": "GetExpressCheckoutDetails"}
required = ["token"]
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def setCustomerBillingAgreement(self, params):
raise DeprecationWarning
2014-11-07 16:22:19 -08:00
def createBillingAgreement(self, params):
"""
Create a billing agreement for future use, without any initial payment
"""
defaults = {"method": "CreateBillingAgreement"}
required = ["token"]
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def getTransactionDetails(self, params):
2014-11-07 16:22:19 -08:00
defaults = {"method": "GetTransactionDetails"}
required = ["transactionid"]
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def massPay(self, params):
raise NotImplementedError
def getRecurringPaymentsProfileDetails(self, params):
raise NotImplementedError
def updateRecurringPaymentsProfile(self, params):
2014-11-07 16:22:19 -08:00
defaults = {"method": "UpdateRecurringPaymentsProfile"}
required = ["profileid"]
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def billOutstandingAmount(self, params):
raise NotImplementedError
2014-11-07 16:22:19 -08:00
def manangeRecurringPaymentsProfileStatus(self, params, fail_silently=False):
"""
Requires `profileid` and `action` params.
Action must be either "Cancel", "Suspend", or "Reactivate".
"""
defaults = {"method": "ManageRecurringPaymentsProfileStatus"}
required = ["profileid", "action"]
nvp_obj = self._fetch(params, required, defaults)
# TODO: This fail silently check should be using the error code, but its not easy to access
if not nvp_obj.flag or (
fail_silently and nvp_obj.flag_info == 'Invalid profile status for cancel action; profile should be active or suspended'):
if params['action'] == 'Cancel':
recurring_cancel.send(sender=nvp_obj)
elif params['action'] == 'Suspend':
recurring_suspend.send(sender=nvp_obj)
elif params['action'] == 'Reactivate':
recurring_reactivate.send(sender=nvp_obj)
else:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def refundTransaction(self, params):
raise NotImplementedError
2014-11-07 16:22:19 -08:00
def doReferenceTransaction(self, params):
"""
Process a payment from a buyer's account, identified by a previous
transaction.
The `paymentaction` param defaults to "Sale", but may also contain the
values "Authorization" or "Order".
"""
defaults = {"method": "DoReferenceTransaction",
"paymentaction": "Sale"}
required = ["referenceid", "amt"]
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj
2010-10-16 18:59:02 -04:00
def _is_recurring(self, params):
"""Returns True if the item passed is a recurring transaction."""
return 'billingfrequency' in params
def _recurring_setExpressCheckout_adapter(self, params):
"""
The recurring payment interface to SEC is different than the recurring payment
interface to ECP. This adapts a normal call to look like a SEC call.
"""
params['l_billingtype0'] = "RecurringPayments"
params['l_billingagreementdescription0'] = params['desc']
2014-11-07 16:22:19 -08:00
REMOVE = ["billingfrequency", "billingperiod", "profilestartdate", "desc"]
2010-10-16 18:59:02 -04:00
for k in params.keys():
if k in REMOVE:
del params[k]
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
return params
def _fetch(self, params, required, defaults):
"""Make the NVP request and store the response."""
defaults.update(params)
2014-11-07 16:22:19 -08:00
pp_params = self._check_and_update_params(required, defaults)
2010-10-16 18:59:02 -04:00
pp_string = self.signature + urlencode(pp_params)
response = self._request(pp_string)
response_params = self._parse_response(response)
2014-11-07 16:22:19 -08:00
if getattr(settings, 'PAYPAL_DEBUG', settings.DEBUG):
log.debug('PayPal Request:\n%s\n', pprint.pformat(defaults))
log.debug('PayPal Response:\n%s\n', pprint.pformat(response_params))
2010-10-16 18:59:02 -04:00
# Gather all NVP parameters to pass to a new instance.
nvp_params = {}
2014-11-07 16:22:19 -08:00
tmpd = defaults.copy()
tmpd.update(response_params)
for k, v in tmpd.items():
if k in self.NVP_FIELDS:
nvp_params[str(k)] = v
2010-10-16 18:59:02 -04:00
# PayPal timestamp has to be formatted.
if 'timestamp' in nvp_params:
nvp_params['timestamp'] = paypaltime2datetime(nvp_params['timestamp'])
nvp_obj = PayPalNVP(**nvp_params)
nvp_obj.init(self.request, params, response_params)
nvp_obj.save()
return nvp_obj
2014-11-07 16:22:19 -08:00
2010-10-16 18:59:02 -04:00
def _request(self, data):
"""Moved out to make testing easier."""
2014-11-07 16:22:19 -08:00
return urlopen(self.endpoint, data.encode("ascii")).read()
2010-10-16 18:59:02 -04:00
def _check_and_update_params(self, required, params):
"""
Ensure all required parameters were passed to the API call and format
them correctly.
"""
for r in required:
if r not in params:
2014-11-07 16:22:19 -08:00
raise PayPalError("Missing required param: %s" % r)
2010-10-16 18:59:02 -04:00
# Upper case all the parameters for PayPal.
2014-11-07 16:22:19 -08:00
return (dict((k.upper(), v) for k, v in params.items()))
2010-10-16 18:59:02 -04:00
def _parse_response(self, response):
"""Turn the PayPal response into a dict"""
2014-11-07 16:22:19 -08:00
q = QueryDict(response, encoding='UTF-8').dict()
return {k.lower(): v for k,v in q.items()}