├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── __init__.py ├── paymentwall ├── __init__.py ├── base.py ├── pingback.py ├── product.py └── widget.py ├── pypi_description.rst └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .pypirc 2 | *.pyc 3 | test.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010-2014 Paymentwall, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst LICENSE *.md 2 | recursive-include *.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About Paymentwall 2 | [Paymentwall](http://paymentwall.com/?source=gh-py) is the leading digital payments platform for globally monetizing digital goods and services. Paymentwall assists game publishers, dating sites, rewards sites, SaaS companies and many other verticals to monetize their digital content and services. 3 | Merchants can plugin Paymentwall's API to accept payments from over 100 different methods including credit cards, debit cards, bank transfers, SMS/Mobile payments, prepaid cards, eWallets, landline payments and others. 4 | 5 | To sign up for a Paymentwall Merchant Account, [click here](http://paymentwall.com/signup/merchant?source=gh-py). 6 | 7 | # Paymentwall Python Library 8 | This library allows developers to use [Paymentwall APIs](http://paymentwall.com/en/documentation/API-Documentation/722?source=gh-py) (Virtual Currency, Digital Goods featuring recurring billing, and Virtual Cart). 9 | 10 | To use Paymentwall, all you need to do is to sign up for a Paymentwall Merchant Account so you can setup an Application designed for your site. 11 | To open your merchant account and set up an application, you can [sign up here](http://paymentwall.com/signup/merchant?source=gh-py). 12 | 13 | # Installation 14 | To install using pip run: 15 | 16 | pip install paymentwall-python 17 | 18 | Notice: If you are using Python 2.6 please run the following command, too: 19 | 20 | pip install ordereddict 21 | 22 | To install from source run: 23 | 24 | python setup.py install 25 | 26 | Then use a code sample below. 27 | 28 | # Code Samples 29 | 30 | ## Digital Goods API 31 | 32 | #### Initializing Paymentwall 33 |
from paymentwall import *
 34 | Paymentwall.set_api_type(Paymentwall.API_GOODS)
 35 | Paymentwall.set_app_key('APPLICATION_KEY') # available in your merchant area
 36 | Paymentwall.set_secret_key('SECRET_KEY') # available in your merchant area
 37 | 
38 | 39 | #### Widget Call 40 | [Web API details](http://www.paymentwall.com/en/documentation/Digital-Goods-API/710#paymentwall_widget_call_flexible_widget_call) 41 | 42 | The widget is a payment page hosted by Paymentwall that embeds the entire payment flow: selecting the payment method, completing the billing details, and providing customer support via the Help section. You can redirect the users to this page or embed it via iframe. Below is an example that renders an iframe with Paymentwall Widget. 43 | 44 |
product = Product(
 45 |     'product301',              # id of the product in your system 
 46 |     12.12,                     # price
 47 |     'USD',                     # currency code
 48 |     'test',                    # product name
 49 |     Product.TYPE_SUBSCRIPTION, # this is a time-based product
 50 |     1,                         # duration is 1
 51 |     Product.PERIOD_TYPE_WEEK,  #               week
 52 |     True                       # recurring
 53 | )
 54 | 
 55 | widget = Widget(
 56 |     'user4522',   # id of the end-user who's making the payment
 57 |     'pw',       # widget code, e.g. pw; can be picked inside of your merchant account
 58 |     [product],    # product details for Flexible Widget Call. To let users select the product on Paymentwall's end, leave this array empty
 59 |     {'email': 'user@hostname.com'}    # additional parameters
 60 | )
 61 | print(widget.get_html_code())
 62 | 
63 | 64 | #### Pingback Processing 65 | 66 | The Pingback is a webhook notifying about a payment being made. Pingbacks are sent via HTTP/HTTPS to your servers. To process pingbacks use the following code: 67 |
pingback = Pingback({x:y for x, y in request.args.iteritems()}, request.remote_addr)
 68 | 
 69 | if pingback.validate():
 70 |     product_id = pingback.get_product().get_id()
 71 |     if pingback.is_deliverable():
 72 |         # deliver the product
 73 |         pass
 74 |     elif pingback.is_cancelable():
 75 |         # withdraw the product
 76 |         pass
 77 | 
 78 |     print('OK') # Paymentwall expects response to be OK, otherwise the pingback will be resent
 79 | 
 80 | else:
 81 |     print(pingback.get_error_summary())
82 | 83 | ## Virtual Currency API 84 | 85 | #### Initializing Paymentwall 86 |
from paymentwall import *
 87 | Paymentwall.set_api_type(Paymentwall.API_VC)
 88 | Paymentwall.set_app_key('APPLICATION_KEY')
 89 | Paymentwall.set_secret_key('SECRET_KEY')
 90 | 
91 | 92 | #### Widget Call 93 |
widget = Widget(
 94 | 	'user40012', # id of the end-user who's making the payment
 95 | 	'p1_1',      # widget code, e.g. p1; can be picked inside of your merchant account
 96 | 	[],          # array of products - leave blank for Virtual Currency API
 97 | 	{'email': 'user@hostname.com'} # additional parameters
 98 | )
 99 | print(widget.get_html_code())
100 | 
101 | 102 | #### Pingback Processing 103 |
pingback = Pingback({x:y for x, y in request.args.iteritems()}, request.remote_addr)
104 | if pingback.validate():
105 |     virtual_currency = pingback.get_vc_amount()
106 |     if pingback.is_deliverable():
107 |         # deliver the virtual currency
108 |         pass
109 |     elif pingback.is_cancelable():
110 |         # withdraw the virtual currency
111 |         pass 
112 |   print('OK') # Paymentwall expects response to be OK, otherwise the pingback will be resent
113 | else:
114 |   print(pingback.get_error_summary())
115 | end
116 | 117 | ## Cart API 118 | 119 | #### Initializing Paymentwall 120 |
from paymentwall import *
121 | Paymentwall.set_api_type(Paymentwall.API_CART)
122 | Paymentwall.set_app_key('APPLICATION_KEY')
123 | Paymentwall.set_secret_key('SECRET_KEY')
124 | 
125 | 126 | #### Widget Call 127 |
widget = Widget(
128 | 	'user40012', # id of the end-user who's making the payment
129 | 	'p1_1',      # widget code, e.g. p1; can be picked inside of your merchant account
130 | 	[
131 | 		Product('product301', 3.33, 'EUR'), # first product in cart
132 | 		Product('product607', 7.77, 'EUR')  # second product in cart
133 | 	],
134 | 	{'email': 'user@hostname.com'} # additional params
135 | )
136 | print(widget.get_html_code())
137 | 138 | #### Pingback Processing 139 |
pingback = Pingback({x:y for x, y in request.args.iteritems()}, request.remote_addr)
140 | if pingback.validate():
141 |     products = pingback.get_products()
142 |     if pingback.is_deliverable():
143 |         # deliver the virtual currency
144 |         pass
145 |     elif pingback.is_cancelable():
146 |         # withdraw the virtual currency
147 |         pass 
148 |   print('OK') # Paymentwall expects response to be OK, otherwise the pingback will be resent
149 | else:
150 |   print(pingback.get_error_summary())
151 | end
152 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from paymentwall.base import Paymentwall 2 | from paymentwall.product import Product 3 | from paymentwall.widget import Widget 4 | from paymentwall.pingback import Pingback 5 | -------------------------------------------------------------------------------- /paymentwall/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from .base import Paymentwall 4 | from .pingback import Pingback 5 | from .product import Product 6 | from .widget import Widget 7 | 8 | __all__ = ['Paymentwall', 'Pingback', 'Product', 'Widget'] 9 | -------------------------------------------------------------------------------- /paymentwall/base.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | class Paymentwall: 5 | 6 | VERSION = '1.0.0' 7 | 8 | API_VC = 1 9 | API_GOODS = 2 10 | API_CART = 3 11 | 12 | VC_CONTROLLER = 'ps' 13 | GOODS_CONTROLLER = 'subscription' 14 | CART_CONTROLLER = 'cart' 15 | 16 | DEFAULT_SIGNATURE_VERSION = 3 17 | SIGNATURE_VERSION_1 = 1 18 | SIGNATURE_VERSION_2 = 2 19 | SIGNATURE_VERSION_3 = 3 20 | 21 | errors = [] 22 | 23 | api_type = None 24 | app_key = None 25 | secret_key = None 26 | 27 | @classmethod 28 | def set_api_type(cls, api_type): 29 | cls.api_type = api_type 30 | 31 | @classmethod 32 | def get_api_type(cls): 33 | return cls.api_type 34 | 35 | @classmethod 36 | def set_app_key(cls, app_key): 37 | cls.app_key = app_key 38 | 39 | @classmethod 40 | def get_app_key(cls): 41 | return cls.app_key 42 | 43 | @classmethod 44 | def set_secret_key(cls, secret_key): 45 | cls.secret_key = secret_key 46 | 47 | @classmethod 48 | def get_secret_key(cls): 49 | return cls.secret_key 50 | 51 | @classmethod 52 | def append_to_errors(cls, err): 53 | cls.errors.append(err) 54 | 55 | @classmethod 56 | def get_errors(cls): 57 | return cls.errors 58 | 59 | @classmethod 60 | def get_error_summary(cls): 61 | return '\n'.join(cls.get_errors()) 62 | 63 | # 64 | # Helper functions 65 | # 66 | @classmethod 67 | def is_empty(cls, dictionary, key): 68 | if isinstance(dictionary, dict): 69 | if key in dictionary and dictionary[key]: 70 | return False 71 | return True 72 | 73 | @classmethod 74 | def array_merge(cls, first_array, second_array): 75 | if isinstance(first_array, list) and isinstance(second_array, list): 76 | return first_array + second_array 77 | elif isinstance(first_array, dict) and isinstance(second_array, dict): 78 | return dict(list(first_array.items()) + list(second_array.items())) 79 | elif isinstance(first_array, set) and isinstance(second_array, set): 80 | return first_array.union(second_array) 81 | return False 82 | 83 | @classmethod 84 | def hash(cls, string, library_type): 85 | hashed_string = hashlib.md5() if library_type == 'md5' else hashlib.sha256() 86 | hashed_string.update(string.encode('utf-8')) 87 | return hashed_string.hexdigest() 88 | -------------------------------------------------------------------------------- /paymentwall/pingback.py: -------------------------------------------------------------------------------- 1 | from paymentwall.base import Paymentwall 2 | from paymentwall.product import Product 3 | 4 | try: 5 | from collections import OrderedDict 6 | except ImportError: 7 | from ordereddict import OrderedDict 8 | 9 | 10 | class Pingback(Paymentwall): 11 | 12 | PINGBACK_TYPE_REGULAR = 0 13 | PINGBACK_TYPE_GOODWILL = 1 14 | PINGBACK_TYPE_NEGATIVE = 2 15 | 16 | PINGBACK_TYPE_RISK_UNDER_REVIEW = 200 17 | PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED = 201 18 | PINGBACK_TYPE_RISK_REVIEWED_DECLINED = 202 19 | 20 | PINGBACK_TYPE_RISK_AUTHORIZATION_VOIDED = 203 21 | 22 | PINGBACK_TYPE_SUBSCRIPTION_CANCELLATION = 12 23 | PINGBACK_TYPE_SUBSCRIPTION_EXPIRED = 13 24 | PINGBACK_TYPE_SUBSCRIPTION_PAYMENT_FAILED = 14 25 | 26 | def __init__(self, parameters={}, ip_address=''): 27 | self.parameters = parameters 28 | self.ip_address = ip_address 29 | 30 | def validate(self, skip_ip_whitelist_check=False): 31 | validated = False 32 | 33 | if self.is_parameters_valid(): 34 | if self.is_ip_address_valid() or skip_ip_whitelist_check: 35 | if self.is_signature_valid(): 36 | validated = True 37 | else: 38 | self.append_to_errors('Wrong signature') 39 | else: 40 | self.append_to_errors('IP address is not whitelisted') 41 | else: 42 | self.append_to_errors('Missing parameters') 43 | 44 | return validated 45 | 46 | def is_signature_valid(self): 47 | signature_params_to_sign = OrderedDict() 48 | 49 | if self.get_api_type() == self.API_VC: 50 | signature_params = ['uid', 'currency', 'type', 'ref'] 51 | elif self.get_api_type() == self.API_GOODS: 52 | signature_params = ['uid', 'goodsid', 'slength', 'speriod', 'type', 'ref'] 53 | else: 54 | signature_params = ['uid', 'goodsid', 'type', 'ref'] 55 | self.parameters['sign_version'] = self.SIGNATURE_VERSION_2 56 | 57 | if 'sign_version' not in self.parameters or int(self.parameters['sign_version']) == self.SIGNATURE_VERSION_1: 58 | for field in signature_params: 59 | signature_params_to_sign[field] = self.parameters[field] if field in self.parameters else None 60 | self.parameters['sign_version'] = self.SIGNATURE_VERSION_1 61 | else: 62 | signature_params_to_sign = self.parameters 63 | 64 | signature_calculated = self.calculate_signature( 65 | signature_params_to_sign, self.get_secret_key(), self.parameters['sign_version'] 66 | ) 67 | 68 | signature = self.parameters['sig'] if 'sig' in self.parameters else None 69 | 70 | return signature == signature_calculated 71 | 72 | def is_ip_address_valid(self): 73 | ips_whitelist = [ 74 | '174.36.92.186', 75 | '174.36.96.66', 76 | '174.36.92.187', 77 | '174.36.92.192', 78 | '174.37.14.28', 79 | '216.127.71.0', 80 | '216.127.71.1', 81 | '216.127.71.2', 82 | '216.127.71.3', 83 | '216.127.71.4', 84 | '216.127.71.5', 85 | '216.127.71.6', 86 | '216.127.71.7', 87 | '216.127.71.8', 88 | '216.127.71.9', 89 | '216.127.71.10', 90 | '216.127.71.11', 91 | '216.127.71.12', 92 | '216.127.71.13', 93 | '216.127.71.14', 94 | '216.127.71.15', 95 | '216.127.71.16', 96 | '216.127.71.17', 97 | '216.127.71.18', 98 | '216.127.71.19', 99 | '216.127.71.20', 100 | '216.127.71.21', 101 | '216.127.71.22', 102 | '216.127.71.23', 103 | '216.127.71.24', 104 | '216.127.71.25', 105 | '216.127.71.26', 106 | '216.127.71.27', 107 | '216.127.71.28', 108 | '216.127.71.29', 109 | '216.127.71.30', 110 | '216.127.71.31', 111 | '216.127.71.32', 112 | '216.127.71.33', 113 | '216.127.71.34', 114 | '216.127.71.35', 115 | '216.127.71.36', 116 | '216.127.71.37', 117 | '216.127.71.38', 118 | '216.127.71.39', 119 | '216.127.71.40', 120 | '216.127.71.41', 121 | '216.127.71.42', 122 | '216.127.71.43', 123 | '216.127.71.44', 124 | '216.127.71.45', 125 | '216.127.71.46', 126 | '216.127.71.47', 127 | '216.127.71.48', 128 | '216.127.71.49', 129 | '216.127.71.50', 130 | '216.127.71.51', 131 | '216.127.71.52', 132 | '216.127.71.53', 133 | '216.127.71.54', 134 | '216.127.71.55', 135 | '216.127.71.56', 136 | '216.127.71.57', 137 | '216.127.71.58', 138 | '216.127.71.59', 139 | '216.127.71.60', 140 | '216.127.71.61', 141 | '216.127.71.62', 142 | '216.127.71.63', 143 | '216.127.71.64', 144 | '216.127.71.65', 145 | '216.127.71.66', 146 | '216.127.71.67', 147 | '216.127.71.68', 148 | '216.127.71.69', 149 | '216.127.71.70', 150 | '216.127.71.71', 151 | '216.127.71.72', 152 | '216.127.71.73', 153 | '216.127.71.74', 154 | '216.127.71.75', 155 | '216.127.71.76', 156 | '216.127.71.77', 157 | '216.127.71.78', 158 | '216.127.71.79', 159 | '216.127.71.80', 160 | '216.127.71.81', 161 | '216.127.71.82', 162 | '216.127.71.83', 163 | '216.127.71.84', 164 | '216.127.71.85', 165 | '216.127.71.86', 166 | '216.127.71.87', 167 | '216.127.71.88', 168 | '216.127.71.89', 169 | '216.127.71.90', 170 | '216.127.71.91', 171 | '216.127.71.92', 172 | '216.127.71.93', 173 | '216.127.71.94', 174 | '216.127.71.95', 175 | '216.127.71.96', 176 | '216.127.71.97', 177 | '216.127.71.98', 178 | '216.127.71.99', 179 | '216.127.71.100', 180 | '216.127.71.101', 181 | '216.127.71.102', 182 | '216.127.71.103', 183 | '216.127.71.104', 184 | '216.127.71.105', 185 | '216.127.71.106', 186 | '216.127.71.107', 187 | '216.127.71.108', 188 | '216.127.71.109', 189 | '216.127.71.110', 190 | '216.127.71.111', 191 | '216.127.71.112', 192 | '216.127.71.113', 193 | '216.127.71.114', 194 | '216.127.71.115', 195 | '216.127.71.116', 196 | '216.127.71.117', 197 | '216.127.71.118', 198 | '216.127.71.119', 199 | '216.127.71.120', 200 | '216.127.71.121', 201 | '216.127.71.122', 202 | '216.127.71.123', 203 | '216.127.71.124', 204 | '216.127.71.125', 205 | '216.127.71.126', 206 | '216.127.71.127', 207 | '216.127.71.128', 208 | '216.127.71.129', 209 | '216.127.71.130', 210 | '216.127.71.131', 211 | '216.127.71.132', 212 | '216.127.71.133', 213 | '216.127.71.134', 214 | '216.127.71.135', 215 | '216.127.71.136', 216 | '216.127.71.137', 217 | '216.127.71.138', 218 | '216.127.71.139', 219 | '216.127.71.140', 220 | '216.127.71.141', 221 | '216.127.71.142', 222 | '216.127.71.143', 223 | '216.127.71.144', 224 | '216.127.71.145', 225 | '216.127.71.146', 226 | '216.127.71.147', 227 | '216.127.71.148', 228 | '216.127.71.149', 229 | '216.127.71.150', 230 | '216.127.71.151', 231 | '216.127.71.152', 232 | '216.127.71.153', 233 | '216.127.71.154', 234 | '216.127.71.155', 235 | '216.127.71.156', 236 | '216.127.71.157', 237 | '216.127.71.158', 238 | '216.127.71.159', 239 | '216.127.71.160', 240 | '216.127.71.161', 241 | '216.127.71.162', 242 | '216.127.71.163', 243 | '216.127.71.164', 244 | '216.127.71.165', 245 | '216.127.71.166', 246 | '216.127.71.167', 247 | '216.127.71.168', 248 | '216.127.71.169', 249 | '216.127.71.170', 250 | '216.127.71.171', 251 | '216.127.71.172', 252 | '216.127.71.173', 253 | '216.127.71.174', 254 | '216.127.71.175', 255 | '216.127.71.176', 256 | '216.127.71.177', 257 | '216.127.71.178', 258 | '216.127.71.179', 259 | '216.127.71.180', 260 | '216.127.71.181', 261 | '216.127.71.182', 262 | '216.127.71.183', 263 | '216.127.71.184', 264 | '216.127.71.185', 265 | '216.127.71.186', 266 | '216.127.71.187', 267 | '216.127.71.188', 268 | '216.127.71.189', 269 | '216.127.71.190', 270 | '216.127.71.191', 271 | '216.127.71.192', 272 | '216.127.71.193', 273 | '216.127.71.194', 274 | '216.127.71.195', 275 | '216.127.71.196', 276 | '216.127.71.197', 277 | '216.127.71.198', 278 | '216.127.71.199', 279 | '216.127.71.200', 280 | '216.127.71.201', 281 | '216.127.71.202', 282 | '216.127.71.203', 283 | '216.127.71.204', 284 | '216.127.71.205', 285 | '216.127.71.206', 286 | '216.127.71.207', 287 | '216.127.71.208', 288 | '216.127.71.209', 289 | '216.127.71.210', 290 | '216.127.71.211', 291 | '216.127.71.212', 292 | '216.127.71.213', 293 | '216.127.71.214', 294 | '216.127.71.215', 295 | '216.127.71.216', 296 | '216.127.71.217', 297 | '216.127.71.218', 298 | '216.127.71.219', 299 | '216.127.71.220', 300 | '216.127.71.221', 301 | '216.127.71.222', 302 | '216.127.71.223', 303 | '216.127.71.224', 304 | '216.127.71.225', 305 | '216.127.71.226', 306 | '216.127.71.227', 307 | '216.127.71.228', 308 | '216.127.71.229', 309 | '216.127.71.230', 310 | '216.127.71.231', 311 | '216.127.71.232', 312 | '216.127.71.233', 313 | '216.127.71.234', 314 | '216.127.71.235', 315 | '216.127.71.236', 316 | '216.127.71.237', 317 | '216.127.71.238', 318 | '216.127.71.239', 319 | '216.127.71.240', 320 | '216.127.71.241', 321 | '216.127.71.242', 322 | '216.127.71.243', 323 | '216.127.71.244', 324 | '216.127.71.245', 325 | '216.127.71.246', 326 | '216.127.71.247', 327 | '216.127.71.248', 328 | '216.127.71.249', 329 | '216.127.71.250', 330 | '216.127.71.251', 331 | '216.127.71.252', 332 | '216.127.71.253', 333 | '216.127.71.254', 334 | '216.127.71.255' 335 | ] 336 | 337 | return self.ip_address in ips_whitelist 338 | 339 | def is_parameters_valid(self): 340 | errors_number = 0 341 | 342 | if self.get_api_type() == self.API_VC: 343 | required_params = ['uid', 'currency', 'type', 'ref', 'sig'] 344 | else: 345 | required_params = ['uid', 'goodsid', 'type', 'ref', 'sig'] 346 | 347 | for field in required_params: 348 | if field not in self.parameters: 349 | self.append_to_errors('Parameter ' + field + ' is missing') 350 | errors_number += 1 351 | 352 | return errors_number == 0 353 | 354 | def get_parameter(self, param): 355 | return self.parameters.get(param, None) 356 | 357 | def get_type(self): 358 | if 'type' in self.parameters: 359 | try: 360 | type_parameter = int(self.parameters['type']) 361 | except ValueError: 362 | return None 363 | 364 | return type_parameter 365 | 366 | def get_user_id(self): 367 | return self.get_parameter('uid') 368 | 369 | def get_vc_amount(self): 370 | return self.get_parameter('currency') 371 | 372 | def get_product_id(self): 373 | return self.get_parameter('goodsid') 374 | 375 | def get_product_period_length(self): 376 | try: 377 | return int(self.parameters['slength']) 378 | except ValueError: 379 | return 0 380 | 381 | def get_product_period_type(self): 382 | try: 383 | return int(self.parameters['speriod']) 384 | except ValueError: 385 | return None 386 | 387 | def get_product(self): 388 | return Product( 389 | self.get_product_id(), 390 | 0, 391 | None, 392 | None, 393 | Product.TYPE_SUBSCRIPTION if self.get_product_period_length() > 0 else Product.TYPE_FIXED, 394 | self.get_product_period_length(), 395 | self.get_product_period_type() 396 | ) 397 | 398 | def get_products(self): 399 | product_ids = self.get_parameter('goodsid') 400 | 401 | if isinstance(product_ids, list) and product_ids: 402 | result = [Product(product_id) for product_id in product_ids] 403 | else: 404 | result = [] 405 | 406 | return result 407 | 408 | def get_reference_id(self): 409 | return self.get_parameter('ref') 410 | 411 | def get_pingback_unique_id(self): 412 | return self.get_reference_id() + '_' + str(self.get_type()) 413 | 414 | def is_deliverable(self): 415 | return ( 416 | self.get_type() == self.PINGBACK_TYPE_REGULAR or 417 | self.get_type() == self.PINGBACK_TYPE_GOODWILL or 418 | self.get_type() == self.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED 419 | ) 420 | 421 | def is_cancelable(self): 422 | return ( 423 | self.get_type() == self.PINGBACK_TYPE_NEGATIVE or 424 | self.get_type() == self.PINGBACK_TYPE_RISK_REVIEWED_DECLINED 425 | ) 426 | 427 | def is_under_review(self): 428 | return self.get_type() == self.PINGBACK_TYPE_RISK_UNDER_REVIEW 429 | 430 | def calculate_signature(self, params, secret, version): 431 | base_string = '' 432 | 433 | params = params.copy() 434 | 435 | if 'sig' in params: 436 | del params['sig'] 437 | 438 | sortable = int(version) in [self.SIGNATURE_VERSION_2, self.SIGNATURE_VERSION_3] 439 | keys = list(sorted(params.keys())) if sortable else list(params.keys()) 440 | 441 | for k in range(len(keys)): 442 | if isinstance(params[keys[k]], (list, tuple)): 443 | for i in range(len(params[keys[k]])): 444 | base_string += str(keys[k]) + '[' + str(i) + ']=' + str(params[keys[k]][i]) 445 | else: 446 | base_string += str(keys[k]) + '=' + str(params[keys[k]]) 447 | 448 | base_string += secret 449 | 450 | return ( 451 | self.hash(base_string, 'sha256') 452 | if int(version) == self.SIGNATURE_VERSION_3 453 | else self.hash(base_string, 'md5') 454 | ) 455 | -------------------------------------------------------------------------------- /paymentwall/product.py: -------------------------------------------------------------------------------- 1 | class Product: 2 | 3 | TYPE_SUBSCRIPTION = 'subscription' 4 | TYPE_FIXED = 'fixed' 5 | 6 | PERIOD_TYPE_DAY = 'day' 7 | PERIOD_TYPE_WEEK = 'week' 8 | PERIOD_TYPE_MONTH = 'month' 9 | PERIOD_TYPE_YEAR = 'year' 10 | 11 | def __init__(self, product_id=None, amount=0.0, currency_code=None, name=None, product_type=TYPE_FIXED, 12 | period_length=0, period_type=None, recurring=False, trial_product=object): 13 | self.product_id = product_id 14 | self.amount = round(amount, 2) 15 | self.currency_code = currency_code 16 | self.name = name 17 | self.product_type = product_type 18 | self.period_length = period_length 19 | self.period_type = period_type 20 | self.recurring = recurring 21 | self.trial_product = trial_product if self.TYPE_SUBSCRIPTION and recurring and recurring != 0 else None 22 | 23 | def get_id(self): 24 | return self.product_id 25 | 26 | def get_amount(self): 27 | return self.amount 28 | 29 | def get_currency_code(self): 30 | return self.currency_code 31 | 32 | def get_name(self): 33 | return self.name or None 34 | 35 | def get_type(self): 36 | return self.product_type 37 | 38 | def get_period_type(self): 39 | return self.period_type 40 | 41 | def get_period_length(self): 42 | return self.period_length 43 | 44 | def is_recurring(self): 45 | return self.recurring 46 | 47 | def get_trial_product(self): 48 | return self.trial_product 49 | -------------------------------------------------------------------------------- /paymentwall/widget.py: -------------------------------------------------------------------------------- 1 | from paymentwall.base import Paymentwall 2 | from paymentwall.product import Product 3 | 4 | import re 5 | 6 | try: 7 | from urllib.parse import urlencode 8 | except ImportError: 9 | from urllib import urlencode 10 | 11 | 12 | class Widget(Paymentwall): 13 | 14 | BASE_URL = 'https://api.paymentwall.com/api' 15 | 16 | def __init__(self, user_id, widget_code, products=[], extra_params={}): 17 | self.user_id = user_id 18 | self.widget_code = widget_code 19 | self.extra_params = extra_params 20 | self.products = products 21 | 22 | def get_default_widget_signature(self): 23 | return self.DEFAULT_SIGNATURE_VERSION if self.get_api_type() != self.API_CART else self.SIGNATURE_VERSION_2 24 | 25 | def get_params(self): 26 | """Get signature and params 27 | """ 28 | params = { 29 | 'key': self.get_app_key(), 30 | 'uid': self.user_id, 31 | 'widget': self.widget_code 32 | } 33 | 34 | products_number = len(self.products) 35 | 36 | if self.get_api_type() == self.API_GOODS: 37 | 38 | if isinstance(self.products, list): 39 | 40 | if products_number == 1: 41 | product = self.products[0] 42 | 43 | if isinstance(product, Product): 44 | post_trial_product = None 45 | 46 | if isinstance(product.get_trial_product(), Product): 47 | post_trial_product = product 48 | product = product.get_trial_product() 49 | 50 | params['amount'] = product.get_amount() 51 | params['currencyCode'] = product.get_currency_code() 52 | params['ag_name'] = product.get_name() 53 | params['ag_external_id'] = product.get_id() 54 | params['ag_type'] = product.get_type() 55 | 56 | if product.get_type() == Product.TYPE_SUBSCRIPTION: 57 | params['ag_period_length'] = product.get_period_length() 58 | params['ag_period_type'] = product.get_period_type() 59 | 60 | if product.is_recurring(): 61 | params['ag_recurring'] = 1 if product.is_recurring() else 0 62 | 63 | if post_trial_product: 64 | params['ag_trial'] = 1 65 | params['ag_post_trial_external_id'] = post_trial_product.get_id() 66 | params['ag_post_trial_period_length'] = post_trial_product.get_period_length() 67 | params['ag_post_trial_period_type'] = post_trial_product.get_period_type() 68 | params['ag_post_trial_name'] = post_trial_product.get_name() 69 | params['post_trial_amount'] = post_trial_product.get_amount() 70 | params['post_trial_currencyCode'] = post_trial_product.get_currency_code() 71 | else: 72 | self.append_to_errors('Not a Product instance') 73 | else: 74 | self.append_to_errors('Only 1 product is allowed') 75 | 76 | elif self.get_api_type() == self.API_CART: 77 | index = 0 78 | 79 | for product in self.products: 80 | params['external_ids[' + str(index) + ']'] = product.get_id() 81 | if product.get_amount() > 0: 82 | params['prices[' + str(index) + ']'] = product.get_amount() 83 | if product.get_currency_code() != '' and product.get_currency_code() is not None: 84 | params['currencies[' + str(index) + ']'] = product.get_currency_code() 85 | index += 1 86 | 87 | params['sign_version'] = signature_version = str(self.get_default_widget_signature()) 88 | 89 | if not self.is_empty(self.extra_params, 'sign_version'): 90 | signature_version = params['sign_version'] = str(self.extra_params['sign_version']) 91 | 92 | params = self.array_merge(params, self.extra_params) 93 | 94 | params['sign'] = self.calculate_signature(params, self.get_secret_key(), int(signature_version)) 95 | return params 96 | 97 | def get_url(self): 98 | return self.BASE_URL + '/' + self.build_controller(self.widget_code) + '?' + urlencode(self.get_params()) 99 | 100 | def get_html_code(self, attributes={}): 101 | default_attributes = { 102 | 'frameborder': '0', 103 | 'width': '750', 104 | 'height': '800' 105 | } 106 | 107 | attributes = self.array_merge(default_attributes, attributes) 108 | 109 | attributes_query = '' 110 | for attr, value in attributes.items(): 111 | attributes_query += ' ' + str(attr) + '="' + str(value) + '"' 112 | 113 | return '' 114 | 115 | def build_controller(self, widget, flexible_call=False): 116 | pattern = '/^w|s|mw/' 117 | 118 | if self.get_api_type() == self.API_VC: 119 | if not re.search(pattern, widget): 120 | return self.VC_CONTROLLER 121 | elif self.get_api_type() == self.API_GOODS: 122 | if not flexible_call and not re.search(pattern, widget): 123 | return self.GOODS_CONTROLLER 124 | else: 125 | return self.CART_CONTROLLER 126 | 127 | @classmethod 128 | def calculate_signature(self, params, secret, version): 129 | base_string = '' 130 | 131 | if version == self.SIGNATURE_VERSION_1: 132 | base_string += params['uid'] if not self.is_empty(params, 'uid') else '' 133 | base_string += secret 134 | return self.hash(base_string, 'md5') 135 | 136 | else: 137 | params = sorted(params.items()) 138 | 139 | for param in params: 140 | if isinstance(param[1], (list, tuple)): 141 | for key in range(len(param[1])): 142 | base_string += str(param[0]) + '[' + str(key) + ']=' + str(param[1][key]) 143 | else: 144 | base_string += str(param[0]) + '=' + str(param[1]) 145 | 146 | base_string += secret 147 | 148 | if version == self.SIGNATURE_VERSION_2: 149 | return self.hash(base_string, 'md5') 150 | 151 | return self.hash(base_string, 'sha256') 152 | -------------------------------------------------------------------------------- /pypi_description.rst: -------------------------------------------------------------------------------- 1 | Paymentwall_ is the leading digital payments platform for globally monetizing digital goods and services. Paymentwall assists game publishers, dating sites, rewards sites, SaaS companies and many other verticals to monetize their digital content and services. Merchants can plugin Paymentwall's API to accept payments from over 100 different methods including credit cards, debit cards, bank transfers, SMS/Mobile payments, prepaid cards, eWallets, landline payments and others. 2 | 3 | To sign up for a Paymentwall Merchant Account, `click here`_. 4 | 5 | This library allows developers to use `Paymentwall APIs`_ (Virtual Currency, Digital Goods featuring recurring billing, and Virtual Cart). 6 | 7 | More instructions and code samples are available on `Paymentwall GitHub page`_. 8 | 9 | .. _Paymentwall: http://www.paymentwall.com/?source=pypi 10 | .. _click here: http://www.paymentwall.com/signup/merchant?source=pypi 11 | .. _Paymentwall GitHub page: https://github.com/paymentwall/paymentwall-python 12 | .. _Paymentwall APIs: http://www.paymentwall.com/en/documentation/API-Documentation/722?source=pypi -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | #from distutils.core import setup 3 | from setuptools import setup 4 | 5 | 6 | def read(fname): 7 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 8 | 9 | 10 | setup( 11 | name='paymentwall-python', 12 | version='1.0.8', 13 | packages=['paymentwall'], 14 | url='https://github.com/paymentwall/paymentwall-python', 15 | description='Paymentwall Python Library', 16 | long_description=read('pypi_description.rst'), 17 | license='MIT', 18 | author='Paymentwall Team', 19 | author_email='devsupport@paymentwall.com' 20 | ) 21 | --------------------------------------------------------------------------------