payfurl 1.0.0__py3-none-any.whl
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.
- payfurl/__init__.py +35 -0
- payfurl/_client.py +278 -0
- payfurl/errors.py +261 -0
- payfurl/models.py +1420 -0
- payfurl/py.typed +0 -0
- payfurl/resources.py +468 -0
- payfurl/webhooks.py +52 -0
- payfurl-1.0.0.dist-info/METADATA +287 -0
- payfurl-1.0.0.dist-info/RECORD +11 -0
- payfurl-1.0.0.dist-info/WHEEL +4 -0
- payfurl-1.0.0.dist-info/licenses/LICENSE +21 -0
payfurl/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""payFURL's Python SDK: the public REST API with an account's secret key.
|
|
2
|
+
|
|
3
|
+
from payfurl import PayfurlClient, Environment
|
|
4
|
+
from payfurl.models import NewChargeToken
|
|
5
|
+
|
|
6
|
+
client = PayfurlClient("sectest...-au", Environment.SANDBOX)
|
|
7
|
+
charge = client.charges.create_with_token(NewChargeToken(amount=Decimal("20.00"), currency="AUD", token=token))
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from . import models, webhooks
|
|
11
|
+
from ._client import Environment, PayfurlClient, __version__
|
|
12
|
+
from .errors import (
|
|
13
|
+
APIConnectionError,
|
|
14
|
+
APIError,
|
|
15
|
+
APITimeoutError,
|
|
16
|
+
AuthenticationError,
|
|
17
|
+
ErrorCode,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
PayfurlError,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"APIConnectionError",
|
|
24
|
+
"APIError",
|
|
25
|
+
"APITimeoutError",
|
|
26
|
+
"AuthenticationError",
|
|
27
|
+
"Environment",
|
|
28
|
+
"ErrorCode",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"PayfurlClient",
|
|
31
|
+
"PayfurlError",
|
|
32
|
+
"__version__",
|
|
33
|
+
"models",
|
|
34
|
+
"webhooks",
|
|
35
|
+
]
|
payfurl/_client.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
from datetime import UTC, date, datetime
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from enum import Enum, StrEnum
|
|
8
|
+
from importlib.metadata import version
|
|
9
|
+
from types import TracebackType
|
|
10
|
+
from typing import Any, Literal, TypeVar, cast
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
from pydantic import BaseModel, TypeAdapter
|
|
14
|
+
|
|
15
|
+
from . import resources
|
|
16
|
+
from .errors import APIConnectionError, APIError, APITimeoutError, AuthenticationError, NotFoundError
|
|
17
|
+
from .models import ApplicationVersion, RootStatus
|
|
18
|
+
|
|
19
|
+
__version__ = version("payfurl")
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("payfurl")
|
|
22
|
+
|
|
23
|
+
T = TypeVar("T")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Environment(StrEnum):
|
|
27
|
+
"""Which payFURL API a client calls. The key's region (its last part, such as `-au`) picks the regional host."""
|
|
28
|
+
|
|
29
|
+
#: The API on this machine (https://localhost:5001). TLS isn't verified: it serves a development certificate.
|
|
30
|
+
LOCAL = "local"
|
|
31
|
+
DEVELOPMENT = "development"
|
|
32
|
+
SANDBOX = "sandbox"
|
|
33
|
+
PRODUCTION = "production"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_HOSTS = {
|
|
37
|
+
Environment.LOCAL: "https://localhost:5001",
|
|
38
|
+
Environment.DEVELOPMENT: "https://develop-api{region}.payfurl.com",
|
|
39
|
+
Environment.SANDBOX: "https://sandbox-api{region}.payfurl.com",
|
|
40
|
+
Environment.PRODUCTION: "https://api{region}.payfurl.com",
|
|
41
|
+
}
|
|
42
|
+
_REGIONS = {"au"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _base_url(environment: Environment, key: str) -> str:
|
|
46
|
+
# A key ends in its account's region: sectest...-au. A region without a host of its own uses the global one.
|
|
47
|
+
region = key.rsplit("-", 1)[1].lower() if "-" in key else ""
|
|
48
|
+
return _HOSTS[environment].format(region=f"-{region}" if region in _REGIONS else "")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Auth = Literal["secret", "public"] | None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PayfurlClient:
|
|
55
|
+
"""The payFURL API with one account's keys.
|
|
56
|
+
|
|
57
|
+
client = PayfurlClient("sectest...-au", Environment.SANDBOX)
|
|
58
|
+
charge = client.charges.create_with_token(NewChargeToken(amount=Decimal("20.00"), currency="AUD", token=token))
|
|
59
|
+
|
|
60
|
+
The secret key calls every endpoint. A public key (`public_key=`) is needed only by `request(auth="public")`;
|
|
61
|
+
`info.providers()` takes either. Make one client per account and reuse it: it keeps connections open. Close it
|
|
62
|
+
with `close()`, or use it as a context manager.
|
|
63
|
+
|
|
64
|
+
GETs are retried `max_retries` times after a connection error, a timeout or a retryable API error, waiting
|
|
65
|
+
`retry_delay` seconds and doubling the wait each time. Nothing else is retried: the API has no idempotency keys,
|
|
66
|
+
so a retried charge could charge twice.
|
|
67
|
+
|
|
68
|
+
`http_client` sends the requests in place of the client's own (for proxies or event hooks); its TLS settings are
|
|
69
|
+
used as they are. `timeout` applies to every request either way.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
secret_key: str | None,
|
|
75
|
+
environment: Environment | str,
|
|
76
|
+
*,
|
|
77
|
+
public_key: str | None = None,
|
|
78
|
+
base_url: str | None = None,
|
|
79
|
+
timeout: float = 60.0,
|
|
80
|
+
max_retries: int = 2,
|
|
81
|
+
retry_delay: float = 0.5,
|
|
82
|
+
http_client: httpx.Client | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
if not (secret_key or "").strip() and not (public_key or "").strip():
|
|
85
|
+
raise ValueError("A payFURL secret key or public key is needed")
|
|
86
|
+
try:
|
|
87
|
+
environment = Environment(str(environment).lower())
|
|
88
|
+
except ValueError:
|
|
89
|
+
names = ", ".join(e.value for e in Environment)
|
|
90
|
+
raise ValueError(f"Unknown payFURL environment {environment!r}: use {names}") from None
|
|
91
|
+
self.secret_key = secret_key or None
|
|
92
|
+
self.public_key = public_key or None
|
|
93
|
+
self.environment = environment
|
|
94
|
+
self.base_url = (base_url or _base_url(environment, self.secret_key or self.public_key or "")).rstrip("/")
|
|
95
|
+
self.timeout = timeout
|
|
96
|
+
self.max_retries = max(0, max_retries)
|
|
97
|
+
self.retry_delay = retry_delay
|
|
98
|
+
self._owns_http = http_client is None
|
|
99
|
+
# TLS is verified unless the client is for the API on this machine, and never changed on a caller's client.
|
|
100
|
+
self._http = http_client or httpx.Client(verify=environment is not Environment.LOCAL)
|
|
101
|
+
|
|
102
|
+
self.batches = resources.Batches(self)
|
|
103
|
+
self.charges = resources.Charges(self)
|
|
104
|
+
self.customers = resources.Customers(self)
|
|
105
|
+
self.disputes = resources.Disputes(self)
|
|
106
|
+
self.info = resources.Info(self)
|
|
107
|
+
self.payment_links = resources.PaymentLinks(self)
|
|
108
|
+
self.payment_methods = resources.PaymentMethods(self)
|
|
109
|
+
self.providers = resources.Providers(self)
|
|
110
|
+
self.settlements = resources.Settlements(self)
|
|
111
|
+
self.subscriptions = resources.Subscriptions(self)
|
|
112
|
+
self.tokens = resources.Tokens(self)
|
|
113
|
+
self.transfers = resources.Transfers(self)
|
|
114
|
+
self.vaults = resources.Vaults(self)
|
|
115
|
+
self.webhook_subscriptions = resources.WebhookSubscriptions(self)
|
|
116
|
+
|
|
117
|
+
def root(self) -> RootStatus:
|
|
118
|
+
"""`GET /`: whether the API is up. Needs no key."""
|
|
119
|
+
return self._call("GET", "/", RootStatus, auth=None)
|
|
120
|
+
|
|
121
|
+
def version(self) -> ApplicationVersion:
|
|
122
|
+
"""`GET /version`: the API's version and region."""
|
|
123
|
+
return self._call("GET", "/version", ApplicationVersion, auth=None)
|
|
124
|
+
|
|
125
|
+
def request(
|
|
126
|
+
self,
|
|
127
|
+
method: str,
|
|
128
|
+
path: str,
|
|
129
|
+
*,
|
|
130
|
+
json: Any = None,
|
|
131
|
+
params: dict[str, str] | None = None,
|
|
132
|
+
auth: Auth = "secret",
|
|
133
|
+
headers: dict[str, str] | None = None,
|
|
134
|
+
) -> Any:
|
|
135
|
+
"""Advanced: calls an endpoint the SDK has no method for, with the client's keys, TLS, retries and errors.
|
|
136
|
+
|
|
137
|
+
Returns the response's JSON, decoded as `json.loads` does (`None` for an empty body), and raises the same
|
|
138
|
+
errors as the other methods. `auth` picks the key header: `"secret"`, `"public"` (needs `public_key`) or
|
|
139
|
+
`None`. Prefer the typed methods: an endpoint called this way may change without notice.
|
|
140
|
+
"""
|
|
141
|
+
response = self._send(method, path, json=json, params=params, auth=auth, headers=headers)
|
|
142
|
+
return response.json() if response.content.strip() else None
|
|
143
|
+
|
|
144
|
+
def close(self) -> None:
|
|
145
|
+
"""Closes the client's connections. A caller's `http_client` is left open."""
|
|
146
|
+
if self._owns_http:
|
|
147
|
+
self._http.close()
|
|
148
|
+
|
|
149
|
+
def __enter__(self) -> PayfurlClient:
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
def __exit__(
|
|
153
|
+
self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
|
|
154
|
+
) -> None:
|
|
155
|
+
self.close()
|
|
156
|
+
|
|
157
|
+
def _call(
|
|
158
|
+
self,
|
|
159
|
+
method: str,
|
|
160
|
+
path: str,
|
|
161
|
+
response_type: type[T],
|
|
162
|
+
*,
|
|
163
|
+
body: BaseModel | dict[str, Any] | None = None,
|
|
164
|
+
query: BaseModel | dict[str, Any] | None = None,
|
|
165
|
+
auth: Auth = "secret",
|
|
166
|
+
headers: dict[str, str] | None = None,
|
|
167
|
+
) -> T:
|
|
168
|
+
if isinstance(body, BaseModel):
|
|
169
|
+
json: Any = body.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
170
|
+
else:
|
|
171
|
+
json = None if body is None else {k: _json_value(v) for k, v in body.items() if v is not None}
|
|
172
|
+
params = _query(query.model_dump(by_alias=True, exclude_none=True) if isinstance(query, BaseModel) else query)
|
|
173
|
+
response = self._send(method, path, json=json, params=params, auth=auth, headers=headers)
|
|
174
|
+
return cast(T, _adapter(response_type).validate_json(response.content))
|
|
175
|
+
|
|
176
|
+
def _send(
|
|
177
|
+
self,
|
|
178
|
+
method: str,
|
|
179
|
+
path: str,
|
|
180
|
+
*,
|
|
181
|
+
json: Any,
|
|
182
|
+
params: dict[str, str] | None,
|
|
183
|
+
auth: Auth,
|
|
184
|
+
headers: dict[str, str] | None,
|
|
185
|
+
) -> httpx.Response:
|
|
186
|
+
method = method.upper()
|
|
187
|
+
sent = {"Accept": "application/json", "User-Agent": f"payfurl-python/{__version__}", **(headers or {})}
|
|
188
|
+
if auth is not None:
|
|
189
|
+
key = self.secret_key if auth == "secret" else self.public_key
|
|
190
|
+
if key is None:
|
|
191
|
+
raise ValueError(f"This client has no {auth} key")
|
|
192
|
+
sent["x-secretkey" if auth == "secret" else "x-publickey"] = key
|
|
193
|
+
|
|
194
|
+
# Only reads are retried: a second charge POST could charge twice.
|
|
195
|
+
retries = self.max_retries if method == "GET" else 0
|
|
196
|
+
attempt = 0
|
|
197
|
+
while True:
|
|
198
|
+
start = time.monotonic()
|
|
199
|
+
try:
|
|
200
|
+
try:
|
|
201
|
+
response = self._http.request(
|
|
202
|
+
method, self.base_url + path, json=json, params=params, headers=sent, timeout=self.timeout
|
|
203
|
+
)
|
|
204
|
+
except httpx.TimeoutException as e:
|
|
205
|
+
raise APITimeoutError(f"{method} {path} timed out after {self.timeout} s") from e
|
|
206
|
+
except httpx.TransportError as e:
|
|
207
|
+
raise APIConnectionError(f"{method} {path} failed: {e}") from e
|
|
208
|
+
# Never the body or headers: they hold card numbers and keys.
|
|
209
|
+
logger.debug(
|
|
210
|
+
"%s %s: HTTP %s in %.0f ms", method, path, response.status_code, (time.monotonic() - start) * 1000
|
|
211
|
+
)
|
|
212
|
+
if response.is_success:
|
|
213
|
+
return response
|
|
214
|
+
raise _error(response)
|
|
215
|
+
except (APIConnectionError, APIError) as e:
|
|
216
|
+
if attempt >= retries or (isinstance(e, APIError) and not e.is_retryable):
|
|
217
|
+
raise
|
|
218
|
+
logger.debug("%s %s: retrying after %s", method, path, e)
|
|
219
|
+
time.sleep(self.retry_delay * 2**attempt)
|
|
220
|
+
attempt += 1
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _error(response: httpx.Response) -> APIError:
|
|
224
|
+
try:
|
|
225
|
+
body: Any = response.json()
|
|
226
|
+
except ValueError:
|
|
227
|
+
body = response.text.strip() or None
|
|
228
|
+
if isinstance(body, dict) and isinstance(body.get("message"), str):
|
|
229
|
+
message = body["message"]
|
|
230
|
+
elif isinstance(body, str) and body:
|
|
231
|
+
# Some endpoints answer with a plain message, and a proxy with HTML.
|
|
232
|
+
message = body[:500]
|
|
233
|
+
else:
|
|
234
|
+
message = f"HTTP {response.status_code} from the payFURL API"
|
|
235
|
+
status = response.status_code
|
|
236
|
+
cls = AuthenticationError if status in (401, 403) else NotFoundError if status == 404 else APIError
|
|
237
|
+
return cls(message, status_code=status, body=body)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _json_value(value: Any) -> Any:
|
|
241
|
+
if isinstance(value, Decimal):
|
|
242
|
+
return float(value)
|
|
243
|
+
if isinstance(value, Enum):
|
|
244
|
+
return value.value
|
|
245
|
+
return value
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _query(values: dict[str, Any] | None) -> dict[str, str] | None:
|
|
249
|
+
"""Query parameters as the API's model binding reads them; `None`s left out."""
|
|
250
|
+
if not values:
|
|
251
|
+
return None
|
|
252
|
+
query = {}
|
|
253
|
+
for name, value in values.items():
|
|
254
|
+
if value is None:
|
|
255
|
+
continue
|
|
256
|
+
if isinstance(value, bool):
|
|
257
|
+
query[name] = "true" if value else "false"
|
|
258
|
+
elif isinstance(value, Decimal):
|
|
259
|
+
query[name] = format(value, "f") # never an exponent: 1E+2
|
|
260
|
+
elif isinstance(value, datetime):
|
|
261
|
+
# A naive datetime is local time, as datetime.astimezone() takes it.
|
|
262
|
+
query[name] = value.astimezone(UTC).isoformat().replace("+00:00", "Z")
|
|
263
|
+
elif isinstance(value, date):
|
|
264
|
+
query[name] = value.isoformat()
|
|
265
|
+
elif isinstance(value, Enum):
|
|
266
|
+
query[name] = str(value.value)
|
|
267
|
+
else:
|
|
268
|
+
query[name] = str(value)
|
|
269
|
+
return query
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
_adapters: dict[Any, TypeAdapter[Any]] = {}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _adapter(response_type: Any) -> TypeAdapter[Any]:
|
|
276
|
+
if response_type not in _adapters:
|
|
277
|
+
_adapters[response_type] = TypeAdapter(response_type)
|
|
278
|
+
return _adapters[response_type]
|
payfurl/errors.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""The exceptions the SDK raises. Catch `PayfurlError` for all of them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import IntEnum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PayfurlError(Exception):
|
|
10
|
+
"""Every error the SDK raises."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class APIError(PayfurlError):
|
|
14
|
+
"""The API answered with an error status: a decline, a validation error, a conflict, a server error.
|
|
15
|
+
|
|
16
|
+
The API's error body (services/api `Error`) is in `body`, and its fields are attributes. `error_code` is the
|
|
17
|
+
payFURL error code (https://docs.payfurl.com/errorcodes). `is_retryable` says whether the same request may
|
|
18
|
+
succeed later.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str, *, status_code: int, body: Any = None) -> None:
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.message = message
|
|
24
|
+
self.status_code = status_code
|
|
25
|
+
self.body = body
|
|
26
|
+
error = body if isinstance(body, dict) else {}
|
|
27
|
+
code = error.get("code")
|
|
28
|
+
self.error_code: int | None = code if isinstance(code, int) else None
|
|
29
|
+
retryable = error.get("isRetryable")
|
|
30
|
+
# A body that isn't the API's (a load balancer's 502, say): worth retrying only if the server failed.
|
|
31
|
+
self.is_retryable: bool = retryable if isinstance(retryable, bool) else status_code >= 500 or status_code == 429
|
|
32
|
+
self.type: str | None = error.get("type")
|
|
33
|
+
self.resource: str | None = error.get("resource")
|
|
34
|
+
self.details: dict[str, str] | None = error.get("details")
|
|
35
|
+
self.gateway_code: str | None = error.get("gatewayCode")
|
|
36
|
+
self.gateway_message: str | None = error.get("gatewayMessage")
|
|
37
|
+
self.charge_id: str | None = error.get("chargeId")
|
|
38
|
+
self.trace_id: str | None = error.get("traceId")
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
code = f", error code {self.error_code}" if self.error_code is not None else ""
|
|
42
|
+
return f"{self.message} (HTTP {self.status_code}{code})"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AuthenticationError(APIError):
|
|
46
|
+
"""HTTP 401 or 403: the key is wrong, or can't call this endpoint (a public key, say)."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NotFoundError(APIError):
|
|
50
|
+
"""HTTP 404: no such charge, customer, ... on this account."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class APIConnectionError(PayfurlError):
|
|
54
|
+
"""The request didn't get a response: DNS, connection refused, TLS, a dropped connection."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class APITimeoutError(APIConnectionError):
|
|
58
|
+
"""The request took longer than the client's `timeout`."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ErrorCode(IntEnum):
|
|
62
|
+
"""The payFURL error codes, in `APIError.error_code`: `ErrorCode(exc.error_code)` names one.
|
|
63
|
+
|
|
64
|
+
https://docs.payfurl.com/errorcodes lists what each means. A code added to the API after this version of the SDK
|
|
65
|
+
is not a member: `ErrorCode(n)` raises `ValueError` for it, so compare `exc.error_code` with the members.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
NO_ERROR = 0
|
|
69
|
+
UNKNOWN_ERROR = 1
|
|
70
|
+
INVALID_AMOUNT = 2
|
|
71
|
+
INVALID_AUTHORISE_DETAILS = 3
|
|
72
|
+
INVALID_CAPTURE_DETAILS = 4
|
|
73
|
+
INVALID_CARD_NUMBER = 5
|
|
74
|
+
INVALID_CCV = 6
|
|
75
|
+
INVALID_COUNTRY = 7
|
|
76
|
+
INVALID_COUNTRY_FOR_TRANSFER = 8
|
|
77
|
+
INVALID_EXPIRY_DATE = 9
|
|
78
|
+
INVALID_PAYMENT_DETAILS = 10
|
|
79
|
+
INVALID_PAYPAL_ACCOUNT = 11
|
|
80
|
+
INVALID_REFUND_DETAILS = 12
|
|
81
|
+
INVALID_VOID_DETAILS = 13
|
|
82
|
+
EXPIRED_CARD = 14
|
|
83
|
+
INSUFFICIENT_FUNDS = 15
|
|
84
|
+
LOCATION_NOT_SUPPORTED = 16
|
|
85
|
+
PAYPAL_BILLING_AGREEMENT_CANCELLED = 17
|
|
86
|
+
THREE_D_SECURE_FAILED = 18
|
|
87
|
+
ACCOUNT_EMAIL_UNCONFIRMED = 19
|
|
88
|
+
ACCOUNT_ERROR = 20
|
|
89
|
+
ACCOUNT_IS_LOCKED_OR_CLOSED = 21
|
|
90
|
+
ACCOUNT_NOT_AUTHORISED = 22
|
|
91
|
+
ACCOUNT_RESTRICTED = 23
|
|
92
|
+
AMOUNT_EXCEEDS_MAX_AMOUNT_FOR_PAY_TO_AGREEMENT = 24
|
|
93
|
+
AMOUNT_TOO_LARGE = 25
|
|
94
|
+
AUTHENTICATION_AUTHORIZATION_ERROR = 26
|
|
95
|
+
BILLING_POSTAL_CODE_NOT_SUPPORTED = 27
|
|
96
|
+
CURRENCY_COMPLIANCE_ISSUES_WITH_TRANSACTIONS = 28
|
|
97
|
+
CURRENCY_NOT_SUPPORTED = 29
|
|
98
|
+
DUPLICATE = 30
|
|
99
|
+
DUPLICATE_TRANSACTION = 31
|
|
100
|
+
FEATURE_UNAVAILABLE = 32
|
|
101
|
+
PAY_TO_AGREEMENT_IS_NOT_ACTIVE = 33
|
|
102
|
+
PROVIDER_SERVICE_ERROR = 34
|
|
103
|
+
RATE_LIMITING_APPLIED = 35
|
|
104
|
+
RECEIVER_ACCOUNT_LOCKED = 36
|
|
105
|
+
RETRY_LATER = 37
|
|
106
|
+
SENDER_ACCOUNT_ISSUE = 38
|
|
107
|
+
INVALID_PROVIDER_ID = 39
|
|
108
|
+
INVALID_AUTHENTICATION = 40
|
|
109
|
+
INVALID_ENVIRONMENT = 41
|
|
110
|
+
TOKENISE_CARD_ERROR = 42
|
|
111
|
+
TRANSACTION_ERROR = 43
|
|
112
|
+
NOT_SUPPORT_CHARGE = 44
|
|
113
|
+
UNABLE_TO_REFUND = 45
|
|
114
|
+
UNABLE_TO_CHARGE = 46
|
|
115
|
+
UNABLE_TO_CAPTURE = 47
|
|
116
|
+
UNABLE_TO_VOID = 48
|
|
117
|
+
UNABLE_TO_ADD_CUSTOMER = 49
|
|
118
|
+
NO_TRANSACTION_FOUND = 50
|
|
119
|
+
INVALID_OR_EXPIRED_TOKEN = 51
|
|
120
|
+
INVALID_CUSTOMER_ID = 52
|
|
121
|
+
INVALID_CHARGE = 53
|
|
122
|
+
INVALID_CHARGE_STATUS = 54
|
|
123
|
+
INVALID_CARD_LENGTH = 55
|
|
124
|
+
INVALID_PAYMENT_METHOD_ID = 56
|
|
125
|
+
INVALID_PAYMENT_METHOD = 57
|
|
126
|
+
INVALID_PAYMENT_TYPE = 58
|
|
127
|
+
INVALID_THREE_DS_SERVER_TRANS_ID = 59
|
|
128
|
+
INVALID_TRANSFER_AMOUNT = 60
|
|
129
|
+
INVALID_REFERENCE_ID = 61
|
|
130
|
+
INVALID_VAULT_ID = 62
|
|
131
|
+
NO_CARD_PROVIDERS_CONFIGURED = 63
|
|
132
|
+
CUSTOMER_HAS_NO_DEFAULT_PAYMENT_METHOD_ID = 64
|
|
133
|
+
NO_PROVIDER_FOUND = 65
|
|
134
|
+
NO_VAULT_ID_ASSOCIATED_WITH_THIS_PAYMENT_METHOD = 66
|
|
135
|
+
NO_TRANSFER_INFORMATION_PROVIDED = 67
|
|
136
|
+
TRANSFER_LARGER_THAN_ORIGINAL_CHARGE = 68
|
|
137
|
+
DUPLICATE_GROUP_REFERENCE = 69
|
|
138
|
+
DUPLICATE_TRANSFER_REFERENCE = 70
|
|
139
|
+
FEATURE_AUTHORISE_AND_CAPTURE_IS_NOT_SUPPORTED = 71
|
|
140
|
+
INVALID_CUSTOMER_ID_REMOVED = 72
|
|
141
|
+
INVALID_PAYMENT_METHOD_ID_REMOVED = 73
|
|
142
|
+
INVALID_AMOUNT_MUST_BE_POSITIVE = 74
|
|
143
|
+
FEATURE_VERIFY_PENDING_IS_NOT_SUPPORTED = 75
|
|
144
|
+
INVALID_AMOUNT_MAXIMUM_AVAILABLE_REFUND = 76
|
|
145
|
+
INVALID_CAPTURE_AMOUNT_PASSED = 77
|
|
146
|
+
FEATURE_PARTIAL_CAPTURE_IS_NOT_SUPPORTED_CAPTURE_AMOUNT = 78
|
|
147
|
+
AMOUNT_OUTSIDE_RANGE_FOR_MERCHANT = 79
|
|
148
|
+
GATEWAY_TIMEOUT = 80
|
|
149
|
+
INVALID_PAYMENT_INFORMATION = 81
|
|
150
|
+
CANNOT_PROVIDE_BOTH_EXPIRE_DATE_AND_EXPIRE_SECONDS = 82
|
|
151
|
+
INVALID_EXPIRE_DATE_OR_EXPIRE_SECONDS = 83
|
|
152
|
+
INVALID_CHARGE_ID = 84
|
|
153
|
+
CANNOT_PROVIDE_BOTH_VAULT_EXPIRE_DATE_AND_VAULT_EXPIRE_SECONDS = 85
|
|
154
|
+
INVALID_VAULT_EXPIRY_DATE = 86
|
|
155
|
+
INVALID_TOKEN_ID = 87
|
|
156
|
+
INVALID_TRANSFER_ID = 88
|
|
157
|
+
INVALID_NOTIFICATION_FORMAT = 89
|
|
158
|
+
VALIDATION_ERROR = 90
|
|
159
|
+
TOKEN_VERIFICATION_NOT_SUPPORTED = 91
|
|
160
|
+
INVALID_ADDRESS = 92
|
|
161
|
+
CARD_BLOCKED = 93
|
|
162
|
+
TIMEOUT = 94
|
|
163
|
+
VIS_IS_NOT_SUPPORTED = 95
|
|
164
|
+
THE_ORDER_INFORMATION_IS_INVALID = 96
|
|
165
|
+
METADATA_DOES_NOT_CONTAIN_REQUIRED_FIELDS = 97
|
|
166
|
+
FRAUD_IS_NOT_SUPPORTED = 98
|
|
167
|
+
CARD_IS_NOT_ENROLLED_FOR_VIS = 99
|
|
168
|
+
PROVIDER_NOT_SUPPORTED = 100
|
|
169
|
+
INVALID_BATCH = 101
|
|
170
|
+
INVALID_BATCH_DATA = 102
|
|
171
|
+
DIRECT_DEBIT_NOT_SUPPORTED = 103
|
|
172
|
+
INVALID_SUBSCRIPTION_ID = 104
|
|
173
|
+
NO_DATA_TO_UPDATE_THE_PAYMENT_METHOD = 105
|
|
174
|
+
PAYMENT_METHOD_MUST_BE_OF_TYPE_CARD = 106
|
|
175
|
+
UNABLE_TO_UPDATE_THE_PAYMENT_METHOD = 107
|
|
176
|
+
UNABLE_TO_UPDATE_SUBSCRIPTION = 108
|
|
177
|
+
UNABLE_TO_CREATE_WEBHOOK_SUBSCRIPTION = 109
|
|
178
|
+
UNABLE_TO_GET_WEBHOOK_SUBSCRIPTION = 110
|
|
179
|
+
UNABLE_TO_DELETE_WEBHOOK_SUBSCRIPTION = 111
|
|
180
|
+
BLOCKED_BY_FRAUD_RULES = 112
|
|
181
|
+
REQUIRE_3D_SECURE = 113
|
|
182
|
+
UNABLE_TO_CREATE_NETWORK_TOKEN = 114
|
|
183
|
+
UNABLE_TO_CREATE_PAYMENT_LINK = 115
|
|
184
|
+
INVALID_PAYMENT_LINK_ID = 116
|
|
185
|
+
INVALID_FALLBACK_PROVIDER_ID = 117
|
|
186
|
+
INVALID_DISPUTE_ID = 118
|
|
187
|
+
APPLE_PAY_UNKNOWN_PUBLIC_KEY_HASH = 119
|
|
188
|
+
APPLE_PAY_PLATFORM_MODE_UNSUPPORTED = 120
|
|
189
|
+
APPLE_PAY_TOKEN_VERIFICATION_FAILED = 121
|
|
190
|
+
APPLE_PAY_TOKEN_DECRYPTION_FAILED = 122
|
|
191
|
+
STOP_RECURRING_PAYMENTS = 123
|
|
192
|
+
REFER_TO_CARD_ISSUER = 901
|
|
193
|
+
REFER_TO_CARD_ISSUER_SPECIAL_CONDITIONS = 902
|
|
194
|
+
INVALID_MERCHANT = 903
|
|
195
|
+
PICK_UP_CARD = 904
|
|
196
|
+
DO_NOT_HONOR = 905
|
|
197
|
+
ERROR = 906
|
|
198
|
+
PICK_UP_CARD_SPECIAL_CONDITION = 907
|
|
199
|
+
HONOUR_WITH_IDENTIFICATION = 908
|
|
200
|
+
REQUEST_IN_PROGRESS = 909
|
|
201
|
+
APPROVED_FOR_PARTIAL_AMOUNT = 910
|
|
202
|
+
APPROVED_VIP = 911
|
|
203
|
+
INVALID_TRANSACTION = 912
|
|
204
|
+
ISSUER_INVALID_AMOUNT = 913
|
|
205
|
+
INVALID_CARD_NUMBER_NO_SUCH_NUMBER = 914
|
|
206
|
+
NO_SUCH_ISSUER = 915
|
|
207
|
+
APPROVED_UPDATE_TRACK3 = 916
|
|
208
|
+
CUSTOMER_CANCELLATION = 917
|
|
209
|
+
CUSTOMER_DISPUTE = 918
|
|
210
|
+
RE_ENTER_TRANSACTION = 919
|
|
211
|
+
INVALID_RESPONSE = 920
|
|
212
|
+
NO_ACTION_TAKEN = 921
|
|
213
|
+
SUSPECTED_MALFUNCTION = 922
|
|
214
|
+
UNACCEPTABLE_TRANSACTION_FEE = 923
|
|
215
|
+
FILE_UPDATE_NOT_SUPPORTED_BY_RECEIVER = 924
|
|
216
|
+
UNABLE_TO_LOCATE_RECORD_ON_FILE = 925
|
|
217
|
+
DUPLICATE_FILE_UPDATE_RECORD_OLD_RECORD_REPLACED = 926
|
|
218
|
+
FILE_UPDATE_FIELD_EDIT_ERROR = 927
|
|
219
|
+
FILE_UPDATE_FILE_LOCKED_OUT = 928
|
|
220
|
+
FILE_UPDATE_NOT_SUCCESSFUL_CONTACT_ACQUIRER = 929
|
|
221
|
+
FORMAT_ERROR = 930
|
|
222
|
+
BANK_NOT_SUPPORTED_BY_SWITCH = 931
|
|
223
|
+
COMPLETED_PARTIALLY = 932
|
|
224
|
+
EXPIRED_CARD_PICK_UP = 933
|
|
225
|
+
SUSPECTED_FRAUD_PICK_UP = 934
|
|
226
|
+
CARD_ACCEPTOR_CONTACT_ACQUIRER_PICK_UP = 935
|
|
227
|
+
RESTRICTED_CARD_PICK_UP = 936
|
|
228
|
+
CARD_ACCEPTOR_CALL_ACQUIRER_SECURITY_PICK_UP = 937
|
|
229
|
+
ALLOWABLE_PIN_TRIES_EXCEEDED = 938
|
|
230
|
+
NO_CREDIT_ACCOUNT = 939
|
|
231
|
+
REQUESTED_FUNCTION_NOT_SUPPORTED = 940
|
|
232
|
+
LOST_CARD_PICK_UP = 941
|
|
233
|
+
NO_UNIVERSAL_ACCOUNT = 942
|
|
234
|
+
STOLEN_CARD_PICK_UP = 943
|
|
235
|
+
NO_INVESTMENT_ACCOUNT = 944
|
|
236
|
+
ISSUER_INSUFFICIENT_FUNDS = 951
|
|
237
|
+
NO_CHECKING_ACCOUNT = 952
|
|
238
|
+
NO_SAVINGS_ACCOUNT = 953
|
|
239
|
+
ISSUER_EXPIRED_CARD = 954
|
|
240
|
+
INCORRECT_PERSONAL_IDENTIFICATION_NUMBER = 955
|
|
241
|
+
NO_CARD_RECORD = 956
|
|
242
|
+
TRANSACTION_NOT_PERMITTED_TO_CARDHOLDER = 957
|
|
243
|
+
TRANSACTION_NOT_PERMITTED_TO_TERMINAL = 958
|
|
244
|
+
SUSPECTED_FRAUD = 959
|
|
245
|
+
CARD_ACCEPTOR_CONTACT_ACQUIRER = 960
|
|
246
|
+
EXCEEDS_WITHDRAWAL_AMOUNT_LIMIT = 961
|
|
247
|
+
RESTRICTED_CARD = 962
|
|
248
|
+
SECURITY_VIOLATION = 963
|
|
249
|
+
ORIGINAL_AMOUNT_INCORRECT = 964
|
|
250
|
+
EXCEEDS_WITHDRAWAL_FREQUENCY_LIMIT = 965
|
|
251
|
+
CARD_ACCEPTOR_CALL_ACQUIRERS_SECURITY_DEPARTMENT = 966
|
|
252
|
+
HARD_CAPTURE_REQUIRES_CARD_PICK_UP_AT_ATM = 967
|
|
253
|
+
RESPONSE_RECEIVED_TOO_LATE = 968
|
|
254
|
+
ALLOWABLE_NUMBER_OF_PIN_TRIES_EXCEEDED = 975
|
|
255
|
+
CUTOFF_IS_IN_PROCESS = 990
|
|
256
|
+
ISSUER_OR_SWITCH_IS_INOPERATIVE = 991
|
|
257
|
+
FINANCIAL_INSTITUTION_OR_INTERMEDIATE_NETWORK_FACILITY_CANNOT_BE_FOUND_FOR_ROUTING = 992
|
|
258
|
+
TRANSACTION_CANNOT_BE_COMPLETED_VIOLATION_OF_LAW = 993
|
|
259
|
+
DUPLICATE_TRANSMISSION = 994
|
|
260
|
+
RECONCILE_ERROR = 995
|
|
261
|
+
SYSTEM_MALFUNCTION = 996
|