Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
Owo Sugiana
/
iso8583-web
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit caa7ba2d
authored
Sep 14, 2020
by
Owo Sugiana
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Penggunaan paket pyramid-linkaja
1 parent
ce7e4d5f
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
61 additions
and
365 deletions
CHANGES.txt
README.rst
iso8583-aggregator.ini
iso8583-bank.ini
iso8583_web/scripts/init_db_linkaja.py
iso8583_web/scripts/views/linkaja/__init__.py → iso8583_web/scripts/views/linkaja.py
iso8583_web/scripts/views/linkaja/exceptions.py
iso8583_web/scripts/views/linkaja/models.py
iso8583_web/scripts/views/linkaja/renderer.py
iso8583_web/scripts/views/linkaja/structure.py
setup.py
CHANGES.txt
View file @
caa7ba2
0.3.3 2020-09-
07
0.3.3 2020-09-
14
----------------
- web_client_linkaja.py ada opsi --timeout
- Init database linkaja dipindahkan ke paket opensipkd-iso8583-bjb
- Hal umum pada linkaja dipindahkan ke paket pyramid-linkaja agar semakin
modular
0.3.2 2020-08-27
----------------
...
...
README.rst
View file @
caa7ba2
...
...
@@ -410,7 +410,18 @@ Dari sisi bank Agratek ini dikategorikan sebagai aggregator. Bila simulasi
aplikasi teller sebelumnya menggunakan format JSON-RPC maka kali ini akan
menggunakan format yang sudah ditentukan LinkAja. Siapkan terminal ke-3.
Modul ``linkaja`` membutuhkan database untuk pencatatan transaksi. Buatlah
database-nya terlebih dahulu. Lalu buatlah file ``test-agratek.ini``::
database-nya terlebih dahulu. Lalu buatlah file ``initdb-agratek.ini``::
[main]
module = opensipkd.iso8583.bjb.pbb.agratek
db_url = postgresql://user:pass@localhost/db
Jalankan::
$ ~/env/bin/iso8583_bjb_pbb_agratek initdb-agratek.ini
Lalu buatlah file ``test-agratek.ini``::
[loggers]
keys = root, iso8583_web
...
...
@@ -477,14 +488,17 @@ database-nya terlebih dahulu. Lalu buatlah file ``test-agratek.ini``::
22:010
32:015
35:622011888888888888=9912?
37:000000000000
41:AGRATEK
42:LINKAJA
43:AGRATEK-LINKAJA
49:360
59:PAY
60:123
63:214
# Bit 60 diisi sesuai prefix bit 61 (Invoice ID)
bit_60 =
3271:123
3676:142
3275:120
Buat tabelnya::
...
...
iso8583-aggregator.ini
View file @
caa7ba2
...
...
@@ -70,7 +70,6 @@ request_bits =
22:010
32:015
35:
622011888888888888
=
9912?
37:000000000000
41:AGRATEK
42:LINKAJA
43:AGRATEK-LINKAJA
...
...
iso8583-bank.ini
View file @
caa7ba2
...
...
@@ -48,7 +48,7 @@ threads = 12
[web_teller]
route_path
=
/rpc
host
=
kota_bogor
module
=
iso8583_web.scripts.view.jsonrpc
module
=
iso8583_web.scripts.view
s
.jsonrpc
allowed_ip
=
127.0.0.1
10.8.20.1
...
...
@@ -62,8 +62,20 @@ module = opensipkd.iso8583.bjb.pbb.test_aggregator
db_url
=
postgresql://user:pass@localhost/db
# Routing dari 4 digit awal InvoiceID
host
=
3271:kota_bogor
3275:kota_bekasi
3271:kota_bogor
3275:kota_bekasi
valid_bits
=
2:622011888888888888
18:6025
22:010
32:015
35:
622011888888888888
=
9912?
41:AGRATEK
42:LINKAJA
43:AGRATEK-LINKAJA
49:360
59:PAY
63:214
[host_kota_bogor]
ip
=
127.0.0.1
...
...
iso8583_web/scripts/init_db_linkaja.py
deleted
100644 → 0
View file @
ce7e4d5
import
sys
from
configparser
import
ConfigParser
from
sqlalchemy
import
create_engine
from
.views.linkaja.models
import
Base
def
main
(
argv
=
sys
.
argv
):
conf_file
=
argv
[
1
]
conf
=
ConfigParser
()
conf
.
read
(
conf_file
)
for
section
in
conf
.
sections
():
if
section
.
find
(
'web_'
)
<
0
:
continue
module_name
=
conf
.
get
(
section
,
'module'
)
if
module_name
!=
'iso8583_web.scripts.views.linkaja'
:
continue
db_url
=
conf
.
get
(
section
,
'db_url'
)
engine
=
create_engine
(
db_url
)
engine
.
echo
=
True
Base
.
metadata
.
create_all
(
engine
)
iso8583_web/scripts/views/linkaja
/__init__
.py
→
iso8583_web/scripts/views/linkaja.py
View file @
caa7ba2
import
transaction
import
venusian
from
ISO8583.ISOErrors
import
BitNotSet
from
sqlalchemy
import
create_engine
from
sqlalchemy.orm
import
(
...
...
@@ -12,17 +11,13 @@ from pyramid.view import (
notfound_view_config
,
)
from
pyramid.response
import
Response
from
deform
import
(
Form
,
Button
,
ValidationFailure
,
)
import
colander
from
opensipkd.string
import
(
FixLength
,
FullDateTimeVar
,
)
from
deform
import
ValidationFailure
from
opensipkd.string
import
FixLength
from
opensipkd.iso8583.bjb.pbb.structure
import
INVOICE_PROFILE
from
opensipkd.iso8583.bjb.pbb.agratek.models
import
(
Rpc
,
Log
,
)
from
iso8583_web.scripts.tools
import
iso_to_dict
from
iso8583_web.read_conf
import
web_path_conf
from
iso8583_web.scripts.logger
import
(
...
...
@@ -33,7 +28,7 @@ from .. import (
WebJob
as
BaseWebJob
,
View
as
BaseView
,
)
from
.exceptions
import
(
from
pyramid_linkaja
.exceptions
import
(
InvoiceIdError
,
NeedPostError
,
InternalError
,
...
...
@@ -47,27 +42,15 @@ from .exceptions import (
PaymentNotFound
,
LinkError
,
)
from
.structure
import
(
DataRequest
,
from
pyramid_linkaja.response
import
(
InquiryResponse
,
PaymentResponse
,
)
from
.renderer
import
Renderer
from
.models
import
(
Rpc
,
Log
,
get_trx_date
,
)
ROUTE
=
'linkaja'
RENDERER
=
'csv'
METHOD
=
{
'021'
:
'inquiry'
,
'022'
:
'payment'
,
'023'
:
'reversal'
}
METHOD_CODE
=
dict
()
for
key
,
value
in
METHOD
.
items
():
METHOD_CODE
[
value
]
=
key
conf
=
dict
()
...
...
@@ -81,26 +64,6 @@ class WebJob(BaseWebJob):
return
TimeoutError
()
def
form_validator
(
form
,
value
):
if
value
[
'trx_type'
]
not
in
METHOD
:
raise
TrxTypeError
()
def
get_form
():
schema
=
DataRequest
(
validator
=
form_validator
)
return
Form
(
schema
)
def
date_from_str
(
s
):
t
=
FullDateTimeVar
()
t
.
set_raw
(
s
)
return
t
.
get_value
()
def
get_method
(
data
):
return
METHOD
[
data
[
'trx_type'
]]
def
get_inquiry
(
data
):
DBSession
=
get_db_session
()
bill_ref
=
int
(
data
[
'bill_ref'
])
...
...
@@ -117,49 +80,6 @@ def get_payment(data):
return
q
.
first
()
def
is_inquiry
(
data
):
return
data
[
'trx_type'
]
==
METHOD_CODE
[
'inquiry'
]
def
is_payment
(
data
):
return
data
[
'trx_type'
]
==
METHOD_CODE
[
'payment'
]
def
is_reversal
(
data
):
return
data
[
'trx_type'
]
==
METHOD_CODE
[
'reversal'
]
def
get_template_response
(
data
):
if
is_inquiry
(
data
):
return
InquiryResponse
()
d
=
PaymentResponse
()
d
[
'Bill Ref'
]
=
data
[
'bill_ref'
]
return
d
# view decorator
class
csv_method
(
object
):
def
__init__
(
self
,
**
kw
):
self
.
kw
=
kw
def
__call__
(
self
,
wrapped
):
kw
=
self
.
kw
.
copy
()
depth
=
kw
.
pop
(
'_depth'
,
0
)
def
callback
(
context
,
name
,
ob
):
config
=
context
.
config
.
with_package
(
info
.
module
)
config
.
add_view
(
view
=
ob
,
renderer
=
RENDERER
,
**
kw
)
info
=
venusian
.
attach
(
wrapped
,
callback
,
category
=
'pyramid'
,
depth
=
depth
+
1
)
if
info
.
scope
==
'class'
:
# ensure that attr is set if decorating a class method
kw
.
setdefault
(
'attr'
,
wrapped
.
__name__
)
kw
[
'_info'
]
=
info
.
codeinfo
# fbo action_method
return
wrapped
def
profile2name
(
profile
):
msg
=
[
profile
[
'Nama'
]
.
strip
()]
fields
=
[(
'Tagihan'
,
'Pok'
),
(
'Denda'
,
'Den'
),
(
'Discount'
,
'Disk'
)]
...
...
@@ -190,7 +110,7 @@ class View(BaseView):
return
HostError
(
hostname
)
def
not_running_error
(
self
,
hostname
):
# Override
msg
=
'Host {} belum terhubung'
.
format
(
hostname
)
msg
=
f
'Host {hostname} belum terhubung'
return
InternalError
(
msg
,
'Sedang offline'
)
def
create_iso_log
(
self
,
iso
,
rpc
):
...
...
@@ -228,7 +148,7 @@ class View(BaseView):
msisdn
=
data
[
'msisdn'
],
acc_no
=
data
[
'acc_no'
],
stan
=
iso_req
.
get_stan
())
row
.
trx_date
=
date_from_str
(
data
[
'trx_date'
]
),
row
.
trx_date
=
get_trx_date
(
data
),
if
data
.
get
(
'msg'
):
row
.
msg
=
data
[
'msg'
]
if
data
.
get
(
'amount'
):
...
...
@@ -305,6 +225,7 @@ class View(BaseView):
if
not
pay
:
raise
BillRefNotFound
()
p
[
'stan'
]
=
pay
.
stan
p
[
'db_session'
]
=
get_db_session
()
conn
=
self
.
get_connection
()
method
=
get_method
(
data
)
iso_func
=
getattr
(
conn
.
job
,
method
)
...
...
@@ -317,10 +238,10 @@ class View(BaseView):
def
view_trx
(
self
):
self
.
validate
()
if
not
self
.
request
.
POST
:
self
.
log_receive
(
'GET {}'
.
format
(
self
.
request
.
GET
)
)
self
.
log_receive
(
f
'GET {self.request.GET}'
)
raise
NeedPostError
()
items
=
self
.
request
.
POST
.
items
()
self
.
log_receive
(
'POST {}'
.
format
(
dict
(
items
))
)
self
.
log_receive
(
f
'POST {dict(items)}'
)
items
=
self
.
request
.
POST
.
items
()
form
=
get_form
()
try
:
...
...
@@ -328,7 +249,7 @@ class View(BaseView):
except
ValidationFailure
as
e
:
d
=
e
.
error
.
asdict
()
for
field
,
err
in
d
.
items
():
msg
=
'{} {}'
.
format
(
field
,
err
)
msg
=
f
'{field} {err}'
break
raise
InternalError
(
msg
)
data
=
dict
(
c
)
...
...
@@ -345,7 +266,7 @@ class View(BaseView):
@notfound_view_config
()
def
view_not_found
(
self
):
msg
=
'Path {} tidak ada'
.
format
(
self
.
request
.
path
)
msg
=
f
'Path {self.request.path} tidak ada'
self
.
log_receive
(
msg
,
True
)
return
self
.
request
.
exception
...
...
@@ -353,8 +274,7 @@ class View(BaseView):
def
log_prefix
(
request
):
web_conf
=
web_path_conf
.
get
(
request
.
path
)
name
=
web_conf
[
'name'
]
return
'{} {} {}'
.
format
(
request
.
client_addr
,
name
,
id
(
request
))
return
f
'{request.client_addr} {name} {id(request)}'
@view_config
(
context
=
BaseError
)
...
...
@@ -388,9 +308,10 @@ def str2dict(s):
# Dipanggil forwarder.py
def
pyramid_init
(
config
):
config
.
add_renderer
(
RENDERER
,
'iso8583_web.scripts.views.linkaja.Renderer'
)
config
.
add_route
(
ROUTE
,
conf
[
'route_path'
])
log_web_info
(
'LinkAja route path {}'
.
format
(
conf
[
'route_path'
]))
config
.
add_renderer
(
RENDERER
,
'pyramid_linkaja.renderer.Renderer'
)
path
=
conf
[
'route_path'
]
config
.
add_route
(
ROUTE
,
path
)
log_web_info
(
f
'LinkAja route path {path}'
)
pool_size
=
int
(
conf
.
get
(
'db_pool_size'
,
50
))
max_overflow
=
int
(
conf
.
get
(
'db_max_overflow'
,
100
))
engine
=
create_engine
(
...
...
iso8583_web/scripts/views/linkaja/exceptions.py
deleted
100644 → 0
View file @
ce7e4d5
class
BaseError
(
Exception
):
code
=
'49'
message
=
'Ada kesalahan yang belum dipahami'
class
NeedPostError
(
BaseError
):
code
=
'40'
message
=
'HTTP Request harus POST'
class
TrxTypeError
(
BaseError
):
code
=
'41'
message
=
'trx_type tidak dikenal'
class
InternalError
(
BaseError
):
def
__init__
(
self
,
orig_msg
,
msg
=
'Ada kesalahan internal'
):
self
.
orig_msg
=
orig_msg
self
.
message
=
msg
class
TimeoutError
(
BaseError
):
code
=
'42'
message
=
'Timeout'
class
InvoiceIdError
(
BaseError
):
code
=
'43'
message
=
'acc_no tidak ditemukan'
class
HostError
(
BaseError
):
code
=
'44'
def
__init__
(
self
,
hostname
):
self
.
hostname
=
hostname
self
.
message
=
'Host {} tidak terdaftar'
.
format
(
hostname
)
class
AlreadyPaidError
(
BaseError
):
code
=
'45'
message
=
'Memang sudah lunas'
class
AmountError
(
BaseError
):
code
=
'46'
message
=
'Jumlah pembayaran tidak sesuai tagihan'
class
BillRefNotFound
(
BaseError
):
code
=
'47'
message
=
'bill_ref tidak ditemukan'
class
PaymentNotFound
(
BaseError
):
code
=
'48'
message
=
'Belum ada pembayaran'
class
LinkError
(
BaseError
):
code
=
'49'
message
=
'Koneksi terputus'
iso8583_web/scripts/views/linkaja/models.py
deleted
100644 → 0
View file @
ce7e4d5
from
sqlalchemy
import
(
Column
,
Integer
,
Float
,
DateTime
,
String
,
ForeignKey
,
func
,
JSON
,
)
from
sqlalchemy.ext.declarative
import
declarative_base
Base
=
declarative_base
()
class
Rpc
(
Base
):
__tablename__
=
'linkaja_trx'
id
=
Column
(
Integer
,
primary_key
=
True
)
# Bill Ref
created
=
Column
(
DateTime
(
timezone
=
True
),
nullable
=
False
,
server_default
=
func
.
now
())
ip
=
Column
(
String
(
15
),
nullable
=
False
)
conf_name
=
Column
(
String
(
16
),
nullable
=
False
)
merchant
=
Column
(
String
(
100
),
nullable
=
False
)
terminal
=
Column
(
String
(
100
),
nullable
=
False
)
trx_type
=
Column
(
String
(
3
),
nullable
=
False
)
msisdn
=
Column
(
String
(
20
),
nullable
=
False
)
# Invoice ID = SPPT ID di PBB
acc_no
=
Column
(
String
(
64
),
nullable
=
False
)
msg
=
Column
(
String
(
100
))
trx_date
=
Column
(
DateTime
(
timezone
=
True
),
nullable
=
False
)
# Dari inquiry response
amount
=
Column
(
Float
)
# Dari inquiry response (Bill Ref),
# diisi saat payment request (bill_ref)
inquiry_id
=
Column
(
Integer
,
ForeignKey
(
'linkaja_trx.id'
))
# Dari payment request (trx_id)
ntb
=
Column
(
String
(
32
))
# Penerjemahan bit 39
resp_code
=
Column
(
String
(
2
))
# Penjelasan resp_code untuk pelanggan
resp_msg
=
Column
(
String
(
100
))
# Penjelasan resp_code untuk audit sistem
resp_orig_msg
=
Column
(
String
(
100
))
# Nama wajib pajak
biller_name
=
Column
(
String
(
100
))
# Dari payment response (Transaction ID)
ntp
=
Column
(
String
(
32
))
# Dari bit 11, dibutuhkan untuk reversal
stan
=
Column
(
String
(
6
))
class
Log
(
Base
):
__tablename__
=
'log_iso'
id
=
Column
(
Integer
,
primary_key
=
True
)
created
=
Column
(
DateTime
(
timezone
=
True
),
nullable
=
False
,
server_default
=
func
.
now
())
rpc_id
=
Column
(
Integer
,
ForeignKey
(
Rpc
.
id
),
nullable
=
False
)
ip
=
Column
(
String
(
15
))
conf_name
=
Column
(
String
(
16
),
nullable
=
False
)
mti
=
Column
(
String
(
4
),
nullable
=
False
)
bit_002
=
Column
(
String
(
99
))
bit_003
=
Column
(
String
(
6
))
bit_004
=
Column
(
String
(
12
))
bit_007
=
Column
(
String
(
10
))
bit_011
=
Column
(
String
(
6
))
bit_012
=
Column
(
String
(
6
))
bit_013
=
Column
(
String
(
4
))
bit_015
=
Column
(
String
(
4
))
bit_018
=
Column
(
String
(
4
))
bit_022
=
Column
(
String
(
3
))
bit_032
=
Column
(
String
(
4
))
bit_033
=
Column
(
String
(
10
))
bit_035
=
Column
(
String
(
99
))
bit_037
=
Column
(
String
(
12
))
bit_039
=
Column
(
String
(
2
))
bit_041
=
Column
(
String
(
8
))
bit_042
=
Column
(
String
(
15
))
bit_043
=
Column
(
String
(
40
))
bit_047
=
Column
(
String
(
99
))
bit_048
=
Column
(
String
(
99
))
bit_049
=
Column
(
String
(
3
))
bit_059
=
Column
(
String
(
16
))
bit_060
=
Column
(
String
(
3
))
bit_061
=
Column
(
String
(
22
))
bit_062
=
Column
(
String
(
512
))
bit_063
=
Column
(
String
(
255
))
bit_102
=
Column
(
String
(
32
))
bit_107
=
Column
(
String
(
8
))
bit_062_data
=
Column
(
JSON
)
iso8583_web/scripts/views/linkaja/renderer.py
deleted
100644 → 0
View file @
ce7e4d5
class
Renderer
(
object
):
def
__init__
(
self
,
info
):
pass
def
__call__
(
self
,
value
,
system
):
''' value bertipe DataResponse '''
request
=
system
.
get
(
'request'
)
if
request
is
not
None
:
response
=
request
.
response
ct
=
response
.
content_type
if
ct
==
response
.
default_content_type
:
response
.
content_type
=
'text/csv'
return
str
(
value
)
iso8583_web/scripts/views/linkaja/structure.py
deleted
100644 → 0
View file @
ce7e4d5
import
colander
from
opensipkd.string.row
import
Row
from
.exceptions
import
InternalError
INQUIRY_RESP_FIELDS
=
(
'Response Code'
,
'Biller Name'
,
'Bill Amount'
,
'Bill Ref'
,
# STAN
'Notification Message'
)
PAYMENT_RESP_FIELDS
=
(
'Response Code'
,
'Transaction ID'
,
# NTP
'Bill Ref'
,
# STAN
'Notification Message'
)
class
DataRequest
(
colander
.
Schema
):
merchant
=
colander
.
SchemaNode
(
colander
.
String
())
terminal
=
colander
.
SchemaNode
(
colander
.
String
())
pwd
=
colander
.
SchemaNode
(
colander
.
String
())
trx_type
=
colander
.
SchemaNode
(
colander
.
String
())
msisdn
=
colander
.
SchemaNode
(
colander
.
String
())
acc_no
=
colander
.
SchemaNode
(
colander
.
String
())
msg
=
colander
.
SchemaNode
(
colander
.
String
(),
missing
=
colander
.
drop
)
trx_date
=
colander
.
SchemaNode
(
colander
.
String
())
amount
=
colander
.
SchemaNode
(
colander
.
String
(),
missing
=
colander
.
drop
)
# Saat payment dan reversal, diperoleh dari inquiry response
bill_ref
=
colander
.
SchemaNode
(
colander
.
String
(),
missing
=
colander
.
drop
)
# Saat payment dan reversal, dibuat saat payment request (NTB)
trx_id
=
colander
.
SchemaNode
(
colander
.
String
(),
missing
=
colander
.
drop
)
class
InquiryResponse
(
Row
):
def
__init__
(
self
):
super
()
.
__init__
(
INQUIRY_RESP_FIELDS
)
def
__setitem__
(
self
,
name
,
value
):
if
value
.
find
(
':'
)
>
-
1
:
msg
=
'Ada titik dua pada {} yaitu {}'
.
format
(
name
,
value
)
raise
InternalError
(
msg
)
Row
.
__setitem__
(
self
,
name
,
value
)
def
__str__
(
self
):
return
self
.
to_str
()
def
to_str
(
self
):
return
':'
.
join
(
list
(
self
))
def
from_err
(
self
,
err
):
self
.
values
[
'Response Code'
]
=
err
.
code
self
.
values
[
'Notification Message'
]
=
err
.
message
def
from_raw
(
self
,
raw
):
t
=
raw
.
split
(
':'
)
i
=
-
1
for
fieldname
in
self
.
fieldnames
:
i
+=
1
if
not
t
[
i
:
i
+
1
]:
return
self
.
values
[
fieldname
]
=
t
[
i
]
class
PaymentResponse
(
InquiryResponse
):
def
__init__
(
self
):
Row
.
__init__
(
self
,
PAYMENT_RESP_FIELDS
)
setup.py
View file @
caa7ba2
...
...
@@ -31,9 +31,10 @@ requires = [
'pyramid_mailer'
,
'requests'
,
'pyramid_rpc'
,
'opensipkd-base @ git+https://git.opensipkd.com/sugiana/opensipkd-base'
,
'opensipkd-iso8583 @ git+https://git.opensipkd.com/sugiana/opensipkd-iso8583'
,
'opensipkd-hitung @ git+https://git.opensipkd.com/sugiana/opensipkd-hitung'
,
'opensipkd-base @ git+https://git.opensipkd.com/sugiana/opensipkd-base.git'
,
'opensipkd-iso8583 @ git+https://git.opensipkd.com/sugiana/opensipkd-iso8583.git'
,
'opensipkd-hitung @ git+https://git.opensipkd.com/sugiana/opensipkd-hitung.git'
,
'opensipkd-hitung @ git+https://git.opensipkd.com/sugiana/pyramid-linkaja.git'
,
]
...
...
@@ -75,7 +76,6 @@ setup(
'iso8583_web_client = iso8583_web.scripts.web_client:main'
,
'iso8583_web_client_linkaja = iso8583_web.scripts.web_client_linkaja:main'
,
'initialize_iso8583_web_db = iso8583_web.scripts.initialize_db:main'
,
'init_db_linkaja = iso8583_web.scripts.init_db_linkaja:main'
,
]
},
)
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment