python-payway 0.0.9__tar.gz → 0.0.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_payway-0.0.9/python_payway.egg-info → python_payway-0.0.11}/PKG-INFO +37 -1
- {python_payway-0.0.9 → python_payway-0.0.11}/README.md +36 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/client.py +17 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/constants.py +1 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/model.py +25 -3
- {python_payway-0.0.9 → python_payway-0.0.11}/pyproject.toml +1 -1
- {python_payway-0.0.9 → python_payway-0.0.11/python_payway.egg-info}/PKG-INFO +37 -1
- {python_payway-0.0.9 → python_payway-0.0.11}/tests/test_client.py +85 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/LICENSE +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/__init__.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/customers.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/exceptions.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/test_utils.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/transactions.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/payway/utils.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/python_payway.egg-info/SOURCES.txt +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/python_payway.egg-info/dependency_links.txt +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/python_payway.egg-info/requires.txt +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/python_payway.egg-info/top_level.txt +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/setup.cfg +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/tests/test_customers.py +0 -0
- {python_payway-0.0.9 → python_payway-0.0.11}/tests/test_transactions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-payway
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Python client for working with Westpac's PayWay REST API
|
|
5
5
|
Author-email: Ben Napper <reppan197@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -274,6 +274,27 @@ Update a customer's payment setup with a new credit card or bank account in PayW
|
|
|
274
274
|
payment_setup, errors = client.update_payment_setup(new_token, payway_customer.customer_number)
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
+
## Renewing the secret API key
|
|
278
|
+
|
|
279
|
+
Secret API keys expire one year after they are created. PayWay generates the replacement
|
|
280
|
+
40 days before that, so an application that asks for the latest key once a day and stores
|
|
281
|
+
what it gets back rolls onto the new key without an administrator creating one in the
|
|
282
|
+
PayWay website.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
api_key, errors = client.get_latest_api_key()
|
|
286
|
+
if api_key and api_key.key != stored_secret_api_key:
|
|
287
|
+
# Persist api_key.key. Log api_key.key_name - it is masked; the key itself is a password.
|
|
288
|
+
save_secret_api_key(api_key.key)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Usually the key returned is the one that authenticated the call. Renewal chains off the
|
|
292
|
+
live key, so if the stored key is left to expire the call fails with a `PaywayError` and
|
|
293
|
+
recovery means minting a key by hand — poll daily and alert on repeated failures.
|
|
294
|
+
|
|
295
|
+
To test your renewal code, create two secret API keys in the PayWay website, configure the
|
|
296
|
+
first, and confirm the application switches to the second on its own.
|
|
297
|
+
|
|
277
298
|
## Additional notes
|
|
278
299
|
|
|
279
300
|
PayWay API documentation <https://www.payway.com.au/docs/rest.html>
|
|
@@ -281,6 +302,21 @@ PayWay API documentation <https://www.payway.com.au/docs/rest.html>
|
|
|
281
302
|
It is recommended to use PayWay's Trusted Frame <https://www.payway.com.au/docs/rest.html#trusted-frame>
|
|
282
303
|
when creating a single use token of a card or bank account so your PCI-compliance scope is reduced.
|
|
283
304
|
|
|
305
|
+
## Keeping the raw response
|
|
306
|
+
|
|
307
|
+
Models parsed from a PayWay response keep that response verbatim on `raw`:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
transaction, errors = client.process_payment(payment)
|
|
311
|
+
transaction.raw # exactly what PayWay returned
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Parsing is lossy — keys PayWay sends that the dataclass does not declare are dropped,
|
|
315
|
+
absent keys become `None`, and a few are renamed (`maskedCardNumber` is parsed into
|
|
316
|
+
`card_number`). Store `raw` rather than `to_dict()` if you are persisting responses for
|
|
317
|
+
auditing, reconciliation or dispute resolution. Models you construct yourself, such as a
|
|
318
|
+
`PayWayPayment` you are about to send, leave `raw` as `None`.
|
|
319
|
+
|
|
284
320
|
## Fraud
|
|
285
321
|
|
|
286
322
|
Please follow PayWay's advice about reducing your risk of fraudulent transactions. <https://www.payway.com.au/docs/card-testing.html#card-testing>
|
|
@@ -251,6 +251,27 @@ Update a customer's payment setup with a new credit card or bank account in PayW
|
|
|
251
251
|
payment_setup, errors = client.update_payment_setup(new_token, payway_customer.customer_number)
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
+
## Renewing the secret API key
|
|
255
|
+
|
|
256
|
+
Secret API keys expire one year after they are created. PayWay generates the replacement
|
|
257
|
+
40 days before that, so an application that asks for the latest key once a day and stores
|
|
258
|
+
what it gets back rolls onto the new key without an administrator creating one in the
|
|
259
|
+
PayWay website.
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
api_key, errors = client.get_latest_api_key()
|
|
263
|
+
if api_key and api_key.key != stored_secret_api_key:
|
|
264
|
+
# Persist api_key.key. Log api_key.key_name - it is masked; the key itself is a password.
|
|
265
|
+
save_secret_api_key(api_key.key)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Usually the key returned is the one that authenticated the call. Renewal chains off the
|
|
269
|
+
live key, so if the stored key is left to expire the call fails with a `PaywayError` and
|
|
270
|
+
recovery means minting a key by hand — poll daily and alert on repeated failures.
|
|
271
|
+
|
|
272
|
+
To test your renewal code, create two secret API keys in the PayWay website, configure the
|
|
273
|
+
first, and confirm the application switches to the second on its own.
|
|
274
|
+
|
|
254
275
|
## Additional notes
|
|
255
276
|
|
|
256
277
|
PayWay API documentation <https://www.payway.com.au/docs/rest.html>
|
|
@@ -258,6 +279,21 @@ PayWay API documentation <https://www.payway.com.au/docs/rest.html>
|
|
|
258
279
|
It is recommended to use PayWay's Trusted Frame <https://www.payway.com.au/docs/rest.html#trusted-frame>
|
|
259
280
|
when creating a single use token of a card or bank account so your PCI-compliance scope is reduced.
|
|
260
281
|
|
|
282
|
+
## Keeping the raw response
|
|
283
|
+
|
|
284
|
+
Models parsed from a PayWay response keep that response verbatim on `raw`:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
transaction, errors = client.process_payment(payment)
|
|
288
|
+
transaction.raw # exactly what PayWay returned
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Parsing is lossy — keys PayWay sends that the dataclass does not declare are dropped,
|
|
292
|
+
absent keys become `None`, and a few are renamed (`maskedCardNumber` is parsed into
|
|
293
|
+
`card_number`). Store `raw` rather than `to_dict()` if you are persisting responses for
|
|
294
|
+
auditing, reconciliation or dispute resolution. Models you construct yourself, such as a
|
|
295
|
+
`PayWayPayment` you are about to send, leave `raw` as `None`.
|
|
296
|
+
|
|
261
297
|
## Fraud
|
|
262
298
|
|
|
263
299
|
Please follow PayWay's advice about reducing your risk of fraudulent transactions. <https://www.payway.com.au/docs/card-testing.html#card-testing>
|
|
@@ -10,6 +10,7 @@ from typing import Any
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
12
|
from payway.constants import (
|
|
13
|
+
API_KEY_URL,
|
|
13
14
|
BANK_ACCOUNT_PAYMENT_CHOICE,
|
|
14
15
|
CREDIT_CARD_PAYMENT_CHOICE,
|
|
15
16
|
CUSTOMER_URL,
|
|
@@ -26,6 +27,7 @@ from payway.model import (
|
|
|
26
27
|
BankAccount,
|
|
27
28
|
PaymentError,
|
|
28
29
|
PaymentSetup,
|
|
30
|
+
PayWayApiKey,
|
|
29
31
|
PayWayCard,
|
|
30
32
|
PayWayCustomer,
|
|
31
33
|
PayWayPayment,
|
|
@@ -361,6 +363,21 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
361
363
|
return None, errors
|
|
362
364
|
return PayWayTransaction.from_dict(response.json()), errors
|
|
363
365
|
|
|
366
|
+
def get_latest_api_key(self) -> tuple[PayWayApiKey | None, list[PaymentError] | None]:
|
|
367
|
+
"""
|
|
368
|
+
Return the secret API key to use from now on, authenticated with the current one.
|
|
369
|
+
Usually this is the key already in use. PayWay generates the replacement 40 days
|
|
370
|
+
before the current key expires, and returns that instead once it exists, so callers
|
|
371
|
+
polling daily and persisting the result renew without an administrator minting a key
|
|
372
|
+
in the PayWay website.
|
|
373
|
+
https://www.payway.com.au/docs/rest.html#automate-secret-api-key-renewal
|
|
374
|
+
"""
|
|
375
|
+
response = self.get_request(f"{API_KEY_URL}/latest")
|
|
376
|
+
errors = self._validate_response(response)
|
|
377
|
+
if errors:
|
|
378
|
+
return None, errors
|
|
379
|
+
return PayWayApiKey.from_dict(response.json()), errors
|
|
380
|
+
|
|
364
381
|
def get_customer(self, customer_id: str) -> tuple[PayWayCustomer | None, list[PaymentError] | None]:
|
|
365
382
|
"""
|
|
366
383
|
Returns a PayWay Customer's Payment Setup, [Payment] Schedule, Contact Details, Custom Fields and Notes
|
|
@@ -13,6 +13,7 @@ PAYWAY_API_URL = "https://api.payway.com.au/rest/v1"
|
|
|
13
13
|
TOKEN_URL = PAYWAY_API_URL + "/single-use-tokens"
|
|
14
14
|
TRANSACTION_URL = PAYWAY_API_URL + "/transactions"
|
|
15
15
|
CUSTOMER_URL = PAYWAY_API_URL + "/customers"
|
|
16
|
+
API_KEY_URL = PAYWAY_API_URL + "/api-keys"
|
|
16
17
|
TRANSACTION_APPROVED = "0"
|
|
17
18
|
|
|
18
19
|
SUMMARY_CODES = {
|
|
@@ -15,9 +15,16 @@ class PayWayModel:
|
|
|
15
15
|
alias: PayWay key when it is not the camelCase of the field name
|
|
16
16
|
exclude: omit the field from to_dict output
|
|
17
17
|
from_dict: callable applied to a non-None raw value when parsing
|
|
18
|
+
|
|
19
|
+
Instances built by from_dict keep the response body they were parsed from
|
|
20
|
+
on ``raw``, unchanged. Parsing is lossy - undeclared PayWay keys are dropped,
|
|
21
|
+
absent ones become None, and aliases rename them - so callers persisting a
|
|
22
|
+
response for auditing or dispute resolution should store ``raw``, not
|
|
23
|
+
``to_dict()``. Models you build yourself leave it None.
|
|
18
24
|
"""
|
|
19
25
|
|
|
20
26
|
__dataclass_fields__: ClassVar[dict[str, Any]]
|
|
27
|
+
raw: dict[str, Any] | None = None
|
|
21
28
|
|
|
22
29
|
def to_dict(self) -> dict[str, Any]:
|
|
23
30
|
result = {}
|
|
@@ -39,7 +46,9 @@ class PayWayModel:
|
|
|
39
46
|
if converter is not None and value is not None:
|
|
40
47
|
value = converter(value)
|
|
41
48
|
kwargs[f.name] = value
|
|
42
|
-
|
|
49
|
+
instance = cls(**kwargs)
|
|
50
|
+
instance.raw = data
|
|
51
|
+
return instance
|
|
43
52
|
|
|
44
53
|
|
|
45
54
|
@dataclass
|
|
@@ -65,8 +74,10 @@ class PayWayCard(PayWayModel):
|
|
|
65
74
|
|
|
66
75
|
@classmethod
|
|
67
76
|
def from_dict(cls, data: dict[str, Any]) -> PayWayCard:
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
card = super().from_dict({**data, "cardNumber": data.get("maskedCardNumber") or data.get("cardNumber")})
|
|
78
|
+
# Keep PayWay's own body, not the copy rewritten for the alias above.
|
|
79
|
+
card.raw = data
|
|
80
|
+
return card
|
|
70
81
|
|
|
71
82
|
|
|
72
83
|
@dataclass
|
|
@@ -284,3 +295,14 @@ class TokenResponse(PayWayModel):
|
|
|
284
295
|
payment_method: str | None = None
|
|
285
296
|
card: PayWayCard | None = field(default=None, metadata={"alias": "creditCard", "from_dict": PayWayCard.from_dict})
|
|
286
297
|
bank_account: dict[str, Any] | None = None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@dataclass
|
|
301
|
+
class PayWayApiKey(PayWayModel):
|
|
302
|
+
"""
|
|
303
|
+
key_name: str: masked form of the key, safe to log
|
|
304
|
+
key: str: the secret API key itself - treat it like a password
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
key_name: str | None = None
|
|
308
|
+
key: str | None = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-payway
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Python client for working with Westpac's PayWay REST API
|
|
5
5
|
Author-email: Ben Napper <reppan197@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -274,6 +274,27 @@ Update a customer's payment setup with a new credit card or bank account in PayW
|
|
|
274
274
|
payment_setup, errors = client.update_payment_setup(new_token, payway_customer.customer_number)
|
|
275
275
|
```
|
|
276
276
|
|
|
277
|
+
## Renewing the secret API key
|
|
278
|
+
|
|
279
|
+
Secret API keys expire one year after they are created. PayWay generates the replacement
|
|
280
|
+
40 days before that, so an application that asks for the latest key once a day and stores
|
|
281
|
+
what it gets back rolls onto the new key without an administrator creating one in the
|
|
282
|
+
PayWay website.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
api_key, errors = client.get_latest_api_key()
|
|
286
|
+
if api_key and api_key.key != stored_secret_api_key:
|
|
287
|
+
# Persist api_key.key. Log api_key.key_name - it is masked; the key itself is a password.
|
|
288
|
+
save_secret_api_key(api_key.key)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Usually the key returned is the one that authenticated the call. Renewal chains off the
|
|
292
|
+
live key, so if the stored key is left to expire the call fails with a `PaywayError` and
|
|
293
|
+
recovery means minting a key by hand — poll daily and alert on repeated failures.
|
|
294
|
+
|
|
295
|
+
To test your renewal code, create two secret API keys in the PayWay website, configure the
|
|
296
|
+
first, and confirm the application switches to the second on its own.
|
|
297
|
+
|
|
277
298
|
## Additional notes
|
|
278
299
|
|
|
279
300
|
PayWay API documentation <https://www.payway.com.au/docs/rest.html>
|
|
@@ -281,6 +302,21 @@ PayWay API documentation <https://www.payway.com.au/docs/rest.html>
|
|
|
281
302
|
It is recommended to use PayWay's Trusted Frame <https://www.payway.com.au/docs/rest.html#trusted-frame>
|
|
282
303
|
when creating a single use token of a card or bank account so your PCI-compliance scope is reduced.
|
|
283
304
|
|
|
305
|
+
## Keeping the raw response
|
|
306
|
+
|
|
307
|
+
Models parsed from a PayWay response keep that response verbatim on `raw`:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
transaction, errors = client.process_payment(payment)
|
|
311
|
+
transaction.raw # exactly what PayWay returned
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Parsing is lossy — keys PayWay sends that the dataclass does not declare are dropped,
|
|
315
|
+
absent keys become `None`, and a few are renamed (`maskedCardNumber` is parsed into
|
|
316
|
+
`card_number`). Store `raw` rather than `to_dict()` if you are persisting responses for
|
|
317
|
+
auditing, reconciliation or dispute resolution. Models you construct yourself, such as a
|
|
318
|
+
`PayWayPayment` you are about to send, leave `raw` as `None`.
|
|
319
|
+
|
|
284
320
|
## Fraud
|
|
285
321
|
|
|
286
322
|
Please follow PayWay's advice about reducing your risk of fraudulent transactions. <https://www.payway.com.au/docs/card-testing.html#card-testing>
|
|
@@ -179,6 +179,29 @@ class TestClient(unittest.TestCase):
|
|
|
179
179
|
self.assertEqual(transaction.status, "approved")
|
|
180
180
|
self.assertEqual(transaction.response_code, "11")
|
|
181
181
|
|
|
182
|
+
@patch("requests.post")
|
|
183
|
+
def test_process_payment_keeps_the_raw_response(self, mock_post) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Parsing drops keys PayWay sent, so ``raw`` keeps the body verbatim for
|
|
186
|
+
callers that persist responses for auditing or dispute resolution.
|
|
187
|
+
"""
|
|
188
|
+
response = load_json_file("tests/data/transaction.json")
|
|
189
|
+
mock_post.return_value.status_code = 200
|
|
190
|
+
mock_post.return_value.json.return_value = response
|
|
191
|
+
payment = copy.deepcopy(self.payment)
|
|
192
|
+
payment.customer_number = "1"
|
|
193
|
+
payment.token = "2bcec36f-7b02-43db-b3ec-bfb65acfe272"
|
|
194
|
+
payment.order_number = "5200"
|
|
195
|
+
payment.merchant_id = self.client.merchant_id
|
|
196
|
+
|
|
197
|
+
transaction, _ = self.client.process_payment(payment)
|
|
198
|
+
|
|
199
|
+
self.assertEqual(transaction.raw, response)
|
|
200
|
+
# cardScheme and cardType are not modelled, so only raw still has them.
|
|
201
|
+
self.assertEqual(transaction.raw["creditCard"]["cardScheme"], "visa")
|
|
202
|
+
self.assertNotIn("cardScheme", transaction.to_dict()["creditCard"])
|
|
203
|
+
self.assertEqual(transaction.card.raw, response["creditCard"])
|
|
204
|
+
|
|
182
205
|
@patch("requests.post")
|
|
183
206
|
def test_process_payment_with_idempotency_key(self, mock_post) -> None:
|
|
184
207
|
"""
|
|
@@ -262,6 +285,68 @@ class TestClient(unittest.TestCase):
|
|
|
262
285
|
self.assertIsNone(ps_errors)
|
|
263
286
|
self.assertIsNotNone(ps)
|
|
264
287
|
|
|
288
|
+
@patch("requests.get")
|
|
289
|
+
def test_get_latest_api_key(self, mock_get) -> None:
|
|
290
|
+
mock_get.return_value.status_code = 200
|
|
291
|
+
mock_get.return_value.json.return_value = {
|
|
292
|
+
"keyName": "T10000_SEC...1A4",
|
|
293
|
+
"key": "T10000_SEC_RANDOM_RANDOM_RANDOM_1A4",
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
api_key, errors = self.client.get_latest_api_key()
|
|
297
|
+
|
|
298
|
+
self.assertIsNone(errors)
|
|
299
|
+
self.assertEqual(api_key.key_name, "T10000_SEC...1A4")
|
|
300
|
+
self.assertEqual(api_key.key, "T10000_SEC_RANDOM_RANDOM_RANDOM_1A4")
|
|
301
|
+
self.assertEqual(mock_get.call_args.kwargs["url"], "https://api.payway.com.au/rest/v1/api-keys/latest")
|
|
302
|
+
self.assertEqual(mock_get.call_args.kwargs["auth"], ("TPUBLISHABLE-SECRET", ""))
|
|
303
|
+
|
|
304
|
+
@patch("requests.get")
|
|
305
|
+
def test_get_latest_api_key_returns_the_replacement_key(self, mock_get) -> None:
|
|
306
|
+
"""
|
|
307
|
+
PayWay generates the next secret key 40 days before the current one expires and
|
|
308
|
+
returns it here in place of the key that authenticated the request.
|
|
309
|
+
"""
|
|
310
|
+
mock_get.return_value.status_code = 200
|
|
311
|
+
mock_get.return_value.json.return_value = {
|
|
312
|
+
"keyName": "T10000_SEC...9B2",
|
|
313
|
+
"key": "T10000_SEC_NEXT_NEXT_NEXT_9B2",
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
api_key, errors = self.client.get_latest_api_key()
|
|
317
|
+
|
|
318
|
+
self.assertIsNone(errors)
|
|
319
|
+
self.assertNotEqual(api_key.key, self.client.secret_api_key)
|
|
320
|
+
self.assertEqual(api_key.key, "T10000_SEC_NEXT_NEXT_NEXT_9B2")
|
|
321
|
+
|
|
322
|
+
@patch("requests.get")
|
|
323
|
+
def test_get_latest_api_key_returns_payway_errors(self, mock_get) -> None:
|
|
324
|
+
mock_get.return_value.status_code = 422
|
|
325
|
+
mock_get.return_value.json.return_value = {
|
|
326
|
+
"data": [{"fieldName": "apiKey", "message": "Invalid API key.", "fieldValue": ""}]
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
api_key, errors = self.client.get_latest_api_key()
|
|
330
|
+
|
|
331
|
+
self.assertIsNone(api_key)
|
|
332
|
+
self.assertEqual(len(errors), 1)
|
|
333
|
+
self.assertEqual(errors[0].message, "Invalid API key.")
|
|
334
|
+
|
|
335
|
+
@patch("requests.get")
|
|
336
|
+
def test_get_latest_api_key_raises_when_the_current_key_is_rejected(self, mock_get) -> None:
|
|
337
|
+
"""
|
|
338
|
+
Renewal chains off the live key, so an expired one cannot fetch its replacement -
|
|
339
|
+
recovering needs an administrator to create a key in the PayWay website.
|
|
340
|
+
"""
|
|
341
|
+
mock_get.return_value.status_code = 401
|
|
342
|
+
mock_get.return_value.reason = "Unauthorized"
|
|
343
|
+
mock_get.return_value.url = "https://api.payway.com.au/rest/v1/api-keys/latest"
|
|
344
|
+
|
|
345
|
+
with self.assertRaises(PaywayError) as context:
|
|
346
|
+
self.client.get_latest_api_key()
|
|
347
|
+
|
|
348
|
+
self.assertIn("401", str(context.exception))
|
|
349
|
+
|
|
265
350
|
|
|
266
351
|
class TestClientRetries(unittest.TestCase):
|
|
267
352
|
def setUp(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|