1 | ## $Id$ |
---|
2 | ## |
---|
3 | ## Copyright (C) 2022 Uli Fouquet & Henrik Bettermann |
---|
4 | ## This program is free software; you can redistribute it and/or modify |
---|
5 | ## it under the terms of the GNU General Public License as published by |
---|
6 | ## the Free Software Foundation; either version 2 of the License, or |
---|
7 | ## (at your option) any later version. |
---|
8 | ## |
---|
9 | ## This program is distributed in the hope that it will be useful, |
---|
10 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
11 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
12 | ## GNU General Public License for more details. |
---|
13 | ## |
---|
14 | ## You should have received a copy of the GNU General Public License |
---|
15 | ## along with this program; if not, write to the Free Software |
---|
16 | ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
---|
17 | ## |
---|
18 | import datetime |
---|
19 | import mock |
---|
20 | import os |
---|
21 | import unittest |
---|
22 | from zope.component import queryUtility, createObject |
---|
23 | from waeup.kofa.applicants.tests.test_browser import ApplicantsFullSetup |
---|
24 | from waeup.kofa.browser.tests.test_browser import UniversitySetup |
---|
25 | from waeup.kofa.configuration import SessionConfiguration |
---|
26 | from kofacustom.nigeria.paypal.config import VALID_PAYPAL_EMAILS |
---|
27 | from kofacustom.nigeria.paypal.interfaces import IPaypalConfig |
---|
28 | from kofacustom.nigeria.paypal.ipn_listener import VERIFY_URL_TEST, verify_notification |
---|
29 | from kofacustom.nigeria.paypal import Amount, PurchaseUnit, module_activated |
---|
30 | from kofacustom.nigeria.paypal.rest import ( |
---|
31 | get_auth_token, create_order, capture_order) |
---|
32 | from kofacustom.nigeria.testing import FunctionalLayer |
---|
33 | |
---|
34 | |
---|
35 | RUN_EXTERNAL_TESTS = False |
---|
36 | |
---|
37 | |
---|
38 | class MockResponse(object): |
---|
39 | def __init__(self, *args, **kw): |
---|
40 | self.text = "VERIFIED" |
---|
41 | self.json_data = None |
---|
42 | |
---|
43 | def raise_for_status(self): |
---|
44 | pass |
---|
45 | |
---|
46 | def json(self): |
---|
47 | return self.json_data |
---|
48 | |
---|
49 | |
---|
50 | class PaypalIPNListenerTests(unittest.TestCase): |
---|
51 | def setUp(self): |
---|
52 | pass |
---|
53 | |
---|
54 | def tearDown(self): |
---|
55 | pass |
---|
56 | |
---|
57 | @mock.patch("requests.post", side_effect=MockResponse) |
---|
58 | def test_verify_transaction(self, post): |
---|
59 | self.assertEqual(verify_notification("foo=bar&bar=baz"), False) |
---|
60 | self.assertTrue(post.called) |
---|
61 | valid_email = VALID_PAYPAL_EMAILS[0] |
---|
62 | self.assertEqual( |
---|
63 | verify_notification("foo=bar&receiver_email=%s" % valid_email), True |
---|
64 | ) |
---|
65 | |
---|
66 | |
---|
67 | class PaypalConfigTests(unittest.TestCase): |
---|
68 | |
---|
69 | layer = FunctionalLayer |
---|
70 | |
---|
71 | def test_paypal_config_util_available(self): |
---|
72 | config = queryUtility(IPaypalConfig) |
---|
73 | self.assertTrue(config is not None) |
---|
74 | |
---|
75 | |
---|
76 | def mocked_requests_post(*args, **kw): |
---|
77 | |
---|
78 | response = MockResponse() |
---|
79 | response.status_code = 401 |
---|
80 | if args[0].endswith("v1/oauth2/token"): |
---|
81 | response.status_code = 200 |
---|
82 | response.json_data = { |
---|
83 | "scope": "https://uri.paypal.com/services/invoicing https://uri.paypal.com/services/vault/payment-tokens/read https://uri.paypal.com/services/disputes/read-buyer https://uri.paypal.com/services/payments/realtimepayment https://uri.paypal.com/services/disputes/update-seller https://uri.paypal.com/services/payments/payment/authcapture openid https://uri.paypal.com/services/disputes/read-seller Braintree:Vault https://uri.paypal.com/services/payments/refund https://api.paypal.com/v1/vault/credit-card https://uri.paypal.com/services/billing-agreements https://api.paypal.com/v1/payments/.* https://uri.paypal.com/payments/payouts https://uri.paypal.com/services/vault/payment-tokens/readwrite https://api.paypal.com/v1/vault/credit-card/.* https://uri.paypal.com/services/shipping/trackers/readwrite https://uri.paypal.com/services/subscriptions https://uri.paypal.com/services/applications/webhooks", |
---|
84 | "access_token": "A21AAL21A1ohAg6u75tLLi3LSshFmONDD8JC_AOd0Bkmc2cloeeSZIofamBbFXEYHvq7C5ViKJeSDq9PMfoV6shmfRhVyvDIg", |
---|
85 | "token_type": "Bearer", |
---|
86 | "app_id": "APP-80W284485P519543T", |
---|
87 | "expires_in": 32400, |
---|
88 | "nonce": "2022-12-14T08:05:21ZF5NvAXAkc46HOplYDYqHnBBPJDZJbwd4e3zDgaI5Vo0", |
---|
89 | } |
---|
90 | if args[0].endswith("/v2/checkout/orders"): |
---|
91 | response.status_code = 200 |
---|
92 | response.json_data = { |
---|
93 | "id": "2LP804900J8837429", |
---|
94 | "status": "PAYER_ACTION_REQUIRED", |
---|
95 | "payment_source": {"paypal": {}}, |
---|
96 | "links": [ |
---|
97 | { |
---|
98 | "href": "https://api.sandbox.paypal.com/v2/checkout/orders/2LP804900J8837429", |
---|
99 | "rel": "self", |
---|
100 | "method": "GET", |
---|
101 | }, |
---|
102 | { |
---|
103 | "href": "https://www.sandbox.paypal.com/checkoutnow?token=2LP804900J8837429", |
---|
104 | "rel": "payer-action", |
---|
105 | "method": "GET", |
---|
106 | }, |
---|
107 | ], |
---|
108 | } |
---|
109 | if args[0].endswith("/capture"): |
---|
110 | response.status_code = 201 |
---|
111 | response.json_data = { |
---|
112 | "id":"2LP804900J8837429", |
---|
113 | "status":"COMPLETED", |
---|
114 | "payment_source":{ |
---|
115 | "paypal":{ |
---|
116 | "email_address":"sb-90spn23140065@personal.example.com", |
---|
117 | "account_id":"3LP3QX8H9YC3G", |
---|
118 | "name":{ |
---|
119 | "given_name": "John", |
---|
120 | "surname":"Doe" |
---|
121 | }, |
---|
122 | "address":{"country_code":"DE"}}}, |
---|
123 | "purchase_units":[ |
---|
124 | { |
---|
125 | "reference_id":"0", |
---|
126 | "payments":{ |
---|
127 | "captures":[ |
---|
128 | { |
---|
129 | "id":"3JF41209TB864721F", |
---|
130 | "status":"COMPLETED", |
---|
131 | "amount":{ |
---|
132 | "currency_code":"USD", |
---|
133 | "value":"3.23"}, |
---|
134 | "final_capture":True, |
---|
135 | "seller_protection":{ |
---|
136 | "status":"ELIGIBLE", |
---|
137 | "dispute_categories":[ |
---|
138 | "ITEM_NOT_RECEIVED", |
---|
139 | "UNAUTHORIZED_TRANSACTION"] |
---|
140 | }, |
---|
141 | "seller_receivable_breakdown":{ |
---|
142 | "gross_amount":{ |
---|
143 | "currency_code":"USD", |
---|
144 | "value":"3.23"}, |
---|
145 | "paypal_fee":{ |
---|
146 | "currency_code":"USD", |
---|
147 | "value":"0.36"}, |
---|
148 | "net_amount":{ |
---|
149 | "currency_code":"USD", |
---|
150 | "value":"2.87"} |
---|
151 | }, |
---|
152 | "custom_id":"my-custom-id", |
---|
153 | "links":[ |
---|
154 | { |
---|
155 | "href":"https://api.sandbox.paypal.com/v2/payments/captures/3JF41209TB864721F", |
---|
156 | "rel":"self","method":"GET" |
---|
157 | }, |
---|
158 | { |
---|
159 | "href":"https://api.sandbox.paypal.com/v2/payments/captures/3JF41209TB864721F/refund", |
---|
160 | "rel":"refund","method":"POST" |
---|
161 | }, |
---|
162 | { |
---|
163 | "href":"https://api.sandbox.paypal.com/v2/checkout/orders/2LP804900J8837429", |
---|
164 | "rel":"up", |
---|
165 | "method":"GET" |
---|
166 | } |
---|
167 | ], |
---|
168 | "create_time":"2022-12-17T16:34:12Z", |
---|
169 | "update_time":"2022-12-17T16:34:12Z" |
---|
170 | } |
---|
171 | ] |
---|
172 | } |
---|
173 | } |
---|
174 | ], |
---|
175 | "payer":{ |
---|
176 | "name":{ |
---|
177 | "given_name":"John", |
---|
178 | "surname":"Doe" |
---|
179 | }, |
---|
180 | "email_address":"sb-90spn23140065@personal.example.com", |
---|
181 | "payer_id":"3LP3QX8H9YC3G","address":{ |
---|
182 | "country_code":"DE" |
---|
183 | } |
---|
184 | }, |
---|
185 | "links":[ |
---|
186 | { |
---|
187 | "href":"https://api.sandbox.paypal.com/v2/checkout/orders/2LP804900J8837429", |
---|
188 | "rel":"self", |
---|
189 | "method":"GET" |
---|
190 | } |
---|
191 | ] |
---|
192 | } |
---|
193 | return response |
---|
194 | |
---|
195 | |
---|
196 | sample_units = [PurchaseUnit(description="Test Good", amount=Amount(value="3.23"))] |
---|
197 | |
---|
198 | |
---|
199 | class PaypalRESTClientTests(unittest.TestCase): |
---|
200 | |
---|
201 | layer = FunctionalLayer # we need the IPaypalConfig data |
---|
202 | |
---|
203 | @unittest.skipUnless(RUN_EXTERNAL_TESTS, "Live paypal tests skipped") |
---|
204 | def test_get_auth_token(self): |
---|
205 | result = get_auth_token() |
---|
206 | self.assertTrue(isinstance(result, basestring)) |
---|
207 | |
---|
208 | @unittest.skipUnless(RUN_EXTERNAL_TESTS, "Live paypal tests skipped") |
---|
209 | def test_create_order_live(self): |
---|
210 | token, url, request_id, data = create_order( |
---|
211 | sample_units, "http://fake-return-url", "http://fake-cancel-url" |
---|
212 | ) |
---|
213 | self.assertTrue(isinstance(token, basestring)) |
---|
214 | self.assertTrue("paypal.com/checkoutnow?token=" in url) |
---|
215 | self.order_id = token |
---|
216 | |
---|
217 | @mock.patch("requests.post", side_effect=mocked_requests_post) |
---|
218 | def test_create_order(self, mock_post): |
---|
219 | token, url, request_id, data = create_order( |
---|
220 | sample_units, "http://fake-return-url", "http://fake-cancel-url" |
---|
221 | ) |
---|
222 | self.assertEqual(len(mock_post.call_args_list), 2) |
---|
223 | self.assertEqual(token, "2LP804900J8837429") |
---|
224 | |
---|
225 | @mock.patch("requests.post", side_effect=mocked_requests_post) |
---|
226 | def test_capture_order(self, mock_post): |
---|
227 | result = capture_order("2LP804900J8837429") |
---|
228 | self.assertEqual(len(mock_post.call_args_list), 2) |
---|
229 | self.assertEqual(result["status"], "COMPLETED") |
---|
230 | |
---|
231 | |
---|
232 | class PaypalBaseObjectsTests(unittest.TestCase): |
---|
233 | def test_amount(self): |
---|
234 | # we can create Amount objects |
---|
235 | amt = Amount() |
---|
236 | self.assertEqual(amt.value, "0.00") |
---|
237 | self.assertEqual(amt.currency_code, "USD") |
---|
238 | |
---|
239 | def test_amount_json(self): |
---|
240 | # we can get a JSON representation of amounts |
---|
241 | amt = Amount() |
---|
242 | self.assertEqual(amt.json(), {"value": "0.00", "currency_code": "USD"}) |
---|
243 | |
---|
244 | def test_purchase_unit(self): |
---|
245 | # we can create PurchaseItem instances |
---|
246 | item = PurchaseUnit() |
---|
247 | self.assertTrue(isinstance(item.amount, Amount)) |
---|
248 | self.assertEqual(item.custom_id, None) |
---|
249 | self.assertEqual(item.description, "") |
---|
250 | |
---|
251 | def test_purchase_unit_json(self): |
---|
252 | # we can get PurchaseItem instances as JSON |
---|
253 | item = PurchaseUnit() |
---|
254 | self.assertEqual( |
---|
255 | item.json(), |
---|
256 | { |
---|
257 | "amount": {"value": "0.00", "currency_code": "USD"}, |
---|
258 | "description": "", |
---|
259 | }, |
---|
260 | ) |
---|
261 | |
---|
262 | |
---|
263 | class PaypalHelperTests(UniversitySetup): |
---|
264 | |
---|
265 | layer = FunctionalLayer # we need the IPaypalConfig data |
---|
266 | |
---|
267 | def test_module_activated(self): |
---|
268 | session_conf1 = SessionConfiguration() |
---|
269 | session_conf2 = SessionConfiguration() |
---|
270 | curr_year = datetime.datetime.now().year |
---|
271 | session_conf1.academic_session = curr_year - 2 |
---|
272 | session_conf2.academic_session = curr_year |
---|
273 | session_conf1.paypal_enabled = True |
---|
274 | session_conf2.paypal_enabled = True |
---|
275 | # session_conf2 yet not active... |
---|
276 | self.app['configuration'].addSessionConfiguration(session_conf1) |
---|
277 | self.assertFalse(module_activated(2000, None)) |
---|
278 | self.assertFalse(module_activated(curr_year, None)) |
---|
279 | self.assertTrue(module_activated(curr_year - 2, None)) |
---|
280 | payment = createObject('waeup.StudentOnlinePayment') |
---|
281 | payment.r_company = 'unknown' |
---|
282 | payment.p_currency = 'USD' |
---|
283 | self.assertFalse(module_activated(curr_year - 2, payment)) |
---|
284 | payment.r_company = 'paypal' |
---|
285 | self.assertTrue(module_activated(curr_year - 2, payment)) |
---|
286 | self.assertFalse(module_activated(curr_year, payment)) |
---|
287 | # activate also session_conf2 |
---|
288 | self.app['configuration'].addSessionConfiguration(session_conf2) |
---|
289 | self.assertTrue(module_activated(curr_year - 1, payment)) |
---|
290 | self.assertTrue(module_activated(curr_year, payment)) |
---|
291 | |
---|
292 | |
---|
293 | class PaypalTestsApplicants(ApplicantsFullSetup): |
---|
294 | """Tests for the Paypal payment gateway. |
---|
295 | """ |
---|
296 | |
---|
297 | layer = FunctionalLayer |
---|
298 | |
---|
299 | def setUp(self): |
---|
300 | super(PaypalTestsApplicants, self).setUp() |
---|
301 | configuration = SessionConfiguration() |
---|
302 | configuration.academic_session = datetime.datetime.now().year - 2 |
---|
303 | configuration.paypal_enabled = True |
---|
304 | self.app['configuration'].addSessionConfiguration(configuration) |
---|
305 | self.browser.addHeader('Authorization', 'Basic mgr:mgrpw') |
---|
306 | self.browser.open(self.manage_path) |
---|
307 | #IWorkflowState(self.student).setState('started') |
---|
308 | super(PaypalTestsApplicants, self).fill_correct_values() |
---|
309 | self.applicantscontainer.application_fee = 10.0 |
---|
310 | self.browser.getControl(name="form.nationality").value = ['NG'] |
---|
311 | self.browser.getControl(name="transition").value = ['start'] |
---|
312 | self.browser.getControl("Save").click() |
---|
313 | self.browser.getControl("Add online").click() |
---|
314 | self.assertMatches('...ticket created...', |
---|
315 | self.browser.contents) |
---|
316 | self.payment = self.applicant.values()[0] |
---|
317 | self.payment_url = self.browser.url |
---|
318 | |
---|
319 | #@unittest.skipUnless(RUN_EXTERNAL_TESTS, "Live paypal tests skipped") |
---|
320 | #def test_applicant_paypal_form(self): |
---|
321 | # import pdb; pdb.set_trace() |
---|
322 | # self.browser.getLink("Pay via Paypal").click() |
---|
323 | # logfile = os.path.join( |
---|
324 | # self.app['datacenter'].storage, 'logs', 'applicants.log') |
---|
325 | # logcontent = open(logfile).read() |
---|
326 | # self.assertTrue(self.payment.r_pay_reference in logcontent) |
---|