__init__.py 10.8 KB
import transaction
import venusian
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 deform import (
    Form,
    Button,
    ValidationFailure,
    )
import colander
from opensipkd.string import (
    FixLength,
    FullDateTimeVar,
    )
from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE
from iso8583_web.scripts.tools import iso_to_dict
from iso8583_web.read_conf import web_path_conf
from iso8583_web.scripts.logger import (
    log_web_info,
    log_web_error,
    )
from .. import (
    WebJob as BaseWebJob,
    View as BaseView,
    )
from .exceptions import (
    InvoiceIdError,
    NeedPostError,
    InternalError,
    TrxTypeError,
    HostError,
    AlreadyPaidError,
    TimeoutError,
    BaseError,
    AmountError,
    BillRefNotFound,
    PaymentNotFound,
    )
from .structure import (
    DataRequest,
    InquiryResponse,
    PaymentResponse,
    )
from .renderer import Renderer
from .models import (
    Rpc,
    Log,
    )


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


def get_db_session():
    return conf['db_session']


class WebJob(BaseWebJob):
    def timeout_error(self):  # override
        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'])
    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 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


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 = 'Host {} belum terhubung'.format(hostname)
        return InternalError(msg, 'Sedang offline')

    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()
        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'],
            stan=iso_req.get_stan())
        row.trx_date = date_from_str(data['trx_date']),
        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']
        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'
            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 = \
                    profile['Nama'].strip()
            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()
        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_response(self, data):
        p = dict(invoice_id=data['acc_no'])
        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()
                #ditambahkan karena harus sesuai dengan inq
                DBSession = get_db_session()
                iso_log = DBSession.query(Log).\
                        filter(Log.rpc_id==inq.id,
                               Log.bit_062 != None).first()
                if not iso_log:
                    raise BillRefNotFound()
                p['bits']={"62": iso_log.bit_062,
                          "37": "".join(['000000', iso_log.bit_011])}
            else:
                pay = get_payment(data)
                if not pay:
                    raise BillRefNotFound()
                p['stan'] = pay.stan
        
        # ditambahkan disini untuk ngisi user dan cabang
        if 'bits' not in p:
            p['bits'] = {}
        
        p['bits']['107']='LINKAJA'
        p['bits']['49']='AGRATEK-LINKAJA'
        
        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_trx(self):
        self.validate()
        if not self.request.POST:
            self.log_receive('GET {}'.format(self.request.GET))
            raise NeedPostError()
        items = self.request.POST.items()
        self.log_receive('POST {}'.format(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 = '{} {}'.format(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 = 'Path {} tidak ada'.format(self.request.path)
        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 '{} {} {}'.format(
        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 = '{} {} {}'.format(prefix, 'Send', r) 
    log_web_error(msg)
    response = Response(str(r))
    response.status_int = 200
    return response


# Dipanggil read_conf.py
def init(cfg):
    conf.update(cfg)


# 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']))
    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'])