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 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