python-payway 0.0.7__tar.gz → 0.0.8__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.7/python_payway.egg-info → python_payway-0.0.8}/PKG-INFO +39 -7
- {python_payway-0.0.7 → python_payway-0.0.8}/README.md +35 -4
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/client.py +71 -28
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/constants.py +28 -14
- python_payway-0.0.8/payway/model.py +286 -0
- python_payway-0.0.8/payway/utils.py +27 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/pyproject.toml +7 -6
- {python_payway-0.0.7 → python_payway-0.0.8/python_payway.egg-info}/PKG-INFO +39 -7
- {python_payway-0.0.7 → python_payway-0.0.8}/python_payway.egg-info/SOURCES.txt +0 -1
- {python_payway-0.0.7 → python_payway-0.0.8}/tests/test_client.py +115 -1
- python_payway-0.0.7/payway/model.py +0 -453
- python_payway-0.0.7/payway/utils.py +0 -23
- python_payway-0.0.7/tests/__init__.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/LICENSE +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/__init__.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/customers.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/exceptions.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/test_utils.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/payway/transactions.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/python_payway.egg-info/dependency_links.txt +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/python_payway.egg-info/requires.txt +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/python_payway.egg-info/top_level.txt +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/setup.cfg +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/tests/test_customers.py +0 -0
- {python_payway-0.0.7 → python_payway-0.0.8}/tests/test_transactions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-payway
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
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
|
|
@@ -11,7 +11,7 @@ Keywords: payway,westpac,api,client
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: requests>=2.20.0
|
|
@@ -19,6 +19,7 @@ Provides-Extra: dev
|
|
|
19
19
|
Requires-Dist: pre-commit>=3.5.0; extra == "dev"
|
|
20
20
|
Requires-Dist: ruff==0.6.6; extra == "dev"
|
|
21
21
|
Requires-Dist: mypy>=0.971; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
22
23
|
|
|
23
24
|
# PayWay REST API - Python
|
|
24
25
|
|
|
@@ -32,6 +33,8 @@ Requires-Dist: mypy>=0.971; extra == "dev"
|
|
|
32
33
|
|
|
33
34
|
## Install
|
|
34
35
|
|
|
36
|
+
Requires Python 3.11 or later.
|
|
37
|
+
|
|
35
38
|
```bash
|
|
36
39
|
pip install python-payway
|
|
37
40
|
```
|
|
@@ -131,6 +134,36 @@ payment = PayWayPayment(customer_number=customer_number,
|
|
|
131
134
|
transaction, errors = client.process_payment(payment)
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
## Retries
|
|
138
|
+
|
|
139
|
+
Retries are off by default. Opt in with `max_retries` (and optionally `retry_delay`, the base
|
|
140
|
+
wait in seconds between attempts):
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
client = Client(merchant_id='<your_payway_merchant_id>',
|
|
144
|
+
bank_account_id='<your_payway_bank_account_id>',
|
|
145
|
+
publishable_api_key='<your_payway_publishable_api_key>',
|
|
146
|
+
secret_api_key='<your_payway_secret_api_key>',
|
|
147
|
+
max_retries=2,
|
|
148
|
+
retry_delay=1.0)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This follows PayWay's retry guidance (<https://www.payway.com.au/docs/rest.html#network-errors>):
|
|
152
|
+
|
|
153
|
+
- Requests are resent on connection errors, timeouts and HTTP 429/503 responses, waiting
|
|
154
|
+
`retry_delay` seconds between attempts (linear backoff, or the response's `Retry-After`
|
|
155
|
+
header when present). PayWay suggests a 20 second wait; keep `retry_delay` small for
|
|
156
|
+
synchronous checkout flows.
|
|
157
|
+
- POSTs are only retried when an `idempotency_key` was supplied — the same
|
|
158
|
+
`Idempotency-Key` is resent so PayWay replays the original response instead of
|
|
159
|
+
processing a duplicate payment. POSTs without a key (and PUTs) are never retried.
|
|
160
|
+
GETs are always safe to retry.
|
|
161
|
+
- Other errors (including HTTP 500/502/504) are never retried, per PayWay's advice.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
transaction, errors = client.process_payment(payment, idempotency_key=str(uuid.uuid4()))
|
|
165
|
+
```
|
|
166
|
+
|
|
134
167
|
## Handling errors
|
|
135
168
|
|
|
136
169
|
Documented errors (such as 422 Unprocessable entity) are parsed into an PaymentError class that you can use in an customer error message.
|
|
@@ -234,12 +267,11 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction
|
|
|
234
267
|
## Running the project
|
|
235
268
|
|
|
236
269
|
```bash
|
|
237
|
-
uv
|
|
238
|
-
uv venv --python 3.8.19
|
|
239
|
-
source .venv/bin/activate
|
|
240
|
-
uv sync --extra dev
|
|
270
|
+
uv sync
|
|
241
271
|
```
|
|
242
272
|
|
|
273
|
+
uv provisions a suitable Python (3.11+) and installs the dev dependencies automatically.
|
|
274
|
+
|
|
243
275
|
## Testing
|
|
244
276
|
|
|
245
277
|
```bash
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
Requires Python 3.11 or later.
|
|
14
|
+
|
|
13
15
|
```bash
|
|
14
16
|
pip install python-payway
|
|
15
17
|
```
|
|
@@ -109,6 +111,36 @@ payment = PayWayPayment(customer_number=customer_number,
|
|
|
109
111
|
transaction, errors = client.process_payment(payment)
|
|
110
112
|
```
|
|
111
113
|
|
|
114
|
+
## Retries
|
|
115
|
+
|
|
116
|
+
Retries are off by default. Opt in with `max_retries` (and optionally `retry_delay`, the base
|
|
117
|
+
wait in seconds between attempts):
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
client = Client(merchant_id='<your_payway_merchant_id>',
|
|
121
|
+
bank_account_id='<your_payway_bank_account_id>',
|
|
122
|
+
publishable_api_key='<your_payway_publishable_api_key>',
|
|
123
|
+
secret_api_key='<your_payway_secret_api_key>',
|
|
124
|
+
max_retries=2,
|
|
125
|
+
retry_delay=1.0)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This follows PayWay's retry guidance (<https://www.payway.com.au/docs/rest.html#network-errors>):
|
|
129
|
+
|
|
130
|
+
- Requests are resent on connection errors, timeouts and HTTP 429/503 responses, waiting
|
|
131
|
+
`retry_delay` seconds between attempts (linear backoff, or the response's `Retry-After`
|
|
132
|
+
header when present). PayWay suggests a 20 second wait; keep `retry_delay` small for
|
|
133
|
+
synchronous checkout flows.
|
|
134
|
+
- POSTs are only retried when an `idempotency_key` was supplied — the same
|
|
135
|
+
`Idempotency-Key` is resent so PayWay replays the original response instead of
|
|
136
|
+
processing a duplicate payment. POSTs without a key (and PUTs) are never retried.
|
|
137
|
+
GETs are always safe to retry.
|
|
138
|
+
- Other errors (including HTTP 500/502/504) are never retried, per PayWay's advice.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
transaction, errors = client.process_payment(payment, idempotency_key=str(uuid.uuid4()))
|
|
142
|
+
```
|
|
143
|
+
|
|
112
144
|
## Handling errors
|
|
113
145
|
|
|
114
146
|
Documented errors (such as 422 Unprocessable entity) are parsed into an PaymentError class that you can use in an customer error message.
|
|
@@ -212,12 +244,11 @@ Please follow PayWay's advice about reducing your risk of fraudulent transaction
|
|
|
212
244
|
## Running the project
|
|
213
245
|
|
|
214
246
|
```bash
|
|
215
|
-
uv
|
|
216
|
-
uv venv --python 3.8.19
|
|
217
|
-
source .venv/bin/activate
|
|
218
|
-
uv sync --extra dev
|
|
247
|
+
uv sync
|
|
219
248
|
```
|
|
220
249
|
|
|
250
|
+
uv provisions a suitable Python (3.11+) and installs the dev dependencies automatically.
|
|
251
|
+
|
|
221
252
|
## Testing
|
|
222
253
|
|
|
223
254
|
```bash
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from http import HTTPStatus
|
|
4
7
|
from logging import getLogger
|
|
5
8
|
from typing import Any
|
|
6
9
|
|
|
@@ -11,9 +14,11 @@ from payway.constants import (
|
|
|
11
14
|
CREDIT_CARD_PAYMENT_CHOICE,
|
|
12
15
|
CUSTOMER_URL,
|
|
13
16
|
PAYWAY_ERROR_RESPONSE_CODES,
|
|
17
|
+
RETRYABLE_STATUS_CODES,
|
|
14
18
|
TOKEN_NO_REDIRECT,
|
|
15
19
|
TRANSACTION_URL,
|
|
16
20
|
VALID_PAYMENT_METHOD_CHOICES,
|
|
21
|
+
PaymentMethod,
|
|
17
22
|
)
|
|
18
23
|
from payway.customers import CustomerRequest
|
|
19
24
|
from payway.exceptions import PaywayError
|
|
@@ -43,18 +48,22 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
43
48
|
secret_api_key = ""
|
|
44
49
|
publishable_api_key = ""
|
|
45
50
|
|
|
46
|
-
def __init__(
|
|
51
|
+
def __init__( # noqa: PLR0913
|
|
47
52
|
self,
|
|
48
53
|
merchant_id: str,
|
|
49
54
|
bank_account_id: str,
|
|
50
55
|
secret_api_key: str,
|
|
51
56
|
publishable_api_key: str,
|
|
57
|
+
max_retries: int = 0,
|
|
58
|
+
retry_delay: float = 1.0,
|
|
52
59
|
) -> None:
|
|
53
60
|
"""
|
|
54
61
|
:param merchant_id: PayWay Merchant ID
|
|
55
62
|
:param bank_account_id: PayWay Bank Account ID
|
|
56
63
|
:param secret_api_key: PayWay Secret APi Key
|
|
57
64
|
:param publishable_api_key: PayWay Publishable API Key
|
|
65
|
+
:param max_retries: retries per request on network errors and HTTP 429/503 (0 disables)
|
|
66
|
+
:param retry_delay: base seconds to wait between attempts (Retry-After header wins if present)
|
|
58
67
|
"""
|
|
59
68
|
self._validate_credentials(
|
|
60
69
|
merchant_id,
|
|
@@ -66,9 +75,11 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
66
75
|
self.bank_account_id = bank_account_id
|
|
67
76
|
self.secret_api_key = secret_api_key
|
|
68
77
|
self.publishable_api_key = publishable_api_key
|
|
78
|
+
self.max_retries = max_retries
|
|
79
|
+
self.retry_delay = retry_delay
|
|
69
80
|
session = requests.Session()
|
|
70
81
|
session.auth = (self.secret_api_key, "")
|
|
71
|
-
session.headers
|
|
82
|
+
session.headers["content-type"] = "application/x-www-form-urlencoded"
|
|
72
83
|
self.session = session
|
|
73
84
|
session_no_headers = requests.Session()
|
|
74
85
|
session_no_headers.auth = session.auth
|
|
@@ -95,7 +106,10 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
95
106
|
)
|
|
96
107
|
|
|
97
108
|
def get_request(self, endpoint: str) -> requests.Response:
|
|
98
|
-
return
|
|
109
|
+
return self._send_with_retries(
|
|
110
|
+
lambda: requests.get(url=endpoint, auth=(self.secret_api_key, ""), timeout=30),
|
|
111
|
+
can_retry=True,
|
|
112
|
+
)
|
|
99
113
|
|
|
100
114
|
def post_request(
|
|
101
115
|
self, endpoint: str, data: dict[str, Any], auth: tuple[str, str] | None = None, idempotency_key: str | None = None
|
|
@@ -109,9 +123,41 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
109
123
|
headers = {"content-type": "application/x-www-form-urlencoded"}
|
|
110
124
|
if idempotency_key:
|
|
111
125
|
headers["Idempotency-Key"] = idempotency_key
|
|
112
|
-
return
|
|
126
|
+
return self._send_with_retries(
|
|
127
|
+
lambda: requests.post(url=endpoint, auth=auth, data=data, headers=headers, timeout=30),
|
|
128
|
+
can_retry=bool(idempotency_key),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def _send_with_retries(self, send: Callable[[], requests.Response], can_retry: bool) -> requests.Response: # noqa: FBT001
|
|
132
|
+
"""
|
|
133
|
+
Resend on network errors and HTTP 429/503 per PayWay's retry guidance
|
|
134
|
+
https://www.payway.com.au/docs/rest.html#network-errors
|
|
135
|
+
Requests without an Idempotency-Key must pass can_retry=False: retrying
|
|
136
|
+
them could double-charge. Other statuses (including 500/502/504) are
|
|
137
|
+
returned as-is.
|
|
138
|
+
"""
|
|
139
|
+
retries = self.max_retries if can_retry else 0
|
|
140
|
+
for attempt in range(retries):
|
|
141
|
+
try:
|
|
142
|
+
response = send()
|
|
143
|
+
except (requests.ConnectionError, requests.Timeout) as exc:
|
|
144
|
+
logger.warning("PayWay request failed (%s), retrying", exc)
|
|
145
|
+
time.sleep(self._retry_wait(attempt, None))
|
|
146
|
+
continue
|
|
147
|
+
if response.status_code not in RETRYABLE_STATUS_CODES:
|
|
148
|
+
return response
|
|
149
|
+
logger.warning("PayWay responded %s, retrying", response.status_code)
|
|
150
|
+
time.sleep(self._retry_wait(attempt, response))
|
|
151
|
+
return send()
|
|
152
|
+
|
|
153
|
+
def _retry_wait(self, attempt: int, response: requests.Response | None) -> float:
|
|
154
|
+
retry_after = response.headers.get("Retry-After") if response is not None else None
|
|
155
|
+
if retry_after and retry_after.isdigit():
|
|
156
|
+
return float(retry_after)
|
|
157
|
+
return self.retry_delay * (attempt + 1)
|
|
113
158
|
|
|
114
159
|
def put_request(self, endpoint: str, data: dict[str, Any]) -> requests.Response:
|
|
160
|
+
# No Idempotency-Key is sent on PUTs, so they are never retried
|
|
115
161
|
return requests.put(
|
|
116
162
|
url=endpoint,
|
|
117
163
|
auth=(self.secret_api_key, ""),
|
|
@@ -121,30 +167,27 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
121
167
|
)
|
|
122
168
|
|
|
123
169
|
def create_token(
|
|
124
|
-
self, payway_obj: BankAccount | PayWayCard, payment_method: str, idempotency_key: str | None = None
|
|
170
|
+
self, payway_obj: BankAccount | PayWayCard, payment_method: PaymentMethod | str, idempotency_key: str | None = None
|
|
125
171
|
) -> tuple[TokenResponse | None, list[PaymentError] | None]:
|
|
126
172
|
"""
|
|
127
173
|
Creates a single use token for a Customer's payment setup (credit card or bank account)
|
|
128
174
|
:param payway_obj: object: one of model.PayWayCard or model.BankAccount object
|
|
129
|
-
:param payment_method: str: one of `card` or `direct_debit`
|
|
175
|
+
:param payment_method: PaymentMethod or str: one of `card` or `direct_debit`
|
|
130
176
|
:param idempotency_key: str: unique value to avoid duplicate POSTs
|
|
131
177
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
elif payment_method == "direct_debit":
|
|
136
|
-
payway_payment_method = BANK_ACCOUNT_PAYMENT_CHOICE
|
|
137
|
-
else:
|
|
178
|
+
try:
|
|
179
|
+
payment_method = PaymentMethod(payment_method)
|
|
180
|
+
except ValueError as exc:
|
|
138
181
|
valid_payment_method_choices = ", ".join(VALID_PAYMENT_METHOD_CHOICES)
|
|
139
182
|
raise PaywayError(
|
|
140
183
|
message=f"Invalid payment method. Must be one of {valid_payment_method_choices}",
|
|
141
184
|
code="INVALID_PAYMENT_METHOD",
|
|
142
|
-
)
|
|
143
|
-
data.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
185
|
+
) from exc
|
|
186
|
+
data = payway_obj.to_dict()
|
|
187
|
+
if payment_method is PaymentMethod.CARD:
|
|
188
|
+
data["paymentMethod"] = CREDIT_CARD_PAYMENT_CHOICE
|
|
189
|
+
else:
|
|
190
|
+
data["paymentMethod"] = BANK_ACCOUNT_PAYMENT_CHOICE
|
|
148
191
|
logger.info("Sending Create Token request to PayWay.")
|
|
149
192
|
response = self.post_request(
|
|
150
193
|
TOKEN_NO_REDIRECT,
|
|
@@ -155,7 +198,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
155
198
|
errors = self._validate_response(response)
|
|
156
199
|
if errors:
|
|
157
200
|
return None, errors
|
|
158
|
-
return TokenResponse
|
|
201
|
+
return TokenResponse.from_dict(response.json()), errors
|
|
159
202
|
|
|
160
203
|
def create_card_token(
|
|
161
204
|
self, card: PayWayCard, idempotency_key: str | None = None
|
|
@@ -164,7 +207,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
164
207
|
:param card: PayWayCard object represents a customer's credit card details
|
|
165
208
|
:param idempotency_key: str: unique value to avoid duplicate POSTs
|
|
166
209
|
"""
|
|
167
|
-
return self.create_token(card,
|
|
210
|
+
return self.create_token(card, PaymentMethod.CARD, idempotency_key=idempotency_key)
|
|
168
211
|
|
|
169
212
|
def create_bank_account_token(
|
|
170
213
|
self, bank_account: BankAccount, idempotency_key: str | None = None
|
|
@@ -176,7 +219,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
176
219
|
"""
|
|
177
220
|
return self.create_token(
|
|
178
221
|
bank_account,
|
|
179
|
-
|
|
222
|
+
PaymentMethod.DIRECT_DEBIT,
|
|
180
223
|
idempotency_key=idempotency_key,
|
|
181
224
|
)
|
|
182
225
|
|
|
@@ -210,7 +253,7 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
210
253
|
errors = self._validate_response(response)
|
|
211
254
|
if errors:
|
|
212
255
|
return None, errors
|
|
213
|
-
customer = PayWayCustomer
|
|
256
|
+
customer = PayWayCustomer.from_dict(response.json())
|
|
214
257
|
return customer, errors
|
|
215
258
|
|
|
216
259
|
def process_payment(
|
|
@@ -241,19 +284,19 @@ class Client(CustomerRequest, TransactionRequest):
|
|
|
241
284
|
http_error_msg = f"{response.status_code} Client Error: {response.reason} for url: {response.url}"
|
|
242
285
|
raise PaywayError(code=str(response.status_code), message=http_error_msg)
|
|
243
286
|
|
|
244
|
-
if response.status_code in [
|
|
245
|
-
return PaymentError
|
|
287
|
+
if response.status_code in [HTTPStatus.NOT_FOUND, HTTPStatus.UNPROCESSABLE_ENTITY]: # Documented PayWay errors in JSON
|
|
288
|
+
return PaymentError.from_dict(response.json())
|
|
246
289
|
|
|
247
|
-
if response.status_code ==
|
|
290
|
+
if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
|
|
248
291
|
try:
|
|
249
292
|
errors = response.json()
|
|
250
|
-
except json.JSONDecodeError:
|
|
293
|
+
except json.JSONDecodeError as exc:
|
|
251
294
|
raise PaywayError(
|
|
252
295
|
code=str(response.status_code),
|
|
253
296
|
message="Internal server error",
|
|
254
|
-
)
|
|
297
|
+
) from exc
|
|
255
298
|
# Documented PayWay server errors in JSON
|
|
256
|
-
payway_error = ServerError
|
|
299
|
+
payway_error = ServerError.from_dict(errors)
|
|
257
300
|
message = payway_error.to_message()
|
|
258
301
|
raise PaywayError(code=str(response.status_code), message=message)
|
|
259
302
|
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from http import HTTPStatus
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PaymentMethod(StrEnum):
|
|
8
|
+
CARD = "card"
|
|
9
|
+
DIRECT_DEBIT = "direct_debit"
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
PAYWAY_API_URL = "https://api.payway.com.au/rest/v1"
|
|
4
13
|
TOKEN_URL = PAYWAY_API_URL + "/single-use-tokens-redirect"
|
|
5
14
|
TRANSACTION_URL = PAYWAY_API_URL + "/transactions"
|
|
@@ -65,7 +74,10 @@ EFT_RESPONSE_CODES = {
|
|
|
65
74
|
"QS": "Transaction Successful",
|
|
66
75
|
"QT": "Invalid currency",
|
|
67
76
|
"QU": "Unknown Customer IP Address",
|
|
68
|
-
"QV":
|
|
77
|
+
"QV": (
|
|
78
|
+
"Invalid Original Order Number specified for Refund, Refund amount exceeds capture amount, "
|
|
79
|
+
"or Previous capture was not approved"
|
|
80
|
+
),
|
|
69
81
|
"QW": "Invalid Reference Number",
|
|
70
82
|
"QX": "Network Error has occurred",
|
|
71
83
|
"QY": "Card Type Not Accepted",
|
|
@@ -138,18 +150,20 @@ DIRECT_DEBIT_CHOICES = (
|
|
|
138
150
|
("remittanceProcessingService", "Remittance Processing Service"),
|
|
139
151
|
)
|
|
140
152
|
PAYMENT_METHOD_CHOICES = OTHER_PAYMENT_CHOICES + DIRECT_DEBIT_CHOICES
|
|
141
|
-
VALID_PAYMENT_METHOD_CHOICES = [
|
|
153
|
+
VALID_PAYMENT_METHOD_CHOICES = [method.value for method in PaymentMethod]
|
|
154
|
+
# PayWay advises resending with the same Idempotency-Key on 429/503 only
|
|
155
|
+
RETRYABLE_STATUS_CODES = frozenset({HTTPStatus.TOO_MANY_REQUESTS, HTTPStatus.SERVICE_UNAVAILABLE})
|
|
142
156
|
PAYWAY_ERROR_RESPONSE_CODES = [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
HTTPStatus.BAD_REQUEST,
|
|
158
|
+
HTTPStatus.UNAUTHORIZED,
|
|
159
|
+
HTTPStatus.FORBIDDEN,
|
|
160
|
+
HTTPStatus.METHOD_NOT_ALLOWED,
|
|
161
|
+
HTTPStatus.NOT_ACCEPTABLE,
|
|
162
|
+
HTTPStatus.PROXY_AUTHENTICATION_REQUIRED,
|
|
163
|
+
HTTPStatus.CONFLICT,
|
|
164
|
+
HTTPStatus.GONE,
|
|
165
|
+
HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
|
|
166
|
+
HTTPStatus.TOO_MANY_REQUESTS,
|
|
167
|
+
HTTPStatus.NOT_IMPLEMENTED,
|
|
168
|
+
HTTPStatus.SERVICE_UNAVAILABLE,
|
|
155
169
|
]
|