Skip to content

Commit 808905b

Browse files
Rohit4997piyushKumar-1
authored andcommitted
Add IFFCO Tokyo home declaration insurance integration
1 parent ad88925 commit 808905b

8 files changed

Lines changed: 379 additions & 9 deletions

File tree

lib/mobility-core/mobility-core.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ library
8888
Kernel.External.Infobip.Types
8989
Kernel.External.Insurance.Acko.Flow
9090
Kernel.External.Insurance.Acko.Types
91+
Kernel.External.Insurance.IffcoTokio.Flow
92+
Kernel.External.Insurance.IffcoTokio.Types
9193
Kernel.External.Insurance.Interface
9294
Kernel.External.Insurance.Interface.Acko
95+
Kernel.External.Insurance.Interface.IffcoTokio
9396
Kernel.External.Insurance.Interface.Types
9497
Kernel.External.Insurance.Types
9598
Kernel.External.Maps
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
module Kernel.External.Insurance.IffcoTokio.Flow where
2+
3+
import qualified Data.ByteString.Lazy as BSL
4+
import qualified Data.Text as T
5+
import qualified Data.Text.Encoding as TE
6+
import qualified Data.Text.Lazy as TL
7+
import EulerHS.Types (EulerClient, client)
8+
import Kernel.External.Encryption
9+
import Kernel.External.Insurance.IffcoTokio.Types
10+
import Kernel.Prelude
11+
import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics)
12+
import Kernel.Types.Error (GenericError (..))
13+
import Kernel.Utils.Common
14+
import qualified Network.HTTP.Media as M
15+
import Servant hiding (throwError)
16+
import Text.XML (def, parseText)
17+
import Text.XML.Cursor (content, fromDocument, laxElement, ($//), (&/))
18+
import qualified Text.XML.Cursor as XC
19+
20+
-- ---------------------------------------------------------------------------
21+
-- Servant content type for SOAP / XML
22+
23+
data IffcoApplicationXML deriving (Typeable)
24+
25+
instance Accept IffcoApplicationXML where
26+
contentType _ = "text" M.// "xml"
27+
28+
-- Render: pass raw BSL bytes directly as the request body
29+
instance MimeRender IffcoApplicationXML BSL.ByteString where
30+
mimeRender _ bs = bs
31+
32+
-- Unrender: decode UTF-8 bytes and parse the SOAP response envelope
33+
instance MimeUnrender IffcoApplicationXML HomeDeclarationResp where
34+
mimeUnrender _ bs =
35+
case TE.decodeUtf8' (BSL.toStrict bs) of
36+
Left err -> Left (show err)
37+
Right txt -> case parseHomeDeclarationResponse txt of
38+
Left e -> Left (T.unpack e)
39+
Right r -> Right r
40+
41+
-- ---------------------------------------------------------------------------
42+
-- Servant API type
43+
44+
-- The IFFCO Tokio endpoint takes fixed query params that identify the operation.
45+
-- SOAPAction value: "document/http://siebel.com/CustomUI:RegisterHomeDeclaration_New"
46+
-- (Servant QueryParam handles URL-encoding automatically.)
47+
type RegisterHomeDeclarationAPI =
48+
"eai_ws_enu"
49+
:> "start.swe"
50+
:> QueryParam "SWEExtSource" Text
51+
:> QueryParam "SWEExtCmd" Text
52+
:> QueryParam "SOAPAction" Text
53+
:> ReqBody '[IffcoApplicationXML] BSL.ByteString
54+
:> Post '[IffcoApplicationXML] HomeDeclarationResp
55+
56+
registerHomeDeclarationClient ::
57+
Maybe Text ->
58+
Maybe Text ->
59+
Maybe Text ->
60+
BSL.ByteString ->
61+
EulerClient HomeDeclarationResp
62+
registerHomeDeclarationClient = client (Proxy :: Proxy RegisterHomeDeclarationAPI)
63+
64+
-- ---------------------------------------------------------------------------
65+
-- Main call function
66+
67+
-- | Make the SOAP call and throw on failure (standard throwing variant).
68+
registerHomeDeclaration ::
69+
( HasCallStack,
70+
EncFlow m r,
71+
MonadFlow m,
72+
CoreMetrics m,
73+
HasRequestId r,
74+
MonadReader r m
75+
) =>
76+
IffcoTokioConfig ->
77+
HomeDeclarationReq ->
78+
m HomeDeclarationResp
79+
registerHomeDeclaration config req =
80+
registerHomeDeclarationEither config req
81+
>>= either (\err -> throwError $ InternalError $ "IFFCO Tokio API call failed: " <> err) return
82+
83+
-- | Make the SOAP call and return 'Either' instead of throwing.
84+
-- Used by the async interface layer so that the callback always fires.
85+
registerHomeDeclarationEither ::
86+
( HasCallStack,
87+
EncFlow m r,
88+
MonadFlow m,
89+
CoreMetrics m,
90+
HasRequestId r,
91+
MonadReader r m
92+
) =>
93+
IffcoTokioConfig ->
94+
HomeDeclarationReq ->
95+
m (Either Text HomeDeclarationResp)
96+
registerHomeDeclarationEither config req = do
97+
decryptedPassword <- decrypt config.password
98+
let soapBody = buildSoapEnvelope config.username decryptedPassword config req
99+
eulerClient =
100+
registerHomeDeclarationClient
101+
(Just "SecureWebService")
102+
(Just "Execute")
103+
-- SOAPAction value includes surrounding double-quotes as per SOAP spec
104+
(Just "\"document/http://siebel.com/CustomUI:RegisterHomeDeclaration_New\"")
105+
soapBody
106+
result <- callAPI config.url eulerClient "IFFCO-Tokio-register-home-declaration" (Proxy @RegisterHomeDeclarationAPI)
107+
return $ case result of
108+
Left err -> Left (T.pack (show err))
109+
Right resp -> Right resp
110+
111+
-- ---------------------------------------------------------------------------
112+
-- SOAP envelope builder
113+
114+
buildSoapEnvelope :: Text -> Text -> IffcoTokioConfig -> HomeDeclarationReq -> BSL.ByteString
115+
buildSoapEnvelope username password cfg req =
116+
BSL.fromStrict . TE.encodeUtf8 $
117+
T.concat
118+
[ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
119+
"<soapenv:Envelope",
120+
" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"",
121+
" xmlns:cus=\"http://siebel.com/CustomUI\"",
122+
" xmlns:ins=\"http://www.siebel.com/xml/INSDeclarationsWebServiceRequestIO\">",
123+
"<soapenv:Header>",
124+
"<wsse:Security xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2002/07/secext\">",
125+
"<wsse:UsernameToken xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">",
126+
"<wsse:Username>",
127+
xmlEscape username,
128+
"</wsse:Username>",
129+
"<wsse:Password Type=\"wsse:PasswordText\">",
130+
xmlEscape password,
131+
"</wsse:Password>",
132+
"</wsse:UsernameToken>",
133+
"</wsse:Security>",
134+
"</soapenv:Header>",
135+
"<soapenv:Body>",
136+
"<cus:RegisterHomeDeclaration_New_Input>",
137+
"<ins:INSDeclarationsWebServiceRequestIO>",
138+
"<ins:DeclarationDetails>",
139+
"<ins:MasterPolicyClient>",
140+
xmlEscape cfg.masterPolicyClient,
141+
"</ins:MasterPolicyClient>",
142+
"<ins:InsurancePlan>",
143+
xmlEscape cfg.insurancePlan,
144+
"</ins:InsurancePlan>",
145+
"<ins:InsuredAddress>",
146+
xmlEscape req.insuredAddress,
147+
"</ins:InsuredAddress>",
148+
"<ins:InsuredEmail>",
149+
xmlEscape req.insuredEmail,
150+
"</ins:InsuredEmail>",
151+
"<ins:InsuredMobile>",
152+
xmlEscape req.insuredMobile,
153+
"</ins:InsuredMobile>",
154+
"<ins:InsuredName>",
155+
xmlEscape req.insuredName,
156+
"</ins:InsuredName>",
157+
"<ins:InvoiceDate>",
158+
xmlEscape req.invoiceDate,
159+
"</ins:InvoiceDate>",
160+
"<ins:InvoiceRequestNumber>",
161+
xmlEscape req.invoiceRequestNumber,
162+
"</ins:InvoiceRequestNumber>",
163+
"<ins:EWCommencesOn>",
164+
maybe "" xmlEscape req.ewCommencesOn,
165+
"</ins:EWCommencesOn>",
166+
"</ins:DeclarationDetails>",
167+
"</ins:INSDeclarationsWebServiceRequestIO>",
168+
"</cus:RegisterHomeDeclaration_New_Input>",
169+
"</soapenv:Body>",
170+
"</soapenv:Envelope>"
171+
]
172+
173+
-- Escape special XML characters in text node values to prevent injection
174+
xmlEscape :: Text -> Text
175+
xmlEscape =
176+
T.replace "&" "&amp;"
177+
. T.replace "<" "&lt;"
178+
. T.replace ">" "&gt;"
179+
. T.replace "\"" "&quot;"
180+
. T.replace "'" "&apos;"
181+
182+
-- SOAP response XML parser (uses xml-conduit cursor navigation)
183+
184+
lookupSoapField :: XC.Cursor -> Text -> Maybe Text
185+
lookupSoapField cursor localName =
186+
case cursor $// laxElement localName &/ content of
187+
[] -> Nothing
188+
ts -> Just $ T.strip $ T.concat ts
189+
190+
parseHomeDeclarationResponse :: Text -> Either Text HomeDeclarationResp
191+
parseHomeDeclarationResponse xmlText = do
192+
doc <- case parseText def (TL.fromStrict xmlText) of
193+
Left err -> Left (T.pack (show err))
194+
Right d -> Right d
195+
let cursor = fromDocument doc
196+
require fieldName =
197+
maybe (Left $ "IFFCO Tokio response missing field: " <> fieldName) Right $
198+
lookupSoapField cursor fieldName
199+
certNum <- require "Certificate_spcNumber"
200+
declId <- require "Declaration_spcId"
201+
st <- require "Status"
202+
return HomeDeclarationResp {certificateNumber = certNum, declarationId = declId, status = st}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Kernel.External.Insurance.IffcoTokio.Types where
2+
3+
import Data.Aeson
4+
import Kernel.External.Encryption
5+
import Kernel.Prelude
6+
7+
data IffcoTokioConfig = IffcoTokioConfig
8+
{ url :: BaseUrl,
9+
username :: Text,
10+
password :: EncryptedField 'AsEncrypted Text,
11+
masterPolicyClient :: Text,
12+
insurancePlan :: Text
13+
}
14+
deriving (Show, Eq, Generic, ToJSON, FromJSON)
15+
16+
data HomeDeclarationReq = HomeDeclarationReq
17+
{ insuredAddress :: Text,
18+
insuredEmail :: Text,
19+
insuredMobile :: Text,
20+
insuredName :: Text,
21+
-- | Date in MM/DD/YYYY format as required by IFFCO Tokio
22+
invoiceDate :: Text,
23+
-- | Correlation ID used to match the async response back to the caller
24+
invoiceRequestNumber :: Text,
25+
ewCommencesOn :: Maybe Text
26+
}
27+
deriving (Show, Eq, Generic, ToJSON, FromJSON)
28+
29+
data HomeDeclarationResp = HomeDeclarationResp
30+
{ certificateNumber :: Text,
31+
declarationId :: Text,
32+
status :: Text
33+
}
34+
deriving (Show, Eq, Generic, ToJSON, FromJSON)

lib/mobility-core/src/Kernel/External/Insurance/Interface.hs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ module Kernel.External.Insurance.Interface
44
)
55
where
66

7+
import qualified Kernel.External.Insurance.IffcoTokio.Types as IffcoTokioTypes
78
import qualified Kernel.External.Insurance.Interface.Acko as Acko
9+
import qualified Kernel.External.Insurance.Interface.IffcoTokio as IffcoTokio
810
import Kernel.External.Insurance.Interface.Types
911
import Kernel.External.Insurance.Types as Reexport
12+
import Kernel.Prelude
1013
import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics)
14+
import Kernel.Types.Error (GenericError (..))
1115
import Kernel.Utils.Common
1216

1317
createInsurance ::
@@ -20,3 +24,20 @@ createInsurance ::
2024
m InsuranceResponse
2125
createInsurance serviceConfig req = case serviceConfig of
2226
AckoInsuranceConfig cfg -> Acko.createInsurance cfg req
27+
IffcoTokioInsuranceConfig _ -> throwError $ InternalError "createInsurance is not supported for IffcoTokio; use registerHomeDeclaration instead"
28+
29+
-- | Fire-and-return home insurance declaration for IFFCO Tokio.
30+
-- The call is executed in a background thread; the caller receives
31+
-- the correlation |invoiceRequestNumber| immediately and must handle
32+
-- the async result inside the |onComplete| callback (e.g. by persisting it).
33+
registerHomeDeclaration ::
34+
( EncFlow m r,
35+
CoreMetrics m,
36+
HasRequestId r,
37+
Forkable m
38+
) =>
39+
IffcoTokioTypes.IffcoTokioConfig ->
40+
HomeDeclarationReq ->
41+
(Either Text HomeDeclarationAsyncResp -> m ()) ->
42+
m HomeDeclarationInstantResp
43+
registerHomeDeclaration = IffcoTokio.registerHomeDeclaration
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module Kernel.External.Insurance.Interface.IffcoTokio where
2+
3+
import Kernel.External.Encryption (EncFlow)
4+
import qualified Kernel.External.Insurance.IffcoTokio.Flow as IffcoFlow
5+
import qualified Kernel.External.Insurance.IffcoTokio.Types as IffcoTypes
6+
import Kernel.External.Insurance.Interface.Types
7+
import Kernel.Prelude
8+
import qualified Kernel.Tools.Metrics.CoreMetrics as Metrics
9+
import Kernel.Types.Forkable (Forkable (..))
10+
import Kernel.Utils.Common (MonadFlow)
11+
import Kernel.Utils.Logging (logError, logInfo)
12+
import Kernel.Utils.Servant.Client (HasRequestId)
13+
14+
registerHomeDeclaration ::
15+
( Metrics.CoreMetrics m,
16+
EncFlow m r,
17+
MonadFlow m,
18+
HasRequestId r,
19+
MonadReader r m,
20+
Forkable m
21+
) =>
22+
IffcoTypes.IffcoTokioConfig ->
23+
HomeDeclarationReq ->
24+
(Either Text HomeDeclarationAsyncResp -> m ()) ->
25+
m HomeDeclarationInstantResp
26+
registerHomeDeclaration config req onComplete = do
27+
fork "iffco-Tokio-register-home-declaration" $ do
28+
logInfo $
29+
"IffcoTokio: firing RegisterHomeDeclaration for invoiceRequestNumber="
30+
<> req.invoiceRequestNumber
31+
result <- IffcoFlow.registerHomeDeclarationEither config (toIffcoReq req)
32+
case result of
33+
Left err -> do
34+
logError $
35+
"IffcoTokio: API call failed for invoiceRequestNumber="
36+
<> req.invoiceRequestNumber
37+
<> " error="
38+
<> err
39+
onComplete (Left err)
40+
Right resp -> do
41+
logInfo $
42+
"IffcoTokio: received response for invoiceRequestNumber="
43+
<> req.invoiceRequestNumber
44+
<> " status="
45+
<> resp.status
46+
onComplete (Right $ fromIffcoResp req.invoiceRequestNumber resp)
47+
return HomeDeclarationInstantResp {invoiceRequestNumber = req.invoiceRequestNumber}
48+
49+
toIffcoReq :: HomeDeclarationReq -> IffcoTypes.HomeDeclarationReq
50+
toIffcoReq req =
51+
IffcoTypes.HomeDeclarationReq
52+
{ insuredAddress = req.insuredAddress,
53+
insuredEmail = req.insuredEmail,
54+
insuredMobile = req.insuredMobile,
55+
insuredName = req.insuredName,
56+
invoiceDate = req.invoiceDate,
57+
invoiceRequestNumber = req.invoiceRequestNumber,
58+
ewCommencesOn = req.ewCommencesOn
59+
}
60+
61+
fromIffcoResp :: Text -> IffcoTypes.HomeDeclarationResp -> HomeDeclarationAsyncResp
62+
fromIffcoResp invoiceReqNum resp =
63+
HomeDeclarationAsyncResp
64+
{ invoiceRequestNumber = invoiceReqNum,
65+
certificateNumber = resp.certificateNumber,
66+
declarationId = resp.declarationId,
67+
status = resp.status
68+
}

0 commit comments

Comments
 (0)