Skip to content

Commit

Permalink
OM-190: added SOAP Service to get vouchers
Browse files Browse the repository at this point in the history
  • Loading branch information
sniedzielski committed Aug 23, 2024
1 parent b74686d commit bd093c1
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 3 deletions.
11 changes: 10 additions & 1 deletion msystems/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,15 @@
"service_private_key": "",
# Mconnect certificate, PEM string format
"mconnect_certificate": ""
}
},
"voucher_config": {
# The same as mpass cert
"service_certificate": "",
# The same as mpass private key
"service_private_key": "",
# client_certificate, PEM string format
"client_certificate": "",
},
}


Expand All @@ -112,6 +120,7 @@ class MsystemsConfig(AppConfig):
mpass_config = None
mpay_config = None
mconnect_config = None
voucher_config = None

def ready(self):
from core.models import ModuleConfiguration
Expand Down
35 changes: 34 additions & 1 deletion msystems/soap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
OrderStatus = Enum('Active', 'PartiallyPaid', 'Paid', 'Completed', 'Expired', 'Canceled', 'Refunding',
'Refunded', type_name='OrderStatus')
PropertyType = Enum('string', 'idn', 'tc', type_name='Type')

VoucherStatus = Enum('Assigned', 'AwaitingPayment', 'Canceled', 'Closed', 'Expired', 'Unassigned',
type_name='VoucherStatus')

class OrderProperty(ComplexModel):
namespace = namespace
Expand Down Expand Up @@ -124,3 +125,35 @@ class PaymentConfirmation(ComplexModel):
.customize(min_occurs=0, max_occurs=1, nillable=False))
ServiceID = Unicode.customize(min_occurs=1, max_occurs=1, max_len=36, nillable=False)
TotalAmount = Decimal.customize(min_occurs=1, max_occurs=1, nillable=False)


class VouchersDetailsQuery(ComplexModel):
__namespace__ = namespace
__type_name__ = 'VouchersDetailsQuery'

AssignedDate = DateTime.customize(min_occurs=0, max_occurs=1, nillable=True)
EmployerCode = Unicode.customize(min_occurs=0, max_occurs=1, nillable=True)
ExpiryDate = DateTime.customize(min_occurs=0, max_occurs=1, nillable=True)
WorkerNationalID = Unicode.customize(min_occurs=0, max_occurs=1, nillable=True)
VoucherCode = Unicode.customize(min_occurs=0, max_occurs=1, nillable=True)
VoucherStatus = VoucherStatus.customize(min_occurs=0, max_occurs=1, nillable=True)


class VoucherDetails(ComplexModel):
__namespace__ = namespace
__type_name__ = 'VoucherDetails'

AssignedDate = DateTime.customize(min_occurs=1, max_occurs=1, nillable=True)
EmployerCode = Unicode.customize(min_occurs=1, max_occurs=1, nillable=True)
ExpiryDate = DateTime.customize(min_occurs=1, max_occurs=1, nillable=True)
WorkerNationalID = Unicode.customize(min_occurs=1, max_occurs=1, nillable=True)
VoucherCode = Unicode.customize(min_occurs=1, max_occurs=1, nillable=True)
VoucherStatus = VoucherStatus.customize(min_occurs=1, max_occurs=1, nillable=True)


class GetVouchersDetailsResult(ComplexModel):
__namespace__ = namespace
__type_name__ = 'GetVouchersDetailsResult'

VouchersDetails = (Array(VoucherDetails.customize(min_occurs=1, max_occurs="unbounded", nillable=False))
.customize(min_occurs=0, max_occurs=1, nillable=False))
3 changes: 2 additions & 1 deletion msystems/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.urls import path, include
from django.views.decorators.csrf import csrf_exempt

from msystems.views import mpass, mpay
from msystems.views import mpass, mpay, vouchers

saml_urls = [
path("login/", mpass.login),
Expand All @@ -14,4 +14,5 @@
path("saml/", include(saml_urls)),
path("mpay/", csrf_exempt(mpay.mpay_app)),
path("mpay_payment/", mpay.mpay_bill_payment_redirect),
path("vouchers/", csrf_exempt(vouchers.voucher_app)),
]
126 changes: 126 additions & 0 deletions msystems/views/vouchers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import logging

from lxml import etree
from spyne.application import Application
from spyne.decorator import rpc
from spyne.model.fault import Fault
from spyne.protocol.soap import Soap11
from spyne.server.django import DjangoApplication
from spyne.service import ServiceBase
from zeep.exceptions import SignatureVerificationFailed

from msystems.apps import MsystemsConfig
from msystems.soap.datetime import SoapDatetime
from msystems.xml_utils import add_signature, verify_signature, verify_timestamp, add_timestamp
from worker_voucher.models import WorkerVoucher
from msystems.soap.models import VoucherDetails, VouchersDetailsQuery, GetVouchersDetailsResult


namespace = 'https://mpay.gov.md'
logger = logging.getLogger(__name__)


def _get_vouchers(query: VouchersDetailsQuery):
vouchers = WorkerVoucher.objects.filter(is_deleted=False)

# Apply filters based on the query attributes
if query.AssignedDate:
vouchers = vouchers.filter(assigned_date=query.AssignedDate)

if query.ExpiryDate:
vouchers = vouchers.filter(expiry_date=query.ExpiryDate)

if query.EmployerCode:
vouchers = vouchers.filter(policyholder__code=query.EmployerCode)

if query.WorkerNationalID:
vouchers = vouchers.filter(insuree__chf_id=query.WorkerNationalID)

if query.VoucherCode:
vouchers = vouchers.filter(code=query.VoucherCode)

if query.VoucherStatus:
vouchers = vouchers.filter(status=query.VoucherStatus)

return vouchers


def _log_rpc_call(ctx):
input = ctx.transport.req.get('wsgi.input')
action = ctx.transport.req.get('HTTP_SOAPACTION')
if input:
input.seek(0)
data = input.read().decode("utf-8")
logger.info(f"Method {action} called with:\n{data}\n")
input.seek(0)


def _validate_envelope(ctx):
root = ctx.in_document

try:
verify_timestamp(root)
except ValueError as e:
logger.error("Timestamp verification failed", exc_info=e)
raise Fault(faultcode='InvalidRequest', faultstring=str(e))

try:
verify_signature(root, MsystemsConfig.voucher_config['client_certificate'])
except SignatureVerificationFailed as e:
logger.error("Envelope signature verification failed", exc_info=e)
raise Fault(faultcode='InvalidRequest', faultstring='Envelope signature verification failed')


def _add_envelope_header(ctx):
root = ctx.out_document

add_timestamp(root)
add_signature(root, MsystemsConfig.voucher_config['service_private_key'],
MsystemsConfig.voucher_config['service_certificate'])

envelope = etree.tostring(ctx.out_document, pretty_print=True)
logger.info(envelope.decode('utf-8'))
ctx.out_string = [envelope]


class VoucherService(ServiceBase):
@rpc(VouchersDetailsQuery.customize(min_occurs=1, max_occurs=1, nillable=False),
_returns=GetVouchersDetailsResult.customize(min_occurs=1, max_occurs=1, nillable=False))
def GetVouchersDetails(ctx, query: VouchersDetailsQuery) -> GetVouchersDetailsResult:
vouchers = _get_vouchers(query)

vouchers_details = []
for voucher in vouchers:
vouchers_details.append(VoucherDetails(
AssignedDate=SoapDatetime.from_ad_date(voucher.assigned_date),
ExpiryDate=SoapDatetime.from_ad_date(voucher.expiry_date),
EmployerCode=str(voucher.policyholder.code) if voucher.policyholder else None,
WorkerNationalID=str(voucher.insuree.chf_id) if voucher.insuree else None,
VoucherCode=str(voucher.code),
VoucherStatus=str(voucher.status),
))

if not vouchers_details:
raise Fault(faultcode='InvalidParameter',
faultstring=f'Given criteria has no associated vouchers')

ret = GetVouchersDetailsResult(VouchersDetails=vouchers_details)
return ret


def _error_handler_function(ctx, *args, **kwargs):
logger.error("Spyne error", exc_info=ctx.in_error)


_application = Application(
[VoucherService],
tns=namespace,
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11(),
)
_application.event_manager.add_listener('method_call', _validate_envelope)
_application.event_manager.add_listener('method_exception_object', _error_handler_function)

voucher_app = DjangoApplication(_application)
voucher_app.event_manager.add_listener('wsgi_call', _log_rpc_call)
voucher_app.event_manager.add_listener('wsgi_return', _add_envelope_header)

0 comments on commit bd093c1

Please sign in to comment.