Commit 66a42da5 by Owo Sugiana

Saat payment linkaja mengambil invoice ID berdasarkan Bill Ref

1 parent 2423558e
0.3.4 2020-10-03
----------------
- Saat payment linkaja mengambil invoice ID berdasarkan Bill Ref
0.3.3 2020-09-14
----------------
- web_client_linkaja.py ada opsi --timeout
......
......@@ -45,18 +45,24 @@ format = %(asctime)s %(levelname)s %(message)s
port = 7001
threads = 12
[web_linkaja]
route_path = /linkaja
module = iso8583_web.scripts.views.linkaja
[web_linkaja_pbb]
route_path = /linkaja/pbb
module = iso8583_web.scripts.views.linkaja.pbb
db_url = postgresql://user:pass@localhost/db
host = bjb
timeout = 30
db_url = postgresql://user:pass@localhost/db
# Notification Message sesuai prefix Invoice ID
notification_message =
3271:PBB Kota Bogor
3676:PBB Kota Tangerang Selatan
3275:PBB Kota Bekasi
[web_linkaja_sambat]
route_path = /linkaja/sambat
module = iso8583_web.scripts.views.linkaja.sambat
db_url = postgresql://user:pass@localhost/db
bank_url = http://localhost:8080/sam-core-app2/rest/SAMWebService
[host_bjb]
ip = 127.0.0.1
port = 10001
......@@ -69,6 +75,7 @@ request_bits =
18:6025
22:010
32:015
33:00110
35:622011888888888888=9912?
41:AGRATEK
42:LINKAJA
......
......@@ -2,6 +2,7 @@ from time import (
time,
sleep,
)
from pyramid.view import notfound_view_config
from opensipkd.tcp.connection import join_ip_port
from iso8583_web.read_conf import (
name_conf,
......@@ -43,6 +44,29 @@ def append_web_response(ip_port, stan, iso):
web_response[ip_port] = {stan: iso}
def get_web_conf(request):
return web_path_conf.get(request.path)
def log_prefix(request):
web_conf = get_web_conf(request)
if web_conf:
name = web_conf['name']
else:
name = 'unknown'
return f'{request.client_addr} {name} {id(request)}'
def log_receive(request, msg, error=False):
prefix = log_prefix(request)
msg = f'{prefix} Receive {msg}'
if error:
log_web_error(msg)
else:
log_web_info(msg)
class WebJob:
def __init__(self, conn, iso, conf=dict()):
self.conn = conn
......@@ -78,13 +102,7 @@ class View:
self.request = request
def log_prefix(self):
web_conf = self.get_web_conf()
if web_conf:
name = web_conf['name']
else:
name = 'unknown'
return '{} {} {}'.format(
self.request.client_addr, name, id(self.request))
return log_prefix(self.request)
def log_receive(self, msg, error=False):
msg = '{} {} {}'.format(self.log_prefix(), 'Receive', msg)
......@@ -123,7 +141,7 @@ class View:
return found_conn
def get_web_conf(self):
return web_path_conf.get(self.request.path)
return get_web_conf(self.request)
def get_iso_conf(self):
web_conf = self.get_web_conf()
......@@ -153,3 +171,10 @@ class View:
cls = self.get_web_job_cls()
job = cls(conn, iso, conf)
return job.get_response()
@notfound_view_config()
def view_not_found(request):
msg = f'Path {request.path} tidak ada'
log_receive(request, msg, True)
return request.exception
......@@ -16,7 +16,7 @@ ROUTE = 'rpc'
conf = dict()
class Webjob(BaseWebJob):
class WebJob(BaseWebJob):
def timeout_error(self): # override
raise JsonRpcBillerNetwork(message='Timeout')
......
from pyramid.response import Response
from pyramid.view import view_config
from deform import ValidationFailure
from sqlalchemy import create_engine
from sqlalchemy.orm import (
sessionmaker,
scoped_session,
)
from zope.sqlalchemy import register
from iso8583_web.read_conf import web_path_conf
from iso8583_web.scripts.logger import (
log_web_info,
log_web_error,
)
from opensipkd.iso8583.bjb.pbb.agratek.models import Rpc
from pyramid_linkaja.responses import (
InquiryResponse,
get_trx_date,
get_method,
is_inquiry,
is_payment,
)
from pyramid_linkaja.exceptions import (
BaseError,
NeedPostError,
InternalError,
BillRefNotFound,
)
from pyramid_linkaja.structure import RENDERER
from pyramid_linkaja.form import get_form
from .. import (
WebJob as BaseWebJob,
View as BaseView,
)
def get_web_conf(request):
return web_path_conf.get(request.path)
def log_prefix(request):
web_conf = get_web_conf(request)
name = web_conf['name']
return f'{request.client_addr} {name} {id(request)}'
def log_receive(self, msg, error=False):
msg = '{} {} {}'.format(self.log_prefix(), 'Receive', msg)
if error:
log_web_error(msg)
else:
log_web_info(msg)
@view_config(context=BaseError)
def view_exception(exc, request):
r = InquiryResponse()
r['Response Code'] = exc.code
r['Notification Message'] = exc.message
prefix = log_prefix(request)
msg = f'{prefix} Send {r}'
log_web_error(msg)
response = Response(str(r))
response.status_int = 200
return response
def pyramid_init(pyramid_config, h2h_conf, route_name):
pyramid_config.add_renderer(RENDERER, 'pyramid_linkaja.renderer.Renderer')
path = h2h_conf['route_path']
pyramid_config.add_route(route_name, path)
log_web_info(f'LinkAja route path {path}')
pool_size = int(h2h_conf.get('db_pool_size', 50))
max_overflow = int(h2h_conf.get('db_max_overflow', 100))
engine = create_engine(
h2h_conf['db_url'], pool_size=pool_size, max_overflow=max_overflow)
session_factory = sessionmaker(bind=engine)
h2h_conf['db_session'] = scoped_session(session_factory)
register(h2h_conf['db_session'])
class WebJob(BaseWebJob):
def timeout_error(self): # override
return TimeoutError()
class View(BaseView):
def get_web_job_cls(self): # Override
return WebJob
def not_found_error(self, hostname): # Override
return HostError(hostname)
def not_running_error(self, hostname): # Override
msg = f'Host {hostname} belum terhubung'
return InternalError(msg, 'Sedang offline')
def get_web_conf(self):
return get_web_conf(self.request)
def get_inquiry(self, data):
DBSession = self.get_db_session()
bill_ref = int(data['bill_ref'])
q = DBSession.query(Rpc).filter_by(id=bill_ref)
return q.first()
def get_payment(self, data):
DBSession = self.get_db_session()
bill_ref = int(data['bill_ref'])
q = DBSession.query(Rpc).filter_by(
inquiry_id=bill_ref, trx_type='022')
q = q.order_by(Rpc.id.desc())
return q.first()
def create_db_log(self, data, inq, pay):
DBSession = self.get_db_session()
web_conf = self.get_web_conf()
row = Rpc(
ip=self.request.client_addr,
conf_name=web_conf['name'],
merchant=data['merchant'],
terminal=data['terminal'],
trx_type=data['trx_type'],
msisdn=data['msisdn'],
acc_no=data['acc_no'])
row.trx_date = get_trx_date(data),
if data.get('msg'):
row.msg = data['msg']
if data.get('amount'):
row.amount = int(data['amount'])
if not is_inquiry(data):
row.inquiry_id = inq and inq.id or pay.inquiry_id
row.ntb = data['trx_id']
return row
def view(self):
self.validate()
if not self.request.POST:
self.log_receive(f'GET {self.request.GET}')
raise NeedPostError()
items = self.request.POST.items()
self.log_receive(f'POST {dict(items)}')
items = self.request.POST.items()
form = get_form()
try:
c = form.validate(items)
except ValidationFailure as e:
d = e.error.asdict()
for field, err in d.items():
msg = f'{field} {err}'
break
raise InternalError(msg)
data = dict(c)
return self.get_response(data)
def get_db_session(self): # Override, please
pass
def get_invoice_id(self, data):
return data['acc_no']
def get_invoice_id_from_inquiry(self, inq):
return inq.acc_no
def prepare_response(self, data):
inq = pay = None
if is_inquiry(data):
invoice_id = self.get_invoice_id(data)
p = dict(invoice_id=invoice_id)
else:
p = dict(amount=data['amount'], ntb=data['trx_id'])
if is_payment(data):
inq = self.get_inquiry(data)
if not inq:
raise BillRefNotFound()
p['invoice_id'] = self.get_invoice_id_from_inquiry(inq)
else:
pay = self.get_payment(data)
if not pay:
raise BillRefNotFound()
inq = self.get_inquiry(data)
p['invoice_id'] = self.get_invoice_id_from_inquiry(inq)
p['stan'] = pay.stan
p['db_session'] = self.get_db_session()
return p, inq, pay
def get_response(self, data): # Override, please
pass
import transaction
from ISO8583.ISOErrors import BitNotSet
from opensipkd.string import FixLength
from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE
from opensipkd.iso8583.bjb.pbb.agratek.models import Log
from iso8583_web.scripts.tools import iso_to_dict
from pyramid_linkaja.exceptions import (
InvoiceIdError,
TrxTypeError,
AlreadyPaidError,
BaseError,
AmountError,
BillRefNotFound,
PaymentNotFound,
LinkError,
)
from pyramid_linkaja.responses import (
InquiryResponse,
PaymentResponse,
get_method,
get_template_response,
is_inquiry,
is_payment,
is_reversal,
)
from pyramid_linkaja.decorator import csv_method
from . import View as BaseView
from . import pyramid_init as base_pyramid_init
ROUTE = 'linkaja-pbb'
conf = dict()
def get_db_session():
return conf['db_session']
def profile2name(profile):
msg = [profile['Nama'].strip()]
fields = [('Tagihan', 'Pok'), ('Denda', 'Den'), ('Discount', 'Disk')]
for field, label in fields:
s = profile[field].strip().lstrip('0')
if s:
msg.append(f'{label} {s}')
fields = ['Nama Kelurahan', 'Nama Kecamatan', 'Lokasi']
for field in fields:
s = profile[field].strip()
if s:
msg.append(s)
return ', '.join(msg)
def get_ok_notify(data):
for key in conf['notification_message']:
if data['acc_no'].find(key) == 0:
return conf['notification_message'][key]
return ''
class View(BaseView):
def get_db_session(self): # Override
return get_db_session()
def create_iso_log(self, iso, rpc):
conf = self.get_iso_conf()
iso_log = Log(
mti=iso.getMTI(),
rpc_id=rpc.id,
ip=conf['ip'],
conf_name=conf['name'])
for bit in iso.get_bit_definition():
try:
value = iso.getBit(bit)
except BitNotSet:
continue
field = 'bit_{}'.format(str(bit).zfill(3))
setattr(iso_log, field, value)
try:
data = iso.getBit(62)
profile = FixLength(INVOICE_PROFILE)
profile.set_raw(data)
iso_log.bit_062_data = profile.to_dict()
except BitNotSet:
pass
return iso_log
def before_send_iso(self, data, inq, pay, iso_req):
DBSession = get_db_session()
row = self.create_db_log(data, inq, pay)
row.stan = iso_req.get_stan()
with transaction.manager:
DBSession.add(row)
DBSession.flush()
DBSession.expunge_all() # Agar dapat row.id
iso_log = self.create_iso_log(iso_req, row)
DBSession.add(iso_log)
return row
def after_send_iso(self, data, inq, pay, row, iso_resp):
DBSession = get_db_session()
iso_log = self.create_iso_log(iso_resp, row)
iso_data = iso_to_dict(iso_resp)
web_data = get_template_response(data)
if iso_data[39] == '00':
web_data['Response Code'] = '00'
web_data['Notification Message'] = get_ok_notify(data)
if is_inquiry(data):
web_data['Bill Ref'] = str(row.id)
if iso_data.get(62):
profile = FixLength(INVOICE_PROFILE)
profile.set_raw(iso_data[62])
iso_log.bit_062_data = profile.to_dict()
web_data['Biller Name'] = row.biller_name = \
profile2name(profile)
web_data['Bill Amount'] = iso_data[4].lstrip('0')
if iso_data.get(47):
web_data['Transaction ID'] = row.ntp = iso_data[47] # NTP
err = None
elif iso_data[39] in ['33', '55']:
err = InvoiceIdError()
elif iso_data[39] == '54':
if is_reversal(data):
err = PaymentNotFound()
else:
err = AlreadyPaidError()
elif iso_data[39] == '51':
err = AmountError()
elif iso_data[39] == '91':
err = LinkError()
else:
err = BaseError()
if err:
web_data.from_err(err)
row.resp_msg = web_data['Notification Message']
self.log_send(web_data.values)
row.resp_code = web_data['Response Code']
row.resp_msg = web_data['Notification Message']
with transaction.manager:
DBSession.add(row)
DBSession.add(iso_log)
return web_data
def get_invoice_id(self, data): # Override
s = data['acc_no']
if len(s) == 18:
s += data['msg']
return s
def get_invoice_id_from_inquiry(self, inq):
s = inq.acc_no
if len(s) == 18:
s += inq.msg
return s
def get_response(self, data): # Override
p, inq, pay = self.prepare_response(data)
conn = self.get_connection()
method = get_method(data)
iso_func = getattr(conn.job, method)
iso_req = iso_func(p)
row = self.before_send_iso(data, inq, pay, iso_req)
iso_resp = self.web_job(conn, iso_req)
return self.after_send_iso(data, inq, pay, row, iso_resp)
@csv_method(route_name=ROUTE)
def view(self):
return super().view()
# Dipanggil read_conf.py
def init(cfg):
conf.update(cfg)
def str2dict(s):
r = dict()
for token in s.split('\n'):
s = token.strip()
if not s:
continue
key, value = s.split(':')
r[key] = value
return r
# Dipanggil forwarder.py
def pyramid_init(config):
base_pyramid_init(config, conf, ROUTE)
conf['notification_message'] = str2dict(conf['notification_message'])
import transaction
from ISO8583.ISOErrors import BitNotSet
from sqlalchemy import create_engine
from sqlalchemy.orm import (
sessionmaker,
scoped_session,
)
from zope.sqlalchemy import register
from pyramid.view import (
view_config,
notfound_view_config,
)
from pyramid.response import Response
from pyramid.view import view_config
from deform import ValidationFailure
from opensipkd.string import FixLength
from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE
......@@ -55,9 +44,14 @@ from pyramid_linkaja.responses import (
from pyramid_linkaja.decorator import csv_method
from pyramid_linkaja.structure import RENDERER
from pyramid_linkaja.form import get_form
from . import (
get_inquiry,
get_payment,
pyramid_init as base_pyramid_init,
)
ROUTE = 'linkaja'
ROUTE = 'linkaja-sambat'
conf = dict()
......@@ -71,22 +65,6 @@ class WebJob(BaseWebJob):
return TimeoutError()
def get_inquiry(data):
DBSession = get_db_session()
bill_ref = int(data['bill_ref'])
q = DBSession.query(Rpc).filter_by(id=bill_ref)
return q.first()
def get_payment(data):
DBSession = get_db_session()
bill_ref = int(data['bill_ref'])
q = DBSession.query(Rpc).filter_by(
inquiry_id=bill_ref, trx_type='022')
q = q.order_by(Rpc.id.desc())
return q.first()
def profile2name(profile):
msg = [profile['Nama'].strip()]
fields = [('Tagihan', 'Pok'), ('Denda', 'Den'), ('Discount', 'Disk')]
......@@ -216,86 +194,12 @@ class View(BaseView):
return web_data
def get_response(self, data):
invoice_id = data['acc_no']
if len(invoice_id) == 18:
invoice_id += data['msg']
p = dict(invoice_id=invoice_id)
inq = pay = None
if not is_inquiry(data):
p['amount'] = data['amount']
p['ntb'] = data['trx_id']
if is_payment(data):
inq = get_inquiry(data)
if not inq:
raise BillRefNotFound()
else:
pay = get_payment(data)
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)
iso_req = iso_func(p)
row = self.before_send_iso(data, inq, pay, iso_req)
iso_resp = self.web_job(conn, iso_req)
return self.after_send_iso(data, inq, pay, row, iso_resp)
p, inq, pay = self.prepare_response(data)
return self.after_send_iso(data, inq, pay, row, iso_resp) #FIXME
@csv_method(route_name=ROUTE)
def view_trx(self):
self.validate()
if not self.request.POST:
self.log_receive(f'GET {self.request.GET}')
raise NeedPostError()
items = self.request.POST.items()
self.log_receive(f'POST {dict(items)}')
items = self.request.POST.items()
form = get_form()
try:
c = form.validate(items)
except ValidationFailure as e:
d = e.error.asdict()
for field, err in d.items():
msg = f'{field} {err}'
break
raise InternalError(msg)
data = dict(c)
return self.get_response(data)
try:
r = self.get_response(data)
self.log_send(r)
return r
except BaseError as e:
r = get_template_response(data)
r['Response Code'] = e.code
r['Notification Message'] = e.message
return r
@notfound_view_config()
def view_not_found(self):
msg = f'Path {self.request.path} tidak ada'
self.log_receive(msg, True)
return self.request.exception
def log_prefix(request):
web_conf = web_path_conf.get(request.path)
name = web_conf['name']
return f'{request.client_addr} {name} {id(request)}'
@view_config(context=BaseError)
def view_exception(exc, request):
r = InquiryResponse()
r['Response Code'] = exc.code
r['Notification Message'] = exc.message
prefix = log_prefix(request)
msg = f'{prefix} Send {r}'
log_web_error(msg)
response = Response(str(r))
response.status_int = 200
return response
def view(self):
return super().view()
# Dipanggil read_conf.py
......@@ -303,28 +207,6 @@ def init(cfg):
conf.update(cfg)
def str2dict(s):
r = dict()
for token in s.split('\n'):
s = token.strip()
if not s:
continue
key, value = s.split(':')
r[key] = value
return r
# Dipanggil forwarder.py
def pyramid_init(config):
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(
conf['db_url'], pool_size=pool_size, max_overflow=max_overflow)
session_factory = sessionmaker(bind=engine)
conf['db_session'] = scoped_session(session_factory)
register(conf['db_session'])
conf['notification_message'] = str2dict(conf['notification_message'])
base_pyramid_init(config, conf, ROUTE)
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!