├── .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 |
--------------------------------------------------------------------------------