mirror of
https://github.com/viq/NewsBlur.git
synced 2025-11-01 09:09:16 +00:00
Merge branch 'master' into queue
* master: Adding Paypal API.
This commit is contained in:
commit
52958522b5
7 changed files with 1188 additions and 0 deletions
11
vendor/paypalapi/__init__.py
vendored
Normal file
11
vendor/paypalapi/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# coding=utf-8
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
from paypalapi.interface import PayPalInterface
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
from paypalapi.settings import PayPalConfig
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
from paypalapi.exceptions import PayPalError, PayPalConfigError, PayPalAPIResponseError
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
import paypalapi.countries
|
||||||
|
|
||||||
|
VERSION = '1.2.0'
|
||||||
128
vendor/paypalapi/compat.py
vendored
Normal file
128
vendor/paypalapi/compat.py
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
pythoncompat, from python-requests.
|
||||||
|
|
||||||
|
Copyright (c) 2012 Kenneth Reitz.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# -------
|
||||||
|
# Pythons
|
||||||
|
# -------
|
||||||
|
|
||||||
|
# Syntax sugar.
|
||||||
|
_ver = sys.version_info
|
||||||
|
|
||||||
|
#: Python 2.x?
|
||||||
|
is_py2 = (_ver[0] == 2)
|
||||||
|
|
||||||
|
#: Python 3.x?
|
||||||
|
is_py3 = (_ver[0] == 3)
|
||||||
|
|
||||||
|
#: Python 3.0.x
|
||||||
|
is_py30 = (is_py3 and _ver[1] == 0)
|
||||||
|
|
||||||
|
#: Python 3.1.x
|
||||||
|
is_py31 = (is_py3 and _ver[1] == 1)
|
||||||
|
|
||||||
|
#: Python 3.2.x
|
||||||
|
is_py32 = (is_py3 and _ver[1] == 2)
|
||||||
|
|
||||||
|
#: Python 3.3.x
|
||||||
|
is_py33 = (is_py3 and _ver[1] == 3)
|
||||||
|
|
||||||
|
#: Python 3.4.x
|
||||||
|
is_py34 = (is_py3 and _ver[1] == 4)
|
||||||
|
|
||||||
|
#: Python 2.7.x
|
||||||
|
is_py27 = (is_py2 and _ver[1] == 7)
|
||||||
|
|
||||||
|
#: Python 2.6.x
|
||||||
|
is_py26 = (is_py2 and _ver[1] == 6)
|
||||||
|
|
||||||
|
#: Python 2.5.x
|
||||||
|
is_py25 = (is_py2 and _ver[1] == 5)
|
||||||
|
|
||||||
|
#: Python 2.4.x
|
||||||
|
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
|
||||||
|
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Platforms
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
|
||||||
|
# Syntax sugar.
|
||||||
|
_ver = sys.version.lower()
|
||||||
|
|
||||||
|
is_pypy = ('pypy' in _ver)
|
||||||
|
is_jython = ('jython' in _ver)
|
||||||
|
is_ironpython = ('iron' in _ver)
|
||||||
|
|
||||||
|
# Assume CPython, if nothing else.
|
||||||
|
is_cpython = not any((is_pypy, is_jython, is_ironpython))
|
||||||
|
|
||||||
|
# Windows-based system.
|
||||||
|
is_windows = 'win32' in str(sys.platform).lower()
|
||||||
|
|
||||||
|
# Standard Linux 2+ system.
|
||||||
|
is_linux = ('linux' in str(sys.platform).lower())
|
||||||
|
is_osx = ('darwin' in str(sys.platform).lower())
|
||||||
|
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
|
||||||
|
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
|
||||||
|
|
||||||
|
|
||||||
|
# ---------
|
||||||
|
# Specifics
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
|
||||||
|
if is_py2:
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from urllib import quote, unquote, urlencode
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from urlparse import urlparse, urlunparse, urljoin, urlsplit
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from urllib2 import parse_http_list
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
import cookielib
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from Cookie import Morsel
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
bytes = str
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
str = unicode
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility,PyUnboundLocalVariable
|
||||||
|
basestring = basestring
|
||||||
|
elif is_py3:
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from urllib.request import parse_http_list
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from http import cookiejar as cookielib
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from http.cookies import Morsel
|
||||||
|
#noinspection PyUnresolvedReferences,PyCompatibility
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
str = str
|
||||||
|
bytes = bytes
|
||||||
|
basestring = (str,bytes)
|
||||||
290
vendor/paypalapi/countries.py
vendored
Normal file
290
vendor/paypalapi/countries.py
vendored
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
"""
|
||||||
|
Country Code List: ISO 3166-1993 (E)
|
||||||
|
|
||||||
|
http://xml.coverpages.org/country3166.html
|
||||||
|
|
||||||
|
A tuple of tuples of country codes and their full names. There are a few helper
|
||||||
|
functions provided if you'd rather not use the dict directly. Examples provided
|
||||||
|
in the test_countries.py unit tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
COUNTRY_TUPLES = (
|
||||||
|
('US', 'United States of America'),
|
||||||
|
('CA', 'Canada'),
|
||||||
|
('AD', 'Andorra'),
|
||||||
|
('AE', 'United Arab Emirates'),
|
||||||
|
('AF', 'Afghanistan'),
|
||||||
|
('AG', 'Antigua & Barbuda'),
|
||||||
|
('AI', 'Anguilla'),
|
||||||
|
('AL', 'Albania'),
|
||||||
|
('AM', 'Armenia'),
|
||||||
|
('AN', 'Netherlands Antilles'),
|
||||||
|
('AO', 'Angola'),
|
||||||
|
('AQ', 'Antarctica'),
|
||||||
|
('AR', 'Argentina'),
|
||||||
|
('AS', 'American Samoa'),
|
||||||
|
('AT', 'Austria'),
|
||||||
|
('AU', 'Australia'),
|
||||||
|
('AW', 'Aruba'),
|
||||||
|
('AZ', 'Azerbaijan'),
|
||||||
|
('BA', 'Bosnia and Herzegovina'),
|
||||||
|
('BB', 'Barbados'),
|
||||||
|
('BD', 'Bangladesh'),
|
||||||
|
('BE', 'Belgium'),
|
||||||
|
('BF', 'Burkina Faso'),
|
||||||
|
('BG', 'Bulgaria'),
|
||||||
|
('BH', 'Bahrain'),
|
||||||
|
('BI', 'Burundi'),
|
||||||
|
('BJ', 'Benin'),
|
||||||
|
('BM', 'Bermuda'),
|
||||||
|
('BN', 'Brunei Darussalam'),
|
||||||
|
('BO', 'Bolivia'),
|
||||||
|
('BR', 'Brazil'),
|
||||||
|
('BS', 'Bahama'),
|
||||||
|
('BT', 'Bhutan'),
|
||||||
|
('BV', 'Bouvet Island'),
|
||||||
|
('BW', 'Botswana'),
|
||||||
|
('BY', 'Belarus'),
|
||||||
|
('BZ', 'Belize'),
|
||||||
|
('CC', 'Cocos (Keeling) Islands'),
|
||||||
|
('CF', 'Central African Republic'),
|
||||||
|
('CG', 'Congo'),
|
||||||
|
('CH', 'Switzerland'),
|
||||||
|
('CI', 'Ivory Coast'),
|
||||||
|
('CK', 'Cook Iislands'),
|
||||||
|
('CL', 'Chile'),
|
||||||
|
('CM', 'Cameroon'),
|
||||||
|
('CN', 'China'),
|
||||||
|
('CO', 'Colombia'),
|
||||||
|
('CR', 'Costa Rica'),
|
||||||
|
('CU', 'Cuba'),
|
||||||
|
('CV', 'Cape Verde'),
|
||||||
|
('CX', 'Christmas Island'),
|
||||||
|
('CY', 'Cyprus'),
|
||||||
|
('CZ', 'Czech Republic'),
|
||||||
|
('DE', 'Germany'),
|
||||||
|
('DJ', 'Djibouti'),
|
||||||
|
('DK', 'Denmark'),
|
||||||
|
('DM', 'Dominica'),
|
||||||
|
('DO', 'Dominican Republic'),
|
||||||
|
('DZ', 'Algeria'),
|
||||||
|
('EC', 'Ecuador'),
|
||||||
|
('EE', 'Estonia'),
|
||||||
|
('EG', 'Egypt'),
|
||||||
|
('EH', 'Western Sahara'),
|
||||||
|
('ER', 'Eritrea'),
|
||||||
|
('ES', 'Spain'),
|
||||||
|
('ET', 'Ethiopia'),
|
||||||
|
('FI', 'Finland'),
|
||||||
|
('FJ', 'Fiji'),
|
||||||
|
('FK', 'Falkland Islands (Malvinas)'),
|
||||||
|
('FM', 'Micronesia'),
|
||||||
|
('FO', 'Faroe Islands'),
|
||||||
|
('FR', 'France'),
|
||||||
|
('FX', 'France, Metropolitan'),
|
||||||
|
('GA', 'Gabon'),
|
||||||
|
('GB', 'United Kingdom (Great Britain)'),
|
||||||
|
('GD', 'Grenada'),
|
||||||
|
('GE', 'Georgia'),
|
||||||
|
('GF', 'French Guiana'),
|
||||||
|
('GH', 'Ghana'),
|
||||||
|
('GI', 'Gibraltar'),
|
||||||
|
('GL', 'Greenland'),
|
||||||
|
('GM', 'Gambia'),
|
||||||
|
('GN', 'Guinea'),
|
||||||
|
('GP', 'Guadeloupe'),
|
||||||
|
('GQ', 'Equatorial Guinea'),
|
||||||
|
('GR', 'Greece'),
|
||||||
|
('GS', 'South Georgia and the South Sandwich Islands'),
|
||||||
|
('GT', 'Guatemala'),
|
||||||
|
('GU', 'Guam'),
|
||||||
|
('GW', 'Guinea-Bissau'),
|
||||||
|
('GY', 'Guyana'),
|
||||||
|
('HK', 'Hong Kong'),
|
||||||
|
('HM', 'Heard & McDonald Islands'),
|
||||||
|
('HN', 'Honduras'),
|
||||||
|
('HR', 'Croatia'),
|
||||||
|
('HT', 'Haiti'),
|
||||||
|
('HU', 'Hungary'),
|
||||||
|
('ID', 'Indonesia'),
|
||||||
|
('IE', 'Ireland'),
|
||||||
|
('IL', 'Israel'),
|
||||||
|
('IN', 'India'),
|
||||||
|
('IO', 'British Indian Ocean Territory'),
|
||||||
|
('IQ', 'Iraq'),
|
||||||
|
('IR', 'Islamic Republic of Iran'),
|
||||||
|
('IS', 'Iceland'),
|
||||||
|
('IT', 'Italy'),
|
||||||
|
('JM', 'Jamaica'),
|
||||||
|
('JO', 'Jordan'),
|
||||||
|
('JP', 'Japan'),
|
||||||
|
('KE', 'Kenya'),
|
||||||
|
('KG', 'Kyrgyzstan'),
|
||||||
|
('KH', 'Cambodia'),
|
||||||
|
('KI', 'Kiribati'),
|
||||||
|
('KM', 'Comoros'),
|
||||||
|
('KN', 'St. Kitts and Nevis'),
|
||||||
|
('KP', 'Korea, Democratic People\'s Republic of'),
|
||||||
|
('KR', 'Korea, Republic of'),
|
||||||
|
('KW', 'Kuwait'),
|
||||||
|
('KY', 'Cayman Islands'),
|
||||||
|
('KZ', 'Kazakhstan'),
|
||||||
|
('LA', 'Lao People\'s Democratic Republic'),
|
||||||
|
('LB', 'Lebanon'),
|
||||||
|
('LC', 'Saint Lucia'),
|
||||||
|
('LI', 'Liechtenstein'),
|
||||||
|
('LK', 'Sri Lanka'),
|
||||||
|
('LR', 'Liberia'),
|
||||||
|
('LS', 'Lesotho'),
|
||||||
|
('LT', 'Lithuania'),
|
||||||
|
('LU', 'Luxembourg'),
|
||||||
|
('LV', 'Latvia'),
|
||||||
|
('LY', 'Libyan Arab Jamahiriya'),
|
||||||
|
('MA', 'Morocco'),
|
||||||
|
('MC', 'Monaco'),
|
||||||
|
('MD', 'Moldova, Republic of'),
|
||||||
|
('MG', 'Madagascar'),
|
||||||
|
('MH', 'Marshall Islands'),
|
||||||
|
('ML', 'Mali'),
|
||||||
|
('MN', 'Mongolia'),
|
||||||
|
('MM', 'Myanmar'),
|
||||||
|
('MO', 'Macau'),
|
||||||
|
('MP', 'Northern Mariana Islands'),
|
||||||
|
('MQ', 'Martinique'),
|
||||||
|
('MR', 'Mauritania'),
|
||||||
|
('MS', 'Monserrat'),
|
||||||
|
('MT', 'Malta'),
|
||||||
|
('MU', 'Mauritius'),
|
||||||
|
('MV', 'Maldives'),
|
||||||
|
('MW', 'Malawi'),
|
||||||
|
('MX', 'Mexico'),
|
||||||
|
('MY', 'Malaysia'),
|
||||||
|
('MZ', 'Mozambique'),
|
||||||
|
('NA', 'Namibia'),
|
||||||
|
('NC', 'New Caledonia'),
|
||||||
|
('NE', 'Niger'),
|
||||||
|
('NF', 'Norfolk Island'),
|
||||||
|
('NG', 'Nigeria'),
|
||||||
|
('NI', 'Nicaragua'),
|
||||||
|
('NL', 'Netherlands'),
|
||||||
|
('NO', 'Norway'),
|
||||||
|
('NP', 'Nepal'),
|
||||||
|
('NR', 'Nauru'),
|
||||||
|
('NU', 'Niue'),
|
||||||
|
('NZ', 'New Zealand'),
|
||||||
|
('OM', 'Oman'),
|
||||||
|
('PA', 'Panama'),
|
||||||
|
('PE', 'Peru'),
|
||||||
|
('PF', 'French Polynesia'),
|
||||||
|
('PG', 'Papua New Guinea'),
|
||||||
|
('PH', 'Philippines'),
|
||||||
|
('PK', 'Pakistan'),
|
||||||
|
('PL', 'Poland'),
|
||||||
|
('PM', 'St. Pierre & Miquelon'),
|
||||||
|
('PN', 'Pitcairn'),
|
||||||
|
('PR', 'Puerto Rico'),
|
||||||
|
('PT', 'Portugal'),
|
||||||
|
('PW', 'Palau'),
|
||||||
|
('PY', 'Paraguay'),
|
||||||
|
('QA', 'Qatar'),
|
||||||
|
('RE', 'Reunion'),
|
||||||
|
('RO', 'Romania'),
|
||||||
|
('RU', 'Russian Federation'),
|
||||||
|
('RW', 'Rwanda'),
|
||||||
|
('SA', 'Saudi Arabia'),
|
||||||
|
('SB', 'Solomon Islands'),
|
||||||
|
('SC', 'Seychelles'),
|
||||||
|
('SD', 'Sudan'),
|
||||||
|
('SE', 'Sweden'),
|
||||||
|
('SG', 'Singapore'),
|
||||||
|
('SH', 'St. Helena'),
|
||||||
|
('SI', 'Slovenia'),
|
||||||
|
('SJ', 'Svalbard & Jan Mayen Islands'),
|
||||||
|
('SK', 'Slovakia'),
|
||||||
|
('SL', 'Sierra Leone'),
|
||||||
|
('SM', 'San Marino'),
|
||||||
|
('SN', 'Senegal'),
|
||||||
|
('SO', 'Somalia'),
|
||||||
|
('SR', 'Suriname'),
|
||||||
|
('ST', 'Sao Tome & Principe'),
|
||||||
|
('SV', 'El Salvador'),
|
||||||
|
('SY', 'Syrian Arab Republic'),
|
||||||
|
('SZ', 'Swaziland'),
|
||||||
|
('TC', 'Turks & Caicos Islands'),
|
||||||
|
('TD', 'Chad'),
|
||||||
|
('TF', 'French Southern Territories'),
|
||||||
|
('TG', 'Togo'),
|
||||||
|
('TH', 'Thailand'),
|
||||||
|
('TJ', 'Tajikistan'),
|
||||||
|
('TK', 'Tokelau'),
|
||||||
|
('TM', 'Turkmenistan'),
|
||||||
|
('TN', 'Tunisia'),
|
||||||
|
('TO', 'Tonga'),
|
||||||
|
('TP', 'East Timor'),
|
||||||
|
('TR', 'Turkey'),
|
||||||
|
('TT', 'Trinidad & Tobago'),
|
||||||
|
('TV', 'Tuvalu'),
|
||||||
|
('TW', 'Taiwan, Province of China'),
|
||||||
|
('TZ', 'Tanzania, United Republic of'),
|
||||||
|
('UA', 'Ukraine'),
|
||||||
|
('UG', 'Uganda'),
|
||||||
|
('UM', 'United States Minor Outlying Islands'),
|
||||||
|
('UY', 'Uruguay'),
|
||||||
|
('UZ', 'Uzbekistan'),
|
||||||
|
('VA', 'Vatican City State (Holy See)'),
|
||||||
|
('VC', 'St. Vincent & the Grenadines'),
|
||||||
|
('VE', 'Venezuela'),
|
||||||
|
('VG', 'British Virgin Islands'),
|
||||||
|
('VI', 'United States Virgin Islands'),
|
||||||
|
('VN', 'Viet Nam'),
|
||||||
|
('VU', 'Vanuatu'),
|
||||||
|
('WF', 'Wallis & Futuna Islands'),
|
||||||
|
('WS', 'Samoa'),
|
||||||
|
('YE', 'Yemen'),
|
||||||
|
('YT', 'Mayotte'),
|
||||||
|
('YU', 'Yugoslavia'),
|
||||||
|
('ZA', 'South Africa'),
|
||||||
|
('ZM', 'Zambia'),
|
||||||
|
('ZR', 'Zaire'),
|
||||||
|
('ZW', 'Zimbabwe'),
|
||||||
|
('ZZ', 'Unknown or unspecified country'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_valid_country_abbrev(abbrev, case_sensitive=False):
|
||||||
|
"""
|
||||||
|
Given a country code abbreviation, check to see if it matches the
|
||||||
|
country table.
|
||||||
|
|
||||||
|
abbrev: (str) Country code to evaluate.
|
||||||
|
case_sensitive: (bool) When True, enforce case sensitivity.
|
||||||
|
|
||||||
|
Returns True if valid, False if not.
|
||||||
|
"""
|
||||||
|
if case_sensitive:
|
||||||
|
country_code = abbrev
|
||||||
|
else:
|
||||||
|
country_code = abbrev.upper()
|
||||||
|
|
||||||
|
for code, full_name in COUNTRY_TUPLES:
|
||||||
|
if country_code == code:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_name_from_abbrev(abbrev, case_sensitive=False):
|
||||||
|
"""
|
||||||
|
Given a country code abbreviation, get the full name from the table.
|
||||||
|
|
||||||
|
abbrev: (str) Country code to retrieve the full name of.
|
||||||
|
case_sensitive: (bool) When True, enforce case sensitivity.
|
||||||
|
"""
|
||||||
|
if case_sensitive:
|
||||||
|
country_code = abbrev
|
||||||
|
else:
|
||||||
|
country_code = abbrev.upper()
|
||||||
|
|
||||||
|
for code, full_name in COUNTRY_TUPLES:
|
||||||
|
if country_code == code:
|
||||||
|
return full_name
|
||||||
|
|
||||||
|
raise KeyError('No country with that country code.')
|
||||||
45
vendor/paypalapi/exceptions.py
vendored
Normal file
45
vendor/paypalapi/exceptions.py
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
Various PayPal API related exceptions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class PayPalError(Exception):
|
||||||
|
"""
|
||||||
|
Used to denote some kind of generic error. This does not include errors
|
||||||
|
returned from PayPal API responses. Those are handled by the more
|
||||||
|
specific exception classes below.
|
||||||
|
"""
|
||||||
|
def __init__(self, message, error_code=None):
|
||||||
|
Exception.__init__(self, message, error_code)
|
||||||
|
self.message = message
|
||||||
|
self.error_code = error_code
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.error_code:
|
||||||
|
return "%s (Error Code: %s)" % (repr(self.message), self.error_code)
|
||||||
|
else:
|
||||||
|
return repr(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
class PayPalConfigError(PayPalError):
|
||||||
|
"""
|
||||||
|
Raised when a configuration problem arises.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PayPalAPIResponseError(PayPalError):
|
||||||
|
"""
|
||||||
|
Raised when there is an error coming back with a PayPal NVP API response.
|
||||||
|
|
||||||
|
Pipe the error message from the API to the exception, along with
|
||||||
|
the error code.
|
||||||
|
"""
|
||||||
|
def __init__(self, response):
|
||||||
|
self.response = response
|
||||||
|
self.error_code = int(getattr(response, 'L_ERRORCODE0', -1))
|
||||||
|
self.message = getattr(response, 'L_LONGMESSAGE0', None)
|
||||||
|
self.short_message = getattr(response, 'L_SHORTMESSAGE0', None)
|
||||||
|
self.correlation_id = getattr(response, 'CORRELATIONID', None)
|
||||||
|
|
||||||
|
super(PayPalAPIResponseError, self).__init__(self.message, self.error_code)
|
||||||
469
vendor/paypalapi/interface.py
vendored
Normal file
469
vendor/paypalapi/interface.py
vendored
Normal file
|
|
@ -0,0 +1,469 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
The end developer will do most of their work with the PayPalInterface class found
|
||||||
|
in this module. Configuration, querying, and manipulation can all be done
|
||||||
|
with it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import types
|
||||||
|
import logging
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from paypalapi.settings import PayPalConfig
|
||||||
|
from paypalapi.response import PayPalResponse
|
||||||
|
from paypalapi.exceptions import PayPalError, PayPalAPIResponseError
|
||||||
|
from paypalapi.compat import is_py3
|
||||||
|
|
||||||
|
if is_py3:
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
import urllib.parse
|
||||||
|
else:
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
logger = logging.getLogger('paypal.interface')
|
||||||
|
|
||||||
|
class PayPalInterface(object):
|
||||||
|
"""
|
||||||
|
The end developers will do 95% of their work through this class. API
|
||||||
|
queries, configuration, etc, all go through here. See the __init__ method
|
||||||
|
for config related details.
|
||||||
|
"""
|
||||||
|
def __init__(self, config=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Constructor, which passes all config directives to the config class
|
||||||
|
via kwargs. For example:
|
||||||
|
|
||||||
|
paypal = PayPalInterface(API_USERNAME='somevalue')
|
||||||
|
|
||||||
|
Optionally, you may pass a 'config' kwarg to provide your own
|
||||||
|
PayPalConfig object.
|
||||||
|
"""
|
||||||
|
if config:
|
||||||
|
# User provided their own PayPalConfig object.
|
||||||
|
self.config = config
|
||||||
|
else:
|
||||||
|
# Take the kwargs and stuff them in a new PayPalConfig object.
|
||||||
|
self.config = PayPalConfig(**kwargs)
|
||||||
|
|
||||||
|
def _encode_utf8(self, **kwargs):
|
||||||
|
"""
|
||||||
|
UTF8 encodes all of the NVP values.
|
||||||
|
"""
|
||||||
|
if is_py3:
|
||||||
|
# This is only valid for Python 2. In Python 3, unicode is
|
||||||
|
# everywhere (yay).
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
unencoded_pairs = kwargs
|
||||||
|
for i in unencoded_pairs.keys():
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
if isinstance(unencoded_pairs[i], types.UnicodeType):
|
||||||
|
unencoded_pairs[i] = unencoded_pairs[i].encode('utf-8')
|
||||||
|
return unencoded_pairs
|
||||||
|
|
||||||
|
def _check_required(self, requires, **kwargs):
|
||||||
|
"""
|
||||||
|
Checks kwargs for the values specified in 'requires', which is a tuple
|
||||||
|
of strings. These strings are the NVP names of the required values.
|
||||||
|
"""
|
||||||
|
for req in requires:
|
||||||
|
# PayPal api is never mixed-case.
|
||||||
|
if req.lower() not in kwargs and req.upper() not in kwargs:
|
||||||
|
raise PayPalError('missing required : %s' % req)
|
||||||
|
|
||||||
|
def _sanitize_locals(self, data):
|
||||||
|
"""
|
||||||
|
Remove the 'self' key in locals()
|
||||||
|
It's more explicit to do it in one function
|
||||||
|
"""
|
||||||
|
if 'self' in data:
|
||||||
|
data = data.copy()
|
||||||
|
del data['self']
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _call(self, method, **kwargs):
|
||||||
|
"""
|
||||||
|
Wrapper method for executing all API commands over HTTP. This method is
|
||||||
|
further used to implement wrapper methods listed here:
|
||||||
|
|
||||||
|
https://www.x.com/docs/DOC-1374
|
||||||
|
|
||||||
|
``method`` must be a supported NVP method listed at the above address.
|
||||||
|
|
||||||
|
``kwargs`` will be a hash of
|
||||||
|
"""
|
||||||
|
# This dict holds the key/value pairs to pass to the PayPal API.
|
||||||
|
url_values = {
|
||||||
|
'METHOD': method,
|
||||||
|
'VERSION': self.config.API_VERSION,
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.config.API_AUTHENTICATION_MODE == "3TOKEN":
|
||||||
|
url_values['USER'] = self.config.API_USERNAME
|
||||||
|
url_values['PWD'] = self.config.API_PASSWORD
|
||||||
|
url_values['SIGNATURE'] = self.config.API_SIGNATURE
|
||||||
|
elif self.config.API_AUTHENTICATION_MODE == "UNIPAY":
|
||||||
|
url_values['SUBJECT'] = self.config.UNIPAY_SUBJECT
|
||||||
|
|
||||||
|
# All values passed to PayPal API must be uppercase.
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
url_values[key.upper()] = value
|
||||||
|
|
||||||
|
# This shows all of the key/val pairs we're sending to PayPal.
|
||||||
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
|
logger.debug('PayPal NVP Query Key/Vals:\n%s' % pformat(url_values))
|
||||||
|
|
||||||
|
req = requests.get(
|
||||||
|
self.config.API_ENDPOINT,
|
||||||
|
params=url_values,
|
||||||
|
timeout=self.config.HTTP_TIMEOUT,
|
||||||
|
verify=self.config.API_CA_CERTS,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call paypal API
|
||||||
|
response = PayPalResponse(req.text, self.config)
|
||||||
|
|
||||||
|
logger.debug('PayPal NVP API Endpoint: %s' % self.config.API_ENDPOINT)
|
||||||
|
|
||||||
|
if not response.success:
|
||||||
|
logger.error('A PayPal API error was encountered.')
|
||||||
|
logger.error('PayPal NVP Query Key/Vals:\n%s' % pformat(url_values))
|
||||||
|
logger.error('PayPal NVP Query Response')
|
||||||
|
logger.error(response)
|
||||||
|
raise PayPalAPIResponseError(response)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def address_verify(self, email, street, zip):
|
||||||
|
"""Shortcut for the AddressVerify method.
|
||||||
|
|
||||||
|
``email``::
|
||||||
|
Email address of a PayPal member to verify.
|
||||||
|
Maximum string length: 255 single-byte characters
|
||||||
|
Input mask: ?@?.??
|
||||||
|
``street``::
|
||||||
|
First line of the billing or shipping postal address to verify.
|
||||||
|
|
||||||
|
To pass verification, the value of Street must match the first three
|
||||||
|
single-byte characters of a postal address on file for the PayPal member.
|
||||||
|
|
||||||
|
Maximum string length: 35 single-byte characters.
|
||||||
|
Alphanumeric plus - , . ‘ # \
|
||||||
|
Whitespace and case of input value are ignored.
|
||||||
|
``zip``::
|
||||||
|
Postal code to verify.
|
||||||
|
|
||||||
|
To pass verification, the value of Zip mustmatch the first five
|
||||||
|
single-byte characters of the postal code of the verified postal
|
||||||
|
address for the verified PayPal member.
|
||||||
|
|
||||||
|
Maximumstring length: 16 single-byte characters.
|
||||||
|
Whitespace and case of input value are ignored.
|
||||||
|
"""
|
||||||
|
args = self._sanitize_locals(locals())
|
||||||
|
return self._call('AddressVerify', **args)
|
||||||
|
|
||||||
|
def create_recurring_payments_profile(self, **kwargs):
|
||||||
|
"""Shortcut for the CreateRecurringPaymentsProfile method.
|
||||||
|
Currently, this method only supports the Direct Payment flavor.
|
||||||
|
|
||||||
|
It requires standard credit card information and a few additional
|
||||||
|
parameters related to the billing. e.g.:
|
||||||
|
|
||||||
|
profile_info = {
|
||||||
|
# Credit card information
|
||||||
|
'creditcardtype': 'Visa',
|
||||||
|
'acct': '4812177017895760',
|
||||||
|
'expdate': '102015',
|
||||||
|
'cvv2': '123',
|
||||||
|
'firstname': 'John',
|
||||||
|
'lastname': 'Doe',
|
||||||
|
'street': '1313 Mockingbird Lane',
|
||||||
|
'city': 'Beverly Hills',
|
||||||
|
'state': 'CA',
|
||||||
|
'zip': '90110',
|
||||||
|
'countrycode': 'US',
|
||||||
|
'currencycode': 'USD',
|
||||||
|
# Recurring payment information
|
||||||
|
'profilestartdate': '2010-10-25T0:0:0',
|
||||||
|
'billingperiod': 'Month',
|
||||||
|
'billingfrequency': '6',
|
||||||
|
'amt': '10.00',
|
||||||
|
'desc': '6 months of our product.'
|
||||||
|
}
|
||||||
|
response = create_recurring_payments_profile(**profile_info)
|
||||||
|
|
||||||
|
The above NVPs compose the bare-minimum request for creating a
|
||||||
|
profile. For the complete list of parameters, visit this URI:
|
||||||
|
https://www.x.com/docs/DOC-1168
|
||||||
|
"""
|
||||||
|
return self._call('CreateRecurringPaymentsProfile', **kwargs)
|
||||||
|
|
||||||
|
def do_authorization(self, transactionid, amt):
|
||||||
|
"""Shortcut for the DoAuthorization method.
|
||||||
|
|
||||||
|
Use the TRANSACTIONID from DoExpressCheckoutPayment for the
|
||||||
|
``transactionid``. The latest version of the API does not support the
|
||||||
|
creation of an Order from `DoDirectPayment`.
|
||||||
|
|
||||||
|
The `amt` should be the same as passed to `DoExpressCheckoutPayment`.
|
||||||
|
|
||||||
|
Flow for a payment involving a `DoAuthorization` call::
|
||||||
|
|
||||||
|
1. One or many calls to `SetExpressCheckout` with pertinent order
|
||||||
|
details, returns `TOKEN`
|
||||||
|
1. `DoExpressCheckoutPayment` with `TOKEN`, `PAYMENTACTION` set to
|
||||||
|
Order, `AMT` set to the amount of the transaction, returns
|
||||||
|
`TRANSACTIONID`
|
||||||
|
1. `DoAuthorization` with `TRANSACTIONID` and `AMT` set to the
|
||||||
|
amount of the transaction.
|
||||||
|
1. `DoCapture` with the `AUTHORIZATIONID` (the `TRANSACTIONID`
|
||||||
|
returned by `DoAuthorization`)
|
||||||
|
|
||||||
|
"""
|
||||||
|
args = self._sanitize_locals(locals())
|
||||||
|
return self._call('DoAuthorization', **args)
|
||||||
|
|
||||||
|
def do_capture(self, authorizationid, amt, completetype='Complete', **kwargs):
|
||||||
|
"""Shortcut for the DoCapture method.
|
||||||
|
|
||||||
|
Use the TRANSACTIONID from DoAuthorization, DoDirectPayment or
|
||||||
|
DoExpressCheckoutPayment for the ``authorizationid``.
|
||||||
|
|
||||||
|
The `amt` should be the same as the authorized transaction.
|
||||||
|
"""
|
||||||
|
kwargs.update(self._sanitize_locals(locals()))
|
||||||
|
return self._call('DoCapture', **kwargs)
|
||||||
|
|
||||||
|
def do_direct_payment(self, paymentaction="Sale", **kwargs):
|
||||||
|
"""Shortcut for the DoDirectPayment method.
|
||||||
|
|
||||||
|
``paymentaction`` could be 'Authorization' or 'Sale'
|
||||||
|
|
||||||
|
To issue a Sale immediately::
|
||||||
|
|
||||||
|
charge = {
|
||||||
|
'amt': '10.00',
|
||||||
|
'creditcardtype': 'Visa',
|
||||||
|
'acct': '4812177017895760',
|
||||||
|
'expdate': '012010',
|
||||||
|
'cvv2': '962',
|
||||||
|
'firstname': 'John',
|
||||||
|
'lastname': 'Doe',
|
||||||
|
'street': '1 Main St',
|
||||||
|
'city': 'San Jose',
|
||||||
|
'state': 'CA',
|
||||||
|
'zip': '95131',
|
||||||
|
'countrycode': 'US',
|
||||||
|
'currencycode': 'USD',
|
||||||
|
}
|
||||||
|
direct_payment("Sale", **charge)
|
||||||
|
|
||||||
|
Or, since "Sale" is the default:
|
||||||
|
|
||||||
|
direct_payment(**charge)
|
||||||
|
|
||||||
|
To issue an Authorization, simply pass "Authorization" instead of "Sale".
|
||||||
|
|
||||||
|
You may also explicitly set ``paymentaction`` as a keyword argument:
|
||||||
|
|
||||||
|
...
|
||||||
|
direct_payment(paymentaction="Sale", **charge)
|
||||||
|
"""
|
||||||
|
kwargs.update(self._sanitize_locals(locals()))
|
||||||
|
return self._call('DoDirectPayment', **kwargs)
|
||||||
|
|
||||||
|
def do_void(self, **kwargs):
|
||||||
|
"""Shortcut for the DoVoid method.
|
||||||
|
|
||||||
|
Use the TRANSACTIONID from DoAuthorization, DoDirectPayment or
|
||||||
|
DoExpressCheckoutPayment for the ``AUTHORIZATIONID``.
|
||||||
|
|
||||||
|
Required Kwargs
|
||||||
|
---------------
|
||||||
|
* AUTHORIZATIONID
|
||||||
|
"""
|
||||||
|
return self._call('DoVoid', **kwargs)
|
||||||
|
|
||||||
|
def get_express_checkout_details(self, **kwargs):
|
||||||
|
"""Shortcut for the GetExpressCheckoutDetails method.
|
||||||
|
|
||||||
|
Required Kwargs
|
||||||
|
---------------
|
||||||
|
* TOKEN
|
||||||
|
"""
|
||||||
|
return self._call('GetExpressCheckoutDetails', **kwargs)
|
||||||
|
|
||||||
|
def get_transaction_details(self, **kwargs):
|
||||||
|
"""Shortcut for the GetTransactionDetails method.
|
||||||
|
|
||||||
|
Use the TRANSACTIONID from DoAuthorization, DoDirectPayment or
|
||||||
|
DoExpressCheckoutPayment for the ``transactionid``.
|
||||||
|
|
||||||
|
Required Kwargs
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* TRANSACTIONID
|
||||||
|
"""
|
||||||
|
return self._call('GetTransactionDetails', **kwargs)
|
||||||
|
|
||||||
|
def set_express_checkout(self, **kwargs):
|
||||||
|
"""Start an Express checkout.
|
||||||
|
|
||||||
|
You'll want to use this in conjunction with
|
||||||
|
:meth:`generate_express_checkout_redirect_url` to create a payment,
|
||||||
|
then figure out where to redirect the user to for them to
|
||||||
|
authorize the payment on PayPal's website.
|
||||||
|
|
||||||
|
Required Kwargs
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* PAYMENTREQUEST_0_AMT
|
||||||
|
* PAYMENTREQUEST_0_PAYMENTACTION
|
||||||
|
* RETURNURL
|
||||||
|
* CANCELURL
|
||||||
|
"""
|
||||||
|
return self._call('SetExpressCheckout', **kwargs)
|
||||||
|
|
||||||
|
def refund_transaction(self, transactionid=None, payerid=None, **kwargs):
|
||||||
|
"""Shortcut for RefundTransaction method.
|
||||||
|
Note new API supports passing a PayerID instead of a transaction id, exactly one must be provided.
|
||||||
|
Optional:
|
||||||
|
INVOICEID
|
||||||
|
REFUNDTYPE
|
||||||
|
AMT
|
||||||
|
CURRENCYCODE
|
||||||
|
NOTE
|
||||||
|
RETRYUNTIL
|
||||||
|
REFUNDSOURCE
|
||||||
|
MERCHANTSTOREDETAILS
|
||||||
|
REFUNDADVICE
|
||||||
|
REFUNDITEMDETAILS
|
||||||
|
MSGSUBID
|
||||||
|
|
||||||
|
MERCHANSTOREDETAILS has two fields:
|
||||||
|
STOREID
|
||||||
|
TERMINALID
|
||||||
|
"""
|
||||||
|
#this line seems like a complete waste of time... kwargs should not be populated
|
||||||
|
if (transactionid is None) and (payerid is None):
|
||||||
|
raise PayPalError('RefundTransaction requires either a transactionid or a payerid')
|
||||||
|
if (transactionid is not None) and (payerid is not None):
|
||||||
|
raise PayPalError('RefundTransaction requires only one of transactionid %s and payerid %s' % (transactionid, payerid))
|
||||||
|
if transactionid is not None:
|
||||||
|
kwargs['TRANSACTIONID'] = transactionid
|
||||||
|
else:
|
||||||
|
kwargs['PAYERID'] = payerid
|
||||||
|
|
||||||
|
return self._call('RefundTransaction', **kwargs)
|
||||||
|
|
||||||
|
def do_express_checkout_payment(self, **kwargs):
|
||||||
|
"""Finishes an Express checkout.
|
||||||
|
|
||||||
|
TOKEN is the token that was returned earlier by
|
||||||
|
:meth:`set_express_checkout`. This identifies the transaction.
|
||||||
|
|
||||||
|
Required
|
||||||
|
--------
|
||||||
|
* TOKEN
|
||||||
|
* PAYMENTACTION
|
||||||
|
* PAYERID
|
||||||
|
* AMT
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._call('DoExpressCheckoutPayment', **kwargs)
|
||||||
|
|
||||||
|
def generate_express_checkout_redirect_url(self, token):
|
||||||
|
"""Returns the URL to redirect the user to for the Express checkout.
|
||||||
|
|
||||||
|
Express Checkouts must be verified by the customer by redirecting them
|
||||||
|
to the PayPal website. Use the token returned in the response from
|
||||||
|
:meth:`set_express_checkout` with this function to figure out where
|
||||||
|
to redirect the user to.
|
||||||
|
|
||||||
|
:param str token: The unique token identifying this transaction.
|
||||||
|
:rtype: str
|
||||||
|
:returns: The URL to redirect the user to for approval.
|
||||||
|
"""
|
||||||
|
url_vars = (self.config.PAYPAL_URL_BASE, token)
|
||||||
|
return "%s?cmd=_express-checkout&token=%s" % url_vars
|
||||||
|
|
||||||
|
def generate_cart_upload_redirect_url(self, **kwargs):
|
||||||
|
"""https://www.sandbox.paypal.com/webscr
|
||||||
|
?cmd=_cart
|
||||||
|
&upload=1
|
||||||
|
"""
|
||||||
|
required_vals = ('business', 'item_name_1', 'amount_1', 'quantity_1')
|
||||||
|
self._check_required(required_vals, **kwargs)
|
||||||
|
url = "%s?cmd=_cart&upload=1" % self.config.PAYPAL_URL_BASE
|
||||||
|
additional = self._encode_utf8(**kwargs)
|
||||||
|
if is_py3:
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
additional = urllib.parse.urlencode(additional)
|
||||||
|
else:
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
additional = urllib.urlencode(additional)
|
||||||
|
return url + "&" + additional
|
||||||
|
|
||||||
|
def get_recurring_payments_profile_details(self, profileid):
|
||||||
|
"""Shortcut for the GetRecurringPaymentsProfile method.
|
||||||
|
|
||||||
|
This returns details for a recurring payment plan. The ``profileid`` is
|
||||||
|
a value included in the response retrieved by the function
|
||||||
|
``create_recurring_payments_profile``. The profile details include the
|
||||||
|
data provided when the profile was created as well as default values
|
||||||
|
for ignored fields and some pertinent stastics.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
response = create_recurring_payments_profile(**profile_info)
|
||||||
|
profileid = response.PROFILEID
|
||||||
|
details = get_recurring_payments_profile(profileid)
|
||||||
|
|
||||||
|
The response from PayPal is somewhat self-explanatory, but for a
|
||||||
|
description of each field, visit the following URI:
|
||||||
|
https://www.x.com/docs/DOC-1194
|
||||||
|
"""
|
||||||
|
args = self._sanitize_locals(locals())
|
||||||
|
return self._call('GetRecurringPaymentsProfileDetails', **args)
|
||||||
|
|
||||||
|
def manage_recurring_payments_profile_status(self, profileid, action, note = None):
|
||||||
|
"""Shortcut to the ManageRecurringPaymentsProfileStatus method.
|
||||||
|
|
||||||
|
``profileid`` is the same profile id used for getting profile details.
|
||||||
|
``action`` should be either 'Cancel', 'Suspend', or 'Reactivate'.
|
||||||
|
``note`` is optional and is visible to the user. It contains the reason for the change in status.
|
||||||
|
"""
|
||||||
|
args = self._sanitize_locals(locals())
|
||||||
|
if not note:
|
||||||
|
del args['note']
|
||||||
|
return self._call('ManageRecurringPaymentsProfileStatus', **args)
|
||||||
|
|
||||||
|
def update_recurring_payments_profile(self, profileid, **kwargs):
|
||||||
|
"""Shortcut to the UpdateRecurringPaymentsProfile method.
|
||||||
|
|
||||||
|
``profileid`` is the same profile id used for getting profile details.
|
||||||
|
|
||||||
|
The keyed arguments are data in the payment profile which you wish to
|
||||||
|
change. The profileid does not change. Anything else will take the new
|
||||||
|
value. Most of, though not all of, the fields available are shared
|
||||||
|
with creating a profile, but for the complete list of parameters, you
|
||||||
|
can visit the following URI:
|
||||||
|
https://www.x.com/docs/DOC-1212
|
||||||
|
"""
|
||||||
|
kwargs.update(self._sanitize_locals(locals()))
|
||||||
|
return self._call('UpdateRecurringPaymentsProfile', **kwargs)
|
||||||
|
|
||||||
|
def bm_create_button(self, **kwargs):
|
||||||
|
"""Shortcut to the BMButtonSearch method.
|
||||||
|
|
||||||
|
See the docs for details on arguments:
|
||||||
|
https://cms.paypal.com/mx/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_BMCreateButton
|
||||||
|
|
||||||
|
The L_BUTTONVARn fields are especially important, so make sure to
|
||||||
|
read those and act accordingly. See unit tests for some examples.
|
||||||
|
"""
|
||||||
|
kwargs.update(self._sanitize_locals(locals()))
|
||||||
|
return self._call('BMCreateButton', **kwargs)
|
||||||
115
vendor/paypalapi/response.py
vendored
Normal file
115
vendor/paypalapi/response.py
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
PayPalResponse parsing and processing.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from paypalapi.compat import is_py3, is_py25
|
||||||
|
|
||||||
|
if is_py3:
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
import urllib.parse
|
||||||
|
#noinspection PyUnresolvedReferences
|
||||||
|
parse_qs = urllib.parse.parse_qs
|
||||||
|
elif is_py25:
|
||||||
|
import cgi
|
||||||
|
#noinspection PyUnresolvedReferences, PyDeprecation
|
||||||
|
parse_qs = cgi.parse_qs
|
||||||
|
else:
|
||||||
|
# Python 2.6 and up (but not 3.0) have urlparse.parse_qs, which is copied
|
||||||
|
# from Python 2.5's cgi.parse_qs.
|
||||||
|
import urlparse
|
||||||
|
#noinspection PyUnresolvedReferences, PyDeprecation
|
||||||
|
parse_qs = urlparse.parse_qs
|
||||||
|
|
||||||
|
logger = logging.getLogger('paypal.response')
|
||||||
|
|
||||||
|
class PayPalResponse(object):
|
||||||
|
"""
|
||||||
|
Parse and prepare the reponse from PayPal's API. Acts as somewhat of a
|
||||||
|
glorified dictionary for API responses.
|
||||||
|
|
||||||
|
NOTE: Don't access self.raw directly. Just do something like
|
||||||
|
PayPalResponse.someattr, going through PayPalResponse.__getattr__().
|
||||||
|
"""
|
||||||
|
def __init__(self, query_string, config):
|
||||||
|
"""
|
||||||
|
query_string is the response from the API, in NVP format. This is
|
||||||
|
parseable by urlparse.parse_qs(), which sticks it into the
|
||||||
|
:attr:`raw` dict for retrieval by the user.
|
||||||
|
|
||||||
|
:param str query_string: The raw response from the API server.
|
||||||
|
:param PayPalConfig config: The config object that was used to send
|
||||||
|
the query that caused this response.
|
||||||
|
"""
|
||||||
|
# A dict of NVP values. Don't access this directly, use
|
||||||
|
# PayPalResponse.attribname instead. See self.__getattr__().
|
||||||
|
self.raw = parse_qs(query_string)
|
||||||
|
self.config = config
|
||||||
|
logger.debug("PayPal NVP API Response:\n%s" % self.__str__())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Returns a string representation of the PayPalResponse object, in
|
||||||
|
'pretty-print' format.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:returns: A 'pretty' string representation of the response dict.
|
||||||
|
"""
|
||||||
|
return pformat(self.raw)
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
"""
|
||||||
|
Handles the retrieval of attributes that don't exist on the object
|
||||||
|
already. This is used to get API response values. Handles some
|
||||||
|
convenience stuff like discarding case and checking the cgi/urlparsed
|
||||||
|
response value dict (self.raw).
|
||||||
|
|
||||||
|
:param str key: The response attribute to get a value for.
|
||||||
|
:rtype: str
|
||||||
|
:returns: The requested value from the API server's response.
|
||||||
|
"""
|
||||||
|
# PayPal response names are always uppercase.
|
||||||
|
key = key.upper()
|
||||||
|
try:
|
||||||
|
value = self.raw[key]
|
||||||
|
if len(value) == 1:
|
||||||
|
# For some reason, PayPal returns lists for all of the values.
|
||||||
|
# I'm not positive as to why, so we'll just take the first
|
||||||
|
# of each one. Hasn't failed us so far.
|
||||||
|
return value[0]
|
||||||
|
return value
|
||||||
|
except KeyError:
|
||||||
|
# The requested value wasn't returned in the response.
|
||||||
|
raise AttributeError(self)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
"""
|
||||||
|
Another (dict-style) means of accessing response data.
|
||||||
|
|
||||||
|
:param str key: The response key to get a value for.
|
||||||
|
:rtype: str
|
||||||
|
:returns: The requested value from the API server's response.
|
||||||
|
"""
|
||||||
|
# PayPal response names are always uppercase.
|
||||||
|
key = key.upper()
|
||||||
|
value = self.raw[key]
|
||||||
|
if len(value) == 1:
|
||||||
|
# For some reason, PayPal returns lists for all of the values.
|
||||||
|
# I'm not positive as to why, so we'll just take the first
|
||||||
|
# of each one. Hasn't failed us so far.
|
||||||
|
return value[0]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def success(self):
|
||||||
|
"""
|
||||||
|
Checks for the presence of errors in the response. Returns ``True`` if
|
||||||
|
all is well, ``False`` otherwise.
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
|
:returns ``True`` if PayPal says our query was successful.
|
||||||
|
"""
|
||||||
|
return self.ack.upper() in (self.config.ACK_SUCCESS,
|
||||||
|
self.config.ACK_SUCCESS_WITH_WARNING)
|
||||||
|
success = property(success)
|
||||||
130
vendor/paypalapi/settings.py
vendored
Normal file
130
vendor/paypalapi/settings.py
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
This module contains config objects needed by paypal.interface.PayPalInterface.
|
||||||
|
Most of this is transparent to the end developer, as the PayPalConfig object
|
||||||
|
is instantiated by the PayPalInterface object.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from paypalapi.compat import basestring
|
||||||
|
from paypalapi.exceptions import PayPalConfigError
|
||||||
|
|
||||||
|
logger = logging.getLogger('paypal.settings')
|
||||||
|
|
||||||
|
class PayPalConfig(object):
|
||||||
|
"""
|
||||||
|
The PayPalConfig object is used to allow the developer to perform API
|
||||||
|
queries with any number of different accounts or configurations. This
|
||||||
|
is done by instantiating paypal.interface.PayPalInterface, passing config
|
||||||
|
directives as keyword args.
|
||||||
|
"""
|
||||||
|
# Used to validate correct values for certain config directives.
|
||||||
|
_valid_ = {
|
||||||
|
'API_ENVIRONMENT': ['SANDBOX', 'PRODUCTION'],
|
||||||
|
'API_AUTHENTICATION_MODE': ['3TOKEN', 'CERTIFICATE'],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Various API servers.
|
||||||
|
_API_ENDPOINTS = {
|
||||||
|
# In most cases, you want 3-Token. There's also Certificate-based
|
||||||
|
# authentication, which uses different servers, but that's not
|
||||||
|
# implemented.
|
||||||
|
'3TOKEN': {
|
||||||
|
'SANDBOX': 'https://api-3t.sandbox.paypal.com/nvp',
|
||||||
|
'PRODUCTION': 'https://api-3t.paypal.com/nvp',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_PAYPAL_URL_BASE = {
|
||||||
|
'SANDBOX': 'https://www.sandbox.paypal.com/webscr',
|
||||||
|
'PRODUCTION': 'https://www.paypal.com/webscr',
|
||||||
|
}
|
||||||
|
|
||||||
|
API_VERSION = '98.0'
|
||||||
|
|
||||||
|
# Defaults. Used in the absence of user-specified values.
|
||||||
|
API_ENVIRONMENT = 'SANDBOX'
|
||||||
|
API_AUTHENTICATION_MODE = '3TOKEN'
|
||||||
|
|
||||||
|
# 3TOKEN credentials
|
||||||
|
API_USERNAME = None
|
||||||
|
API_PASSWORD = None
|
||||||
|
API_SIGNATURE = None
|
||||||
|
|
||||||
|
# API Endpoints are just API server addresses.
|
||||||
|
API_ENDPOINT = None
|
||||||
|
PAYPAL_URL_BASE = None
|
||||||
|
|
||||||
|
# API Endpoint CA certificate chain. If this is True, do a simple SSL
|
||||||
|
# certificate check on the endpoint. If it's a full path, verify against
|
||||||
|
# a private cert.
|
||||||
|
# e.g. '/etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem'
|
||||||
|
API_CA_CERTS = True
|
||||||
|
|
||||||
|
# UNIPAY credentials
|
||||||
|
UNIPAY_SUBJECT = None
|
||||||
|
|
||||||
|
ACK_SUCCESS = "SUCCESS"
|
||||||
|
ACK_SUCCESS_WITH_WARNING = "SUCCESSWITHWARNING"
|
||||||
|
|
||||||
|
# In seconds. Depending on your setup, this may need to be higher.
|
||||||
|
HTTP_TIMEOUT = 15.0
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""
|
||||||
|
PayPalConfig constructor. **kwargs catches all of the user-specified
|
||||||
|
config directives at time of instantiation. It is fine to set these
|
||||||
|
values post-instantiation, too.
|
||||||
|
|
||||||
|
Some basic validation for a few values is performed below, and defaults
|
||||||
|
are applied for certain directives in the absence of
|
||||||
|
user-provided values.
|
||||||
|
"""
|
||||||
|
if kwargs.get('API_ENVIRONMENT'):
|
||||||
|
api_environment = kwargs['API_ENVIRONMENT'].upper()
|
||||||
|
# Make sure the environment is one of the acceptable values.
|
||||||
|
if api_environment not in self._valid_['API_ENVIRONMENT']:
|
||||||
|
raise PayPalConfigError('Invalid API_ENVIRONMENT')
|
||||||
|
else:
|
||||||
|
self.API_ENVIRONMENT = api_environment
|
||||||
|
|
||||||
|
if kwargs.get('API_AUTHENTICATION_MODE'):
|
||||||
|
auth_mode = kwargs['API_AUTHENTICATION_MODE'].upper()
|
||||||
|
# Make sure the auth mode is one of the known/implemented methods.
|
||||||
|
if auth_mode not in self._valid_['API_AUTHENTICATION_MODE']:
|
||||||
|
choices = ", ".join(self._valid_['API_AUTHENTICATION_MODE'])
|
||||||
|
raise PayPalConfigError(
|
||||||
|
"Not a supported auth mode. Use one of: %s" % choices
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.API_AUTHENTICATION_MODE = auth_mode
|
||||||
|
|
||||||
|
# Set the API endpoints, which is a cheesy way of saying API servers.
|
||||||
|
self.API_ENDPOINT = self._API_ENDPOINTS[self.API_AUTHENTICATION_MODE][self.API_ENVIRONMENT]
|
||||||
|
self.PAYPAL_URL_BASE = self._PAYPAL_URL_BASE[self.API_ENVIRONMENT]
|
||||||
|
|
||||||
|
# Set the CA_CERTS location. This can either be a None, a bool, or a
|
||||||
|
# string path.
|
||||||
|
if kwargs.get('API_CA_CERTS'):
|
||||||
|
self.API_CA_CERTS = kwargs['API_CA_CERTS']
|
||||||
|
|
||||||
|
if isinstance(self.API_CA_CERTS, basestring) and not os.path.exists(self.API_CA_CERTS):
|
||||||
|
# A CA Cert path was specified, but it's invalid.
|
||||||
|
raise PayPalConfigError('Invalid API_CA_CERTS')
|
||||||
|
|
||||||
|
# set the 3TOKEN required fields
|
||||||
|
if self.API_AUTHENTICATION_MODE == '3TOKEN':
|
||||||
|
for arg in ('API_USERNAME', 'API_PASSWORD', 'API_SIGNATURE'):
|
||||||
|
if arg not in kwargs:
|
||||||
|
raise PayPalConfigError('Missing in PayPalConfig: %s ' % arg)
|
||||||
|
setattr(self, arg, kwargs[arg])
|
||||||
|
|
||||||
|
for arg in ['HTTP_TIMEOUT']:
|
||||||
|
if arg in kwargs:
|
||||||
|
setattr(self, arg, kwargs[arg])
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
'PayPalConfig object instantiated with kwargs: %s' % pformat(kwargs)
|
||||||
|
)
|
||||||
Loading…
Add table
Reference in a new issue