Skip to content

Commit 76045e3

Browse files
Abhishek ChorotiyaAbhishek Chorotiya
authored andcommitted
feat: integration for redsys 3ds
1 parent 60850dd commit 76045e3

File tree

7 files changed

+264
-2
lines changed

7 files changed

+264
-2
lines changed

src/App.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ let make = () => {
6767
</div>
6868
| "qrData" => <QRCodeDisplay />
6969
| "3dsAuth" => <ThreeDSAuth />
70+
| "redsys3ds" => <Redsys3ds />
7071
| "3ds" => <ThreeDSMethod />
7172
| "voucherData" => <VoucherDisplay />
7273
| "preMountLoader" => {

src/Redsys3ds.res

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
open Utils
2+
3+
@react.component
4+
let make = () => {
5+
let logger = HyperLogger.make(~source=Elements(Payment))
6+
let isConfirmCalled = React.useRef(false)
7+
let eventsToSendToParent = ["confirmParams", "poll_status", "openurl_if_required"]
8+
let completeAuthorize = PaymentHelpers.useRedsysCompleteAuthorize(Some(logger))
9+
let handleConfirmCall = (threeDsMethodComp, paymentIntentId, publishableKey, headers) => {
10+
Console.log2("state", threeDsMethodComp)
11+
let body = [
12+
("client_secret", paymentIntentId->JSON.Encode.string),
13+
("threeds_method_comp_ind", threeDsMethodComp->JSON.Encode.string),
14+
]
15+
completeAuthorize(
16+
~bodyArr=body,
17+
~confirmParam={
18+
return_url: "http://localhost:9060",
19+
publishableKey,
20+
},
21+
~headers,
22+
~iframeId="redsys3ds",
23+
~clientSecret=Some(paymentIntentId),
24+
)
25+
}
26+
27+
eventsToSendToParent->UtilityHooks.useSendEventsToParent
28+
29+
React.useEffect0(() => {
30+
messageParentWindow([("iframeMountedCallback", true->JSON.Encode.bool)])
31+
let handle = (ev: Window.event) => {
32+
try {
33+
let json = ev.data->safeParse
34+
Console.log(ev.data)
35+
Console.log2("Redsys3ds", ev.data)
36+
let dict = json->getDictFromJson
37+
if dict->Dict.get("fullScreenIframeMounted")->Option.isSome {
38+
let metadata = dict->getJsonObjectFromDict("metadata")
39+
let metaDataDict = metadata->JSON.Decode.object->Option.getOr(Dict.make())
40+
let paymentIntentId = metaDataDict->getString("paymentIntentId", "")
41+
let publishableKey = metaDataDict->getString("publishableKey", "")
42+
logger.setClientSecret(paymentIntentId)
43+
logger.setMerchantId(publishableKey)
44+
let headersDict =
45+
metaDataDict
46+
->getJsonObjectFromDict("headers")
47+
->JSON.Decode.object
48+
->Option.getOr(Dict.make())
49+
50+
let headers =
51+
headersDict
52+
->Dict.toArray
53+
->Array.map(entries => {
54+
let (x, val) = entries
55+
(x, val->JSON.Decode.string->Option.getOr(""))
56+
})
57+
let iframeDataDict =
58+
metaDataDict
59+
->Dict.get("iframeData")
60+
->Option.flatMap(JSON.Decode.object)
61+
->Option.getOr(Dict.make())
62+
let methodKey =
63+
iframeDataDict
64+
->Dict.get("method_key")
65+
->Option.flatMap(json => Js.Json.decodeString(json))
66+
->Option.getOr("threeDSMethodData")
67+
68+
let threeDsMethodUrl =
69+
iframeDataDict
70+
->Dict.get("three_ds_method_url")
71+
->Belt.Option.flatMap(json => Js.Json.decodeString(json))
72+
->Option.getOr("")
73+
74+
let threeDsMethodData =
75+
iframeDataDict
76+
->Dict.get("three_ds_method_data")
77+
->Belt.Option.flatMap(json => Js.Json.decodeString(json))
78+
->Option.getOr("")
79+
80+
let threeDsIframe = CommonHooks.querySelector("#threeDsAuthFrame")
81+
let threeDsDiv = Window.querySelector("#threeDsDiv")
82+
83+
switch threeDsDiv->Nullable.toOption {
84+
| Some(elem) =>
85+
if threeDsMethodUrl !== "" {
86+
let form = elem->makeForm(threeDsMethodUrl, "threeDsHiddenPostMethod")
87+
let input = Types.createElement("input")
88+
input.name = encodeURIComponent(methodKey)
89+
input.value = encodeURIComponent(threeDsMethodData)
90+
form.target = "threeDsAuthFrame"
91+
form.appendChild(input)
92+
form.submit()
93+
}
94+
| None => ()
95+
}
96+
let id = setTimeout(() => {
97+
isConfirmCalled.current = true
98+
handleConfirmCall("N", paymentIntentId, publishableKey, headers)->ignore
99+
}, 10000)
100+
101+
switch threeDsIframe->Nullable.toOption {
102+
| Some(elem) =>
103+
elem->CommonHooks.addEventListener("load", _ => {
104+
clearTimeout(id)
105+
if !isConfirmCalled.current {
106+
handleConfirmCall("Y", paymentIntentId, publishableKey, headers)->ignore
107+
}
108+
})
109+
| None => ()
110+
}
111+
}
112+
} catch {
113+
| err => Console.log(err)
114+
}
115+
}
116+
Window.addEventListener("message", handle)
117+
Some(() => {Window.removeEventListener("message", handle)})
118+
})
119+
120+
<div id="threeDsDiv" className="max-w-1 max-h-1 opacity-0 fixed left-[-9999px]">
121+
<iframe id="threeDsAuthFrame" name="threeDsAuthFrame" title="3D Secure Authentication Frame" />
122+
</div>
123+
}

src/Types/PaymentConfirmTypes.res

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type nextAction = {
4545
next_action_data: option<JSON.t>,
4646
display_text: option<string>,
4747
border_color: option<string>,
48+
iframe_data: option<JSON.t>,
4849
}
4950
type intent = {
5051
nextAction: nextAction,
@@ -74,6 +75,7 @@ let defaultNextAction = {
7475
next_action_data: None,
7576
display_text: None,
7677
border_color: None,
78+
iframe_data: None,
7779
}
7880
let defaultIntent = {
7981
nextAction: defaultNextAction,
@@ -170,6 +172,11 @@ let getNextAction = (dict, str) => {
170172
next_action_data: Some(json->getDictFromDict("next_action_data")->JSON.Encode.object),
171173
display_text: json->getOptionString("display_text"),
172174
border_color: json->getOptionString("border_color"),
175+
iframe_data: Some(
176+
json
177+
->Dict.get("iframe_data")
178+
->Option.getOr(Dict.make()->JSON.Encode.object),
179+
),
173180
}
174181
})
175182
->Option.getOr(defaultNextAction)

src/Utilities/PaymentHelpers.res

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,6 @@ let rec intentCall = (
303303
) => {
304304
open Promise
305305
let isConfirm = uri->String.includes("/confirm")
306-
307306
let isCompleteAuthorize = uri->String.includes("/complete_authorize")
308307
let isPostSessionTokens = uri->String.includes("/post_session_tokens")
309308
let (eventName: HyperLogger.eventName, initEventName: HyperLogger.eventName) = switch (
@@ -332,6 +331,8 @@ let rec intentCall = (
332331
openUrl(url)
333332
}
334333
}
334+
Console.log("inside intent Call")
335+
335336
fetchApi(
336337
uri,
337338
~method=fetchMethod,
@@ -344,9 +345,13 @@ let rec intentCall = (
344345
url.searchParams.set("payment_intent_client_secret", clientSecret)
345346
url.searchParams.set("status", "failed")
346347
url.searchParams.set("payment_id", clientSecret->getPaymentId)
348+
Console.log("Message parent window")
349+
347350
messageParentWindow([("confirmParams", confirmParam->anyTypeToJson)])
348351

349352
if statusCode->String.charAt(0) !== "2" {
353+
Console.log2("statusCode !==2", statusCode)
354+
350355
res
351356
->Fetch.Response.json
352357
->then(data => {
@@ -469,9 +474,11 @@ let rec intentCall = (
469474
)->then(resolve)
470475
})
471476
} else {
477+
Console.log("status code ===2")
472478
res
473479
->Fetch.Response.json
474480
->then(data => {
481+
Console.log2("statusCode", statusCode)
475482
Promise.make(
476483
(resolve, _) => {
477484
logApi(
@@ -527,7 +534,10 @@ let rec intentCall = (
527534
}
528535

529536
if intent.status == "requires_customer_action" {
537+
Console.log("redirect to url")
530538
if intent.nextAction.type_ == "redirect_to_url" {
539+
Console.log2("redirect to url inside", intent.nextAction.redirectToUrl)
540+
531541
handleLogging(
532542
~optLogger,
533543
~value="",
@@ -654,6 +664,36 @@ let rec intentCall = (
654664
("metadata", metaData->JSON.Encode.object),
655665
])
656666
}
667+
} else if intent.nextAction.type_ === "invoke_hidden_iframe" {
668+
Console.log("Invoke hidden iframe")
669+
let iframeData =
670+
intent.nextAction.iframe_data
671+
->Option.flatMap(JSON.Decode.object)
672+
->Option.getOr(Dict.make())
673+
674+
let headerObj = Dict.make()
675+
headers->Array.forEach(
676+
entries => {
677+
let (x, val) = entries
678+
Dict.set(headerObj, x, val->JSON.Encode.string)
679+
},
680+
)
681+
let metaData =
682+
[
683+
("iframeData", iframeData->JSON.Encode.object),
684+
("paymentIntentId", clientSecret->JSON.Encode.string),
685+
("publishableKey", confirmParam.publishableKey->JSON.Encode.string),
686+
("headers", headerObj->JSON.Encode.object),
687+
("url", url.href->JSON.Encode.string),
688+
("iframeId", iframeId->JSON.Encode.string),
689+
]->Dict.fromArray
690+
691+
messageParentWindow([
692+
("fullscreen", true->JSON.Encode.bool),
693+
("param", `redsys3ds`->JSON.Encode.string),
694+
("iframeId", iframeId->JSON.Encode.string),
695+
("metadata", metaData->JSON.Encode.object),
696+
])
657697
} else if intent.nextAction.type_ === "display_voucher_information" {
658698
let voucherData = intent.nextAction.voucher_details->Option.getOr({
659699
download_url: "",
@@ -834,6 +874,8 @@ let rec intentCall = (
834874
}
835875
})
836876
->catch(err => {
877+
Console.log("fetch api fail")
878+
837879
Promise.make((resolve, _) => {
838880
try {
839881
let url = makeUrl(confirmParam.return_url)
@@ -916,6 +958,26 @@ let rec intentCall = (
916958
})
917959
}
918960

961+
// let redsys3dsAuth = (~clientSecret, ~threeDsMethodComp, ~headers) => {
962+
// let endpoint = ApiEndpoint.getApiEndPoint()
963+
// let paymentIntentID = String.split(clientSecret, "_secret_")[0]->Option.getOr("")
964+
// let url = `${endpoint}/payments/${paymentIntentID}/complete_authorize`
965+
// let body =
966+
// [
967+
// ("client_secret", clientSecret->JSON.Encode.string),
968+
// ("threeds_method_comp_ind", threeDsMethodComp->JSON.Encode.string),
969+
// ]->getJsonFromArrayOfJson
970+
// // open Promise
971+
// // fetchApi(url, ~method=#POST, ~bodyStr=body->JSON.stringify, ~headers=headers->Dict.fromArray)
972+
// // ->then(res => {
973+
// // Console.log2("completeAuthorize", res)
974+
// // resolve(res)
975+
// // })
976+
// // ->catch(err => reject(err))
977+
978+
// intentCall(~fetchApi, ~uri=url, ~headers, ~bodyStr=body->JSON.stringify)
979+
// }
980+
919981
let usePaymentSync = (optLogger: option<HyperLogger.loggerMake>, paymentType: payment) => {
920982
open RecoilAtoms
921983
let paymentMethodList = Recoil.useRecoilValueFromAtom(paymentMethodList)
@@ -1177,6 +1239,67 @@ let usePaymentIntent = (optLogger, paymentType) => {
11771239
}
11781240
}
11791241

1242+
let useRedsysCompleteAuthorize = (optLogger: option<HyperLogger.loggerMake>) => {
1243+
open RecoilAtoms
1244+
let customPodUri = Recoil.useRecoilValueFromAtom(customPodUri)
1245+
let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled)
1246+
let isCallbackUsedVal = Recoil.useRecoilValueFromAtom(RecoilAtoms.isCompleteCallbackUsed)
1247+
let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom)
1248+
1249+
(
1250+
~handleUserError=false,
1251+
~bodyArr: array<(string, JSON.t)>,
1252+
~confirmParam: ConfirmType.confirmParams,
1253+
~iframeId="redsys3ds",
1254+
~clientSecret,
1255+
~headers,
1256+
) => {
1257+
Console.log("intentCall")
1258+
switch clientSecret {
1259+
| Some(clientSecret) =>
1260+
let paymentIntentID = clientSecret->getPaymentId
1261+
Console.log("some clientSecret")
1262+
let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey=confirmParam.publishableKey)
1263+
let uri = `${endpoint}/payments/${paymentIntentID}/complete_authorize`
1264+
1265+
let bodyStr =
1266+
[("client_secret", clientSecret->JSON.Encode.string)]
1267+
->Array.concatMany([bodyArr])
1268+
->getJsonFromArrayOfJson
1269+
->JSON.stringify
1270+
1271+
let completeAuthorize = () => {
1272+
intentCall(
1273+
~fetchApi,
1274+
~uri,
1275+
~headers,
1276+
~bodyStr,
1277+
~confirmParam: ConfirmType.confirmParams,
1278+
~clientSecret,
1279+
~optLogger,
1280+
~handleUserError,
1281+
~paymentType=Redsys3ds,
1282+
~iframeId,
1283+
~fetchMethod=#POST,
1284+
~setIsManualRetryEnabled,
1285+
~customPodUri,
1286+
~sdkHandleOneClickConfirmPayment=false,
1287+
~counter=0,
1288+
~isCallbackUsedVal,
1289+
~redirectionFlags,
1290+
)->ignore
1291+
}
1292+
1293+
completeAuthorize()
1294+
1295+
| None =>
1296+
postFailedSubmitResponse(
1297+
~errortype="complete_authorize_failed",
1298+
~message="Complete Authorize Failed. Try Again!",
1299+
)
1300+
}
1301+
}
1302+
}
11801303
let useCompleteAuthorize = (optLogger: option<HyperLogger.loggerMake>, paymentType: payment) => {
11811304
open RecoilAtoms
11821305
let paymentMethodList = Recoil.useRecoilValueFromAtom(paymentMethodList)

src/Utilities/PaymentHelpersTypes.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type payment =
88
| Paypal
99
| Samsungpay
1010
| Paze
11+
| Redsys3ds
1112
| Other
1213

1314
type paymentIntent = (

src/Utilities/Utils.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ let getHeaders = (~uri=?, ~token=?, ~headers=Dict.make()) => {
837837
("X-Browser-Name", HyperLogger.arrayOfNameAndVersion->Array.get(0)->Option.getOr("Others")),
838838
("X-Browser-Version", HyperLogger.arrayOfNameAndVersion->Array.get(1)->Option.getOr("0")),
839839
("X-Client-Platform", "web"),
840+
("ngrok-skip-browser-warning", "true"),
840841
]->Dict.fromArray
841842

842843
switch (token, uri) {

0 commit comments

Comments
 (0)