glaring 0.2__tar.gz → 0.3__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.
- glaring-0.3/MANIFEST.in +3 -0
- {glaring-0.2/glaring.egg-info → glaring-0.3}/PKG-INFO +1 -1
- glaring-0.3/glaring/svs/__init__.py +0 -0
- glaring-0.3/glaring/svs/async_rest.py +431 -0
- glaring-0.3/glaring/svs/hello.py +2 -0
- glaring-0.3/glaring/svs/response_codes.py +43 -0
- glaring-0.3/glaring/svs/rest.py +512 -0
- glaring-0.3/glaring/svs/soap.py +484 -0
- glaring-0.3/glaring/version.txt +1 -0
- {glaring-0.2 → glaring-0.3/glaring.egg-info}/PKG-INFO +1 -1
- {glaring-0.2 → glaring-0.3}/glaring.egg-info/SOURCES.txt +7 -1
- {glaring-0.2 → glaring-0.3}/setup.py +3 -2
- glaring-0.2/MANIFEST.in +0 -3
- glaring-0.2/glaring/version.txt +0 -1
- {glaring-0.2 → glaring-0.3}/LICENSE +0 -0
- {glaring-0.2 → glaring-0.3}/README.md +0 -0
- {glaring-0.2 → glaring-0.3}/dev.txt +0 -0
- {glaring-0.2 → glaring-0.3}/glaring/__init__.py +0 -0
- {glaring-0.2 → glaring-0.3}/glaring.egg-info/dependency_links.txt +0 -0
- {glaring-0.2 → glaring-0.3}/glaring.egg-info/requires.txt +0 -0
- {glaring-0.2 → glaring-0.3}/glaring.egg-info/top_level.txt +0 -0
- {glaring-0.2 → glaring-0.3}/pyproject.toml +0 -0
- {glaring-0.2 → glaring-0.3}/setup.cfg +0 -0
glaring-0.3/MANIFEST.in
ADDED
|
File without changes
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
from base64 import b64encode, b64decode
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from hashlib import sha1
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from threading import Lock
|
|
7
|
+
from cryptography.hazmat.primitives import padding as sym_padding
|
|
8
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
9
|
+
from deceit.async_api_client import ApiClient
|
|
10
|
+
from raft.tasks import task
|
|
11
|
+
from httpx import Client
|
|
12
|
+
from pytz import utc
|
|
13
|
+
from .rest import CsmCrypto, Stan
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsyncRestApi(ApiClient):
|
|
17
|
+
def __init__(self, base_url, auth_id, root_key, username, password, merchant_id, store_number=None):
|
|
18
|
+
super().__init__(base_url=base_url)
|
|
19
|
+
self.base_url = base_url
|
|
20
|
+
self.auth_id = auth_id
|
|
21
|
+
self.root_key = root_key
|
|
22
|
+
self.username = username
|
|
23
|
+
self.password = password
|
|
24
|
+
self.merchant_id = int(merchant_id)
|
|
25
|
+
self.root_crypto = CsmCrypto(root_key)
|
|
26
|
+
self.crypto = None
|
|
27
|
+
self.store_number = store_number
|
|
28
|
+
self.kyn = None
|
|
29
|
+
self.active_key = None
|
|
30
|
+
self.encrypted_auth_message = None
|
|
31
|
+
self.tvb = None
|
|
32
|
+
self.session_id = None
|
|
33
|
+
|
|
34
|
+
def build_csm(self, **kwargs):
|
|
35
|
+
csm = (
|
|
36
|
+
f'CSM(MCL/SMA RCV/Comdata ORG/{self.merchant_id} TVB/{self.tvb} TVA/d9b94d17828b2853 '
|
|
37
|
+
f'SID/{self.session_id} USR/{self.username} PWD/{self.password})'
|
|
38
|
+
)
|
|
39
|
+
return csm
|
|
40
|
+
|
|
41
|
+
def headers(self, **kwargs):
|
|
42
|
+
if not self.session_id:
|
|
43
|
+
return {}
|
|
44
|
+
csm = self.build_csm()
|
|
45
|
+
auth_message = self.crypto.encrypt(csm)
|
|
46
|
+
# this is stupid but important -- httpx wants header keys
|
|
47
|
+
# to be all lowercase. if you set `Content-Type`
|
|
48
|
+
# and then somewhere else set `content-type`, you will
|
|
49
|
+
# get duplicated conent-types in your headers: e.g.,
|
|
50
|
+
# content-type: application/json, application/json
|
|
51
|
+
return {
|
|
52
|
+
'content-type': 'application/json',
|
|
53
|
+
'auth-id': self.auth_id,
|
|
54
|
+
'auth-message': auth_message,
|
|
55
|
+
'accept': 'application/json',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async def authenticate_root(self):
|
|
59
|
+
# Step 1: Get KEX from SVS
|
|
60
|
+
route = 'oltp-gift/authenticate/root'
|
|
61
|
+
headers = {
|
|
62
|
+
'Auth-Id': self.auth_id,
|
|
63
|
+
'Name': 'root',
|
|
64
|
+
'Key': self.root_key,
|
|
65
|
+
}
|
|
66
|
+
response = await self.get(route, headers=headers, raw=True)
|
|
67
|
+
headers = response.headers
|
|
68
|
+
auth_message = headers.get('Auth-Message')
|
|
69
|
+
auth_message = self.root_crypto.decrypt(auth_message)
|
|
70
|
+
csm = self.parse_csm(auth_message)
|
|
71
|
+
self.kyn = csm.get('kyn')
|
|
72
|
+
self.active_key = csm.get('key')
|
|
73
|
+
self.crypto = CsmCrypto(self.active_key)
|
|
74
|
+
self.encrypted_auth_message = self.crypto.encrypt(auth_message)
|
|
75
|
+
return response
|
|
76
|
+
|
|
77
|
+
# async def presend(self, request):
|
|
78
|
+
# body = request.content.decode()
|
|
79
|
+
# if body:
|
|
80
|
+
# body = json.loads(body)
|
|
81
|
+
# body = json.dumps(body, indent=2)
|
|
82
|
+
# self.log.info(f'presend: {request.method} {request.url}')
|
|
83
|
+
# self.log.info('body=%s', body)
|
|
84
|
+
# headers = request.headers
|
|
85
|
+
# headers = json.dumps(dict(headers), indent=2)
|
|
86
|
+
# self.log.info('headers=%s', headers)
|
|
87
|
+
|
|
88
|
+
async def postsend(self, request, response):
|
|
89
|
+
headers = response.headers
|
|
90
|
+
if 'Auth-Message' in headers:
|
|
91
|
+
if not self.crypto:
|
|
92
|
+
return
|
|
93
|
+
auth_message = headers.get('Auth-Message')
|
|
94
|
+
auth_message = self.crypto.decrypt(auth_message)
|
|
95
|
+
# print(f'received csm: {auth_message}')
|
|
96
|
+
csm = self.parse_csm(auth_message)
|
|
97
|
+
self.tvb = csm.get('tvb')
|
|
98
|
+
|
|
99
|
+
async def establish_session(self):
|
|
100
|
+
"""
|
|
101
|
+
establishes a session with svs
|
|
102
|
+
"""
|
|
103
|
+
route = f'oltp-gift/authenticate/{self.kyn}'
|
|
104
|
+
headers = {
|
|
105
|
+
'Auth-Id': self.auth_id,
|
|
106
|
+
'Name': self.kyn,
|
|
107
|
+
'Key': self.active_key,
|
|
108
|
+
'Auth-Message': self.encrypted_auth_message,
|
|
109
|
+
}
|
|
110
|
+
response = await self.get(route, headers=headers, raw=True)
|
|
111
|
+
headers = response.headers
|
|
112
|
+
auth_message = headers.get('Auth-Message')
|
|
113
|
+
auth_message = self.crypto.decrypt(auth_message)
|
|
114
|
+
# print(auth_message)
|
|
115
|
+
csm = self.parse_csm(auth_message)
|
|
116
|
+
self.tvb = csm.get('tvb')
|
|
117
|
+
self.session_id = csm.get('sid')
|
|
118
|
+
return response
|
|
119
|
+
|
|
120
|
+
async def post_it(self, route, payload, retry=False, retry_403=False):
|
|
121
|
+
"""
|
|
122
|
+
we do a lot of generic processing for every call -- handle it here
|
|
123
|
+
instead.
|
|
124
|
+
"""
|
|
125
|
+
payload = self.base_payload(**payload)
|
|
126
|
+
stan = payload['stan']
|
|
127
|
+
headers = None
|
|
128
|
+
if retry:
|
|
129
|
+
# only use the retry flag when recovering from a
|
|
130
|
+
# connect timeout
|
|
131
|
+
headers = { 'Retry': 'Y', }
|
|
132
|
+
response = await self.post(route, json_data=payload, headers=headers, raw=True)
|
|
133
|
+
if response.status_code == 403 and not retry_403:
|
|
134
|
+
# if we immediately get a 403 response, svs
|
|
135
|
+
# expects us to retry with the updated tvb
|
|
136
|
+
response = await self.post_it(route, payload, retry=retry, retry_403=True)
|
|
137
|
+
result = await self.handle_response(response)
|
|
138
|
+
if isinstance(result, dict):
|
|
139
|
+
result['stan'] = stan
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
async def balance_inquiry(self, data, enhanced=False, retry=False):
|
|
143
|
+
"""
|
|
144
|
+
performs a balance inquiry request to svs
|
|
145
|
+
expected payload:
|
|
146
|
+
{
|
|
147
|
+
'currency': 'USD',
|
|
148
|
+
'card_number': '600...2449',
|
|
149
|
+
'pin': '1234',
|
|
150
|
+
'invoice_number': '00000000',
|
|
151
|
+
}
|
|
152
|
+
"""
|
|
153
|
+
route = 'oltp-gift/v1/balance-inquiry'
|
|
154
|
+
if enhanced: # pragma: no cover
|
|
155
|
+
route = 'oltp-gift/v1/enhanced-balance-inquiry'
|
|
156
|
+
response = await self.post_it(route, data, retry=retry)
|
|
157
|
+
return response
|
|
158
|
+
|
|
159
|
+
def now(self):
|
|
160
|
+
return datetime.now(utc)
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def lpad(cls, st, n=10):
|
|
164
|
+
if len(st) < n:
|
|
165
|
+
l = '0' * (n - len(st))
|
|
166
|
+
return f'{l}{st}'
|
|
167
|
+
return st
|
|
168
|
+
|
|
169
|
+
def base_payload(
|
|
170
|
+
self,
|
|
171
|
+
card_number: str = None,
|
|
172
|
+
pin: str = '',
|
|
173
|
+
currency = 'USD',
|
|
174
|
+
amount = None,
|
|
175
|
+
invoice_number: str = '00000000',
|
|
176
|
+
stan: int = None,
|
|
177
|
+
) -> dict:
|
|
178
|
+
"""Builds the standard SVS request payload for card operations."""
|
|
179
|
+
now = self.now()
|
|
180
|
+
payload = {
|
|
181
|
+
'merchantId': self.merchant_id,
|
|
182
|
+
'storeNumber': self.lpad(self.store_number),
|
|
183
|
+
'invoiceNumber': self.lpad(invoice_number),
|
|
184
|
+
'stan': Stan.get(card_number) if not stan else '',
|
|
185
|
+
'currencyCode': currency,
|
|
186
|
+
'posTime': now.strftime('%H:%M:%S'),
|
|
187
|
+
'posDate': now.strftime('%Y-%m-%d'),
|
|
188
|
+
}
|
|
189
|
+
if card_number:
|
|
190
|
+
payload['cardNumber'] = card_number
|
|
191
|
+
if pin:
|
|
192
|
+
payload['pin'] = pin
|
|
193
|
+
if amount is not None:
|
|
194
|
+
payload['amount'] = amount
|
|
195
|
+
if stan is not None:
|
|
196
|
+
payload['stan'] = stan
|
|
197
|
+
return payload
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def parse_csm(cls, csm):
|
|
201
|
+
"""
|
|
202
|
+
Parses a CSM string into a dictionary.
|
|
203
|
+
|
|
204
|
+
Example input:
|
|
205
|
+
CSM(MCL/SMA RCV/Comdata ORG/10001095 TVB/abc TVA/def SID/xxx USR/u PWD/p)
|
|
206
|
+
|
|
207
|
+
Example output:
|
|
208
|
+
{
|
|
209
|
+
'MCL': 'SMA',
|
|
210
|
+
'RCV': 'Comdata',
|
|
211
|
+
'ORG': '10001095',
|
|
212
|
+
'TVB': 'abc',
|
|
213
|
+
'TVA': 'def',
|
|
214
|
+
'SID': 'xxx',
|
|
215
|
+
'USR': 'u',
|
|
216
|
+
'PWD': 'p'
|
|
217
|
+
}
|
|
218
|
+
"""
|
|
219
|
+
if not csm.startswith('CSM(') or not csm.endswith(')'): # pragma: no cover
|
|
220
|
+
raise ValueError('Invalid CSM format')
|
|
221
|
+
content = csm[4:-1] # Strip off 'CSM(' and ')'
|
|
222
|
+
parts = content.split()
|
|
223
|
+
result = {}
|
|
224
|
+
for part in parts:
|
|
225
|
+
key, value = part.split('/', 1)
|
|
226
|
+
result[key.lower()] = value
|
|
227
|
+
return result
|
|
228
|
+
|
|
229
|
+
async def reload(self, data, retry=False, **kwargs):
|
|
230
|
+
"""
|
|
231
|
+
reloads a card with svs
|
|
232
|
+
expected payload:
|
|
233
|
+
{
|
|
234
|
+
'currency': 'USD',
|
|
235
|
+
'card_number': '600...244',
|
|
236
|
+
'pin': '1234',
|
|
237
|
+
'amount': 25,
|
|
238
|
+
'invoice_number': '00000000',
|
|
239
|
+
}
|
|
240
|
+
"""
|
|
241
|
+
route = 'oltp-gift/v1/reload'
|
|
242
|
+
response = await self.post_it(route, data, retry=retry)
|
|
243
|
+
return response
|
|
244
|
+
|
|
245
|
+
async def issue(self, data, retry=False, **kwargs):
|
|
246
|
+
"""
|
|
247
|
+
initial activation of a **physical** gift card with a certain balance
|
|
248
|
+
"""
|
|
249
|
+
route = 'oltp-gift/v1/issue'
|
|
250
|
+
response = await self.post_it(route, data, retry=retry)
|
|
251
|
+
return response
|
|
252
|
+
|
|
253
|
+
async def preissue(self, data, retry=False, **kwargs):
|
|
254
|
+
"""
|
|
255
|
+
checks whether a card is eligible to be issued
|
|
256
|
+
"""
|
|
257
|
+
route = 'oltp-gift/v1/pre-issue'
|
|
258
|
+
response = await self.post_it(route, data, retry=retry)
|
|
259
|
+
return response
|
|
260
|
+
|
|
261
|
+
async def preactivate(self, data, retry=False, **kwargs): # pragma: no cover
|
|
262
|
+
"""
|
|
263
|
+
checks whether a card is eligible to be activated
|
|
264
|
+
"""
|
|
265
|
+
route = 'oltp-gift/v1/pre-activate'
|
|
266
|
+
response = await self.post_it(route, data, retry=retry)
|
|
267
|
+
return response
|
|
268
|
+
|
|
269
|
+
async def activate(self, data, retry=False, **kwargs): # pragma: no cover
|
|
270
|
+
"""
|
|
271
|
+
activates a fixed value (i.e., a conversion bin) card
|
|
272
|
+
"""
|
|
273
|
+
route = 'oltp-gift/v1/activate'
|
|
274
|
+
response = await self.post_it(route, data, retry=retry)
|
|
275
|
+
return response
|
|
276
|
+
|
|
277
|
+
async def issue_virtual(self, data, retry=False, **kwargs):
|
|
278
|
+
"""
|
|
279
|
+
issues a virtual gift card and pin from a pool allocated on svs
|
|
280
|
+
|
|
281
|
+
sample payload:
|
|
282
|
+
{
|
|
283
|
+
'invoice_number': '00004321',
|
|
284
|
+
'stan': 123456,
|
|
285
|
+
'amount': 12.50,
|
|
286
|
+
}
|
|
287
|
+
"""
|
|
288
|
+
route = 'oltp-gift/v1/issue-virtual'
|
|
289
|
+
response = await self.post_it(route, data, retry=retry)
|
|
290
|
+
return response
|
|
291
|
+
|
|
292
|
+
async def redeem(self, data, retry=False, **kwargs):
|
|
293
|
+
"""
|
|
294
|
+
This transaction is used when purchasing goods with a gift card.
|
|
295
|
+
|
|
296
|
+
sample payload:
|
|
297
|
+
{
|
|
298
|
+
'invoice_umber': '00004321',
|
|
299
|
+
'stan': 123456,
|
|
300
|
+
'amount': 12.50,
|
|
301
|
+
'card_number':'6006...123',
|
|
302
|
+
'pin':'0123',
|
|
303
|
+
}
|
|
304
|
+
"""
|
|
305
|
+
route = 'oltp-gift/v1/redeem'
|
|
306
|
+
response = await self.post_it(route, data, retry=retry)
|
|
307
|
+
return response
|
|
308
|
+
|
|
309
|
+
async def preauth(self, data, retry=False, **kwargs):
|
|
310
|
+
"""
|
|
311
|
+
Used to place a reserve on funds on a card for a fixed period of time or
|
|
312
|
+
until a completion message is approved. This message type should
|
|
313
|
+
always be followed by a Pre-Authorization Completion transaction.
|
|
314
|
+
|
|
315
|
+
The amount sent in the REQ should be an amount defined by the
|
|
316
|
+
client's POS. Amount of funds held is determined by:
|
|
317
|
+
|
|
318
|
+
If amount sent in the REQ is greater than the available funds,
|
|
319
|
+
the available funds on the card are held.
|
|
320
|
+
|
|
321
|
+
If the amount sent in the REQ is less than the available balance,
|
|
322
|
+
the requested amount is held.
|
|
323
|
+
|
|
324
|
+
sample payload:
|
|
325
|
+
{
|
|
326
|
+
'invoice_number': '00004321',
|
|
327
|
+
'stan':123456,
|
|
328
|
+
'amount': 12.50,
|
|
329
|
+
'card_number':'6006...123',
|
|
330
|
+
'pin':'0123',
|
|
331
|
+
'ssc':'0234',
|
|
332
|
+
'track1':'B6006...123^ ^4912110ABC',
|
|
333
|
+
'track2':'6006...123=491211012345678',
|
|
334
|
+
'auxiliaryData':'',
|
|
335
|
+
'cardType':''
|
|
336
|
+
}
|
|
337
|
+
"""
|
|
338
|
+
route = 'oltp-gift/v1/pre-authorization'
|
|
339
|
+
response = await self.post_it(route, data, retry=retry, **kwargs)
|
|
340
|
+
return response
|
|
341
|
+
|
|
342
|
+
async def completion(self, data, retry=False, **kwargs):
|
|
343
|
+
"""
|
|
344
|
+
This transaction is used remove the funds and holds placed by
|
|
345
|
+
the pre-authorization transaction These two transactions must
|
|
346
|
+
use the same STAN. In some cases, a pre-authorization will expire
|
|
347
|
+
before the transaction is completed or it will be necessary to
|
|
348
|
+
cancel out of a pre-authorization transaction. In order to release
|
|
349
|
+
the funds held by the pre-authorization, send a zero-dollar ($0)
|
|
350
|
+
Completion transaction.
|
|
351
|
+
sample payload:
|
|
352
|
+
{
|
|
353
|
+
'invoice_number': '00004321',
|
|
354
|
+
'stan': 123456,
|
|
355
|
+
'amount': 12.50,
|
|
356
|
+
'card_number':'6006...123',
|
|
357
|
+
'pin':'0123',
|
|
358
|
+
}
|
|
359
|
+
"""
|
|
360
|
+
route = 'oltp-gift/v1/completion'
|
|
361
|
+
response = await self.post_it(route, data, retry=retry, **kwargs)
|
|
362
|
+
return response
|
|
363
|
+
|
|
364
|
+
async def cancel(self, data, retry=False, **kwargs):
|
|
365
|
+
"""
|
|
366
|
+
Used to manually void or post void a previous transaction. It is
|
|
367
|
+
possible to cancel a transaction until it is
|
|
368
|
+
reconciled or reversed.
|
|
369
|
+
Notes:
|
|
370
|
+
|
|
371
|
+
* In order to cancel funds held by the pre-authorization,
|
|
372
|
+
send a zero-dollar ($0) Completion transaction.
|
|
373
|
+
* A cancellation is NOT a reversal.
|
|
374
|
+
* Cancel transactions can be sent for any original transaction
|
|
375
|
+
type except Cancels, Reversals, Balance Inquiry, and Pre-Authorizations.
|
|
376
|
+
* On a time out, resend the Cancel transaction up to 3 times,
|
|
377
|
+
with a delay between each attempt, until a response is received
|
|
378
|
+
to the Cancellation.
|
|
379
|
+
|
|
380
|
+
sample payload:
|
|
381
|
+
{
|
|
382
|
+
'invoice_number': '00004321',
|
|
383
|
+
'stan': 123456,
|
|
384
|
+
'amount': 12.50,
|
|
385
|
+
'card_number':'6006...123',
|
|
386
|
+
'pin':'0123',
|
|
387
|
+
}
|
|
388
|
+
"""
|
|
389
|
+
route = 'oltp-gift/v1/cancel'
|
|
390
|
+
response = await self.post_it(route, data, retry=retry, **kwargs)
|
|
391
|
+
return response
|
|
392
|
+
|
|
393
|
+
async def reversal(self, data, retry=False, **kwargs):
|
|
394
|
+
"""
|
|
395
|
+
Used to resolve situations arising from a POS timeout or
|
|
396
|
+
network outage. This includes the case when SVS is
|
|
397
|
+
switching authorizations to a third party and a
|
|
398
|
+
response code of 98 (timeout) is returned.
|
|
399
|
+
Notes:
|
|
400
|
+
* A reversal is not a cancellation.
|
|
401
|
+
* Reversal transactions can be sent for any original
|
|
402
|
+
transaction types, excluding Cancels, Reversals,
|
|
403
|
+
Balance Inquiry, and Pre-Authorizations.
|
|
404
|
+
* Do not send a Reversal transaction to back
|
|
405
|
+
out of a Pre-Authorization transaction when the
|
|
406
|
+
pre-authorization times out or must be cancelled.
|
|
407
|
+
In order to release the funds held by the pre-authorization,
|
|
408
|
+
send a zero-dollar ($0) Pre-Authorization Completion
|
|
409
|
+
transaction instead.
|
|
410
|
+
* When a response to the reversal transaction is not
|
|
411
|
+
received, the reversal should be retransmitted. There
|
|
412
|
+
should be a total of 3 reversal attempts with a delay
|
|
413
|
+
between each transaction.
|
|
414
|
+
* Another transaction from the same device should not
|
|
415
|
+
be sent to SVS until a response is received to the reversal.
|
|
416
|
+
* An Issue Virtual Card transaction, which has timed out
|
|
417
|
+
cannot be reversed when the card number is not known.
|
|
418
|
+
However, a reversal is automatically generated by the
|
|
419
|
+
client's system when the response was not received.
|
|
420
|
+
sample payload:
|
|
421
|
+
{
|
|
422
|
+
'invoice_number': '00004321',
|
|
423
|
+
'stan': 123456,
|
|
424
|
+
'amount': 12.50,
|
|
425
|
+
'card_number':'6006...123',
|
|
426
|
+
'pin':'0123',
|
|
427
|
+
}
|
|
428
|
+
"""
|
|
429
|
+
route = 'oltp-gift/v1/reversal'
|
|
430
|
+
response = await self.post_it(route, data, retry=retry, **kwargs)
|
|
431
|
+
return response
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
response_code_map = {
|
|
2
|
+
'01': 'approval',
|
|
3
|
+
'02': 'inactive card',
|
|
4
|
+
'03': 'invalid card number',
|
|
5
|
+
'04': 'invalid transaction code',
|
|
6
|
+
'05': 'insufficient funds',
|
|
7
|
+
'06': 'no previous authorizations',
|
|
8
|
+
'07': 'invalid message',
|
|
9
|
+
'08': 'no card found',
|
|
10
|
+
'09': 'insufficient funds due to outstanding pre-authorization.',
|
|
11
|
+
'10': 'denial - no previous authorization',
|
|
12
|
+
'11': 'no authorization number',
|
|
13
|
+
'12': 'invalid authorization number',
|
|
14
|
+
'13': 'maximum single recharge exceeded',
|
|
15
|
+
'14': 'maximum working balance exceeded',
|
|
16
|
+
'15': 'host unavailable',
|
|
17
|
+
'16': 'invalid card status',
|
|
18
|
+
'17': 'unknown dealer/store code - special edit',
|
|
19
|
+
'18': 'maximum number of recharges exceeded',
|
|
20
|
+
'19': 'invalid card verification value (cvv) or secondary security code (ssc)',
|
|
21
|
+
'20': 'invalid pin number or pin locked',
|
|
22
|
+
'21': 'card already issued',
|
|
23
|
+
'22': 'card not issued',
|
|
24
|
+
'23': 'card already used',
|
|
25
|
+
'24': 'manual transaction not allowed',
|
|
26
|
+
'25': 'mag stripe read not valid',
|
|
27
|
+
'26': 'transaction type unknown',
|
|
28
|
+
'27': 'invalid tender type',
|
|
29
|
+
'28': 'invalid customer type',
|
|
30
|
+
'29': 'n/a',
|
|
31
|
+
'30': 'max number of redemption exceeded',
|
|
32
|
+
'31': 'invalid currency code',
|
|
33
|
+
'32': 'invalid server id (restaurant only - server id code is invalid)',
|
|
34
|
+
'33': 'frozen card or unknown',
|
|
35
|
+
'34': 'invalid amount (transaction amount does not match the pre-valued card dollar amount)',
|
|
36
|
+
'35': 'n/a',
|
|
37
|
+
'36': 'transaction rejected - invalid transaction promotion-wide or invalid for originating merchant and store. (dps)',
|
|
38
|
+
'37': 'transaction rejected - invalid merchant and store combination for promotion. (dps)',
|
|
39
|
+
'38': 'transaction rejected - exceeded maximum number of promotion cards allowed for a single pos transaction. (dps fraud control)',
|
|
40
|
+
'42': 'transaction declined - transaction amount less than the required minimum. (dps)',
|
|
41
|
+
'98': 'timeout/error communicating with third party authorizer',
|
|
42
|
+
'99': 'failure in retrieving data (enhance balance inquiry only)',
|
|
43
|
+
}
|