@@ -18,22 +18,23 @@ use hyperswitch_domain_models::{
18
18
access_token_auth:: AccessTokenAuth ,
19
19
payments:: { Authorize , Capture , PSync , PaymentMethodToken , Session , SetupMandate , Void } ,
20
20
refunds:: { Execute , RSync } ,
21
- AuthorizeSessionToken , CompleteAuthorize , PreProcessing ,
21
+ AuthorizeSessionToken , CompleteAuthorize , PostCaptureVoid , PreProcessing ,
22
22
} ,
23
23
router_request_types:: {
24
24
AccessTokenRequestData , AuthorizeSessionTokenData , CompleteAuthorizeData ,
25
25
PaymentMethodTokenizationData , PaymentsAuthorizeData , PaymentsCancelData ,
26
- PaymentsCaptureData , PaymentsPreProcessingData , PaymentsSessionData , PaymentsSyncData ,
27
- RefundsData , SetupMandateRequestData ,
26
+ PaymentsCancelPostCaptureData , PaymentsCaptureData , PaymentsPreProcessingData ,
27
+ PaymentsSessionData , PaymentsSyncData , RefundsData , SetupMandateRequestData ,
28
28
} ,
29
29
router_response_types:: {
30
30
ConnectorInfo , PaymentMethodDetails , PaymentsResponseData , RefundsResponseData ,
31
31
SupportedPaymentMethods , SupportedPaymentMethodsExt ,
32
32
} ,
33
33
types:: {
34
34
PaymentsAuthorizeRouterData , PaymentsAuthorizeSessionTokenRouterData ,
35
- PaymentsCancelRouterData , PaymentsCaptureRouterData , PaymentsCompleteAuthorizeRouterData ,
36
- PaymentsPreProcessingRouterData , PaymentsSyncRouterData , RefundsRouterData ,
35
+ PaymentsCancelPostCaptureRouterData , PaymentsCancelRouterData , PaymentsCaptureRouterData ,
36
+ PaymentsCompleteAuthorizeRouterData , PaymentsPreProcessingRouterData ,
37
+ PaymentsSyncRouterData , RefundsRouterData ,
37
38
} ,
38
39
} ;
39
40
use hyperswitch_interfaces:: {
@@ -131,7 +132,7 @@ impl api::RefundSync for Nuvei {}
131
132
impl api:: PaymentsCompleteAuthorize for Nuvei { }
132
133
impl api:: ConnectorAccessToken for Nuvei { }
133
134
impl api:: PaymentsPreProcessing for Nuvei { }
134
-
135
+ impl api :: PaymentPostCaptureVoid for Nuvei { }
135
136
impl ConnectorIntegration < SetupMandate , SetupMandateRequestData , PaymentsResponseData > for Nuvei {
136
137
fn build_request (
137
138
& self ,
@@ -175,7 +176,6 @@ impl ConnectorIntegration<CompleteAuthorize, CompleteAuthorizeData, PaymentsResp
175
176
) -> CustomResult < RequestContent , errors:: ConnectorError > {
176
177
let meta: nuvei:: NuveiMeta = utils:: to_connector_meta ( req. request . connector_meta . clone ( ) ) ?;
177
178
let connector_req = nuvei:: NuveiPaymentsRequest :: try_from ( ( req, meta. session_token ) ) ?;
178
-
179
179
Ok ( RequestContent :: Json ( Box :: new ( connector_req) ) )
180
180
}
181
181
fn build_request (
@@ -209,7 +209,6 @@ impl ConnectorIntegration<CompleteAuthorize, CompleteAuthorizeData, PaymentsResp
209
209
. response
210
210
. parse_struct ( "NuveiPaymentsResponse" )
211
211
. switch ( ) ?;
212
-
213
212
event_builder. map ( |i| i. set_response_body ( & response) ) ;
214
213
router_env:: logger:: info!( connector_response=?response) ;
215
214
@@ -309,6 +308,90 @@ impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Nu
309
308
}
310
309
}
311
310
311
+ impl ConnectorIntegration < PostCaptureVoid , PaymentsCancelPostCaptureData , PaymentsResponseData >
312
+ for Nuvei
313
+ {
314
+ fn get_headers (
315
+ & self ,
316
+ req : & PaymentsCancelPostCaptureRouterData ,
317
+ connectors : & Connectors ,
318
+ ) -> CustomResult < Vec < ( String , masking:: Maskable < String > ) > , errors:: ConnectorError > {
319
+ self . build_headers ( req, connectors)
320
+ }
321
+
322
+ fn get_content_type ( & self ) -> & ' static str {
323
+ self . common_get_content_type ( )
324
+ }
325
+
326
+ fn get_url (
327
+ & self ,
328
+ _req : & PaymentsCancelPostCaptureRouterData ,
329
+ connectors : & Connectors ,
330
+ ) -> CustomResult < String , errors:: ConnectorError > {
331
+ Ok ( format ! (
332
+ "{}ppp/api/v1/voidTransaction.do" ,
333
+ ConnectorCommon :: base_url( self , connectors)
334
+ ) )
335
+ }
336
+
337
+ fn get_request_body (
338
+ & self ,
339
+ req : & PaymentsCancelPostCaptureRouterData ,
340
+ _connectors : & Connectors ,
341
+ ) -> CustomResult < RequestContent , errors:: ConnectorError > {
342
+ let connector_req = nuvei:: NuveiVoidRequest :: try_from ( req) ?;
343
+ Ok ( RequestContent :: Json ( Box :: new ( connector_req) ) )
344
+ }
345
+
346
+ fn build_request (
347
+ & self ,
348
+ req : & PaymentsCancelPostCaptureRouterData ,
349
+ connectors : & Connectors ,
350
+ ) -> CustomResult < Option < Request > , errors:: ConnectorError > {
351
+ let request = RequestBuilder :: new ( )
352
+ . method ( Method :: Post )
353
+ . url ( & types:: PaymentsPostCaptureVoidType :: get_url (
354
+ self , req, connectors,
355
+ ) ?)
356
+ . attach_default_headers ( )
357
+ . headers ( types:: PaymentsPostCaptureVoidType :: get_headers (
358
+ self , req, connectors,
359
+ ) ?)
360
+ . set_body ( types:: PaymentsPostCaptureVoidType :: get_request_body (
361
+ self , req, connectors,
362
+ ) ?)
363
+ . build ( ) ;
364
+ Ok ( Some ( request) )
365
+ }
366
+
367
+ fn handle_response (
368
+ & self ,
369
+ data : & PaymentsCancelPostCaptureRouterData ,
370
+ event_builder : Option < & mut ConnectorEvent > ,
371
+ res : Response ,
372
+ ) -> CustomResult < PaymentsCancelPostCaptureRouterData , errors:: ConnectorError > {
373
+ let response: nuvei:: NuveiPaymentsResponse = res
374
+ . response
375
+ . parse_struct ( "NuveiPaymentsResponse" )
376
+ . switch ( ) ?;
377
+ event_builder. map ( |i| i. set_response_body ( & response) ) ;
378
+ router_env:: logger:: info!( connector_response=?response) ;
379
+ RouterData :: try_from ( ResponseRouterData {
380
+ response,
381
+ data : data. clone ( ) ,
382
+ http_code : res. status_code ,
383
+ } )
384
+ . change_context ( errors:: ConnectorError :: ResponseHandlingFailed )
385
+ }
386
+
387
+ fn get_error_response (
388
+ & self ,
389
+ res : Response ,
390
+ event_builder : Option < & mut ConnectorEvent > ,
391
+ ) -> CustomResult < ErrorResponse , errors:: ConnectorError > {
392
+ self . build_error_response ( res, event_builder)
393
+ }
394
+ }
312
395
impl ConnectorIntegration < AccessTokenAuth , AccessTokenRequestData , AccessToken > for Nuvei { }
313
396
314
397
impl ConnectorIntegration < PSync , PaymentsSyncData , PaymentsResponseData > for Nuvei {
@@ -509,7 +592,6 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
509
592
_connectors : & Connectors ,
510
593
) -> CustomResult < RequestContent , errors:: ConnectorError > {
511
594
let connector_req = nuvei:: NuveiPaymentsRequest :: try_from ( ( req, req. get_session_token ( ) ?) ) ?;
512
-
513
595
Ok ( RequestContent :: Json ( Box :: new ( connector_req) ) )
514
596
}
515
597
@@ -545,7 +627,6 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
545
627
. response
546
628
. parse_struct ( "NuveiPaymentsResponse" )
547
629
. switch ( ) ?;
548
-
549
630
event_builder. map ( |i| i. set_response_body ( & response) ) ;
550
631
router_env:: logger:: info!( connector_response=?response) ;
551
632
@@ -683,7 +764,6 @@ impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResp
683
764
_connectors : & Connectors ,
684
765
) -> CustomResult < RequestContent , errors:: ConnectorError > {
685
766
let connector_req = nuvei:: NuveiPaymentsRequest :: try_from ( ( req, req. get_session_token ( ) ?) ) ?;
686
-
687
767
Ok ( RequestContent :: Json ( Box :: new ( connector_req) ) )
688
768
}
689
769
@@ -719,7 +799,6 @@ impl ConnectorIntegration<PreProcessing, PaymentsPreProcessingData, PaymentsResp
719
799
. response
720
800
. parse_struct ( "NuveiPaymentsResponse" )
721
801
. switch ( ) ?;
722
-
723
802
event_builder. map ( |i| i. set_response_body ( & response) ) ;
724
803
router_env:: logger:: info!( connector_response=?response) ;
725
804
@@ -802,7 +881,6 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Nuvei {
802
881
. response
803
882
. parse_struct ( "NuveiPaymentsResponse" )
804
883
. switch ( ) ?;
805
-
806
884
event_builder. map ( |i| i. set_response_body ( & response) ) ;
807
885
router_env:: logger:: info!( connector_response=?response) ;
808
886
@@ -849,59 +927,118 @@ impl IncomingWebhook for Nuvei {
849
927
_merchant_id : & id_type:: MerchantId ,
850
928
connector_webhook_secrets : & api_models:: webhooks:: ConnectorWebhookSecrets ,
851
929
) -> CustomResult < Vec < u8 > , errors:: ConnectorError > {
852
- let body = serde_urlencoded:: from_str :: < nuvei:: NuveiWebhookDetails > ( & request. query_params )
930
+ // Parse the webhook payload
931
+ let webhook = serde_urlencoded:: from_str :: < nuvei:: NuveiWebhook > ( & request. query_params )
853
932
. change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
933
+
854
934
let secret_str = std:: str:: from_utf8 ( & connector_webhook_secrets. secret )
855
935
. change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
856
- let status = format ! ( "{:?}" , body. status) . to_uppercase ( ) ;
857
- let to_sign = format ! (
858
- "{}{}{}{}{}{}{}" ,
859
- secret_str,
860
- body. total_amount,
861
- body. currency,
862
- body. response_time_stamp,
863
- body. ppp_transaction_id,
864
- status,
865
- body. product_id
866
- ) ;
867
- Ok ( to_sign. into_bytes ( ) )
936
+
937
+ // Generate signature based on webhook type
938
+ match webhook {
939
+ nuvei:: NuveiWebhook :: PaymentDmn ( notification) => {
940
+ // For payment DMNs, use the same format as before
941
+ let status = notification
942
+ . transaction_status
943
+ . as_ref ( )
944
+ . map ( |s| format ! ( "{s:?}" ) . to_uppercase ( ) )
945
+ . unwrap_or_else ( || "UNKNOWN" . to_string ( ) ) ;
946
+
947
+ let to_sign = transformers:: concat_strings ( & [
948
+ secret_str. to_string ( ) ,
949
+ notification. total_amount . unwrap_or_default ( ) ,
950
+ notification. currency . unwrap_or_default ( ) ,
951
+ notification. response_time_stamp . unwrap_or_default ( ) ,
952
+ notification. ppp_transaction_id . unwrap_or_default ( ) ,
953
+ status,
954
+ notification. product_id . unwrap_or_default ( ) ,
955
+ ] ) ;
956
+ Ok ( to_sign. into_bytes ( ) )
957
+ }
958
+ nuvei:: NuveiWebhook :: Chargeback ( notification) => {
959
+ // For chargeback notifications, use a different format based on Nuvei's documentation
960
+ // Note: This is a placeholder - you'll need to adjust based on Nuvei's actual chargeback signature format
961
+ let status = notification
962
+ . status
963
+ . as_ref ( )
964
+ . map ( |s| format ! ( "{s:?}" ) . to_uppercase ( ) )
965
+ . unwrap_or_else ( || "UNKNOWN" . to_string ( ) ) ;
966
+
967
+ let to_sign = transformers:: concat_strings ( & [
968
+ secret_str. to_string ( ) ,
969
+ notification. chargeback_amount . unwrap_or_default ( ) ,
970
+ notification. chargeback_currency . unwrap_or_default ( ) ,
971
+ notification. ppp_transaction_id . unwrap_or_default ( ) ,
972
+ status,
973
+ ] ) ;
974
+ Ok ( to_sign. into_bytes ( ) )
975
+ }
976
+ }
868
977
}
869
978
870
979
fn get_webhook_object_reference_id (
871
980
& self ,
872
981
request : & IncomingWebhookRequestDetails < ' _ > ,
873
982
) -> CustomResult < api_models:: webhooks:: ObjectReferenceId , errors:: ConnectorError > {
874
- let body =
875
- serde_urlencoded:: from_str :: < nuvei:: NuveiWebhookTransactionId > ( & request. query_params )
876
- . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
983
+ // Parse the webhook payload
984
+ let webhook = serde_urlencoded:: from_str :: < nuvei:: NuveiWebhook > ( & request. query_params )
985
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
986
+
987
+ // Extract transaction ID from the webhook
988
+ let transaction_id = match & webhook {
989
+ nuvei:: NuveiWebhook :: PaymentDmn ( notification) => {
990
+ notification. ppp_transaction_id . clone ( ) . unwrap_or_default ( )
991
+ }
992
+ nuvei:: NuveiWebhook :: Chargeback ( notification) => {
993
+ notification. ppp_transaction_id . clone ( ) . unwrap_or_default ( )
994
+ }
995
+ } ;
996
+
877
997
Ok ( api_models:: webhooks:: ObjectReferenceId :: PaymentId (
878
- PaymentIdType :: ConnectorTransactionId ( body . ppp_transaction_id ) ,
998
+ PaymentIdType :: ConnectorTransactionId ( transaction_id ) ,
879
999
) )
880
1000
}
881
1001
882
1002
fn get_webhook_event_type (
883
1003
& self ,
884
1004
request : & IncomingWebhookRequestDetails < ' _ > ,
885
1005
) -> CustomResult < IncomingWebhookEvent , errors:: ConnectorError > {
886
- let body =
887
- serde_urlencoded:: from_str :: < nuvei:: NuveiWebhookDataStatus > ( & request. query_params )
888
- . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
889
- match body. status {
890
- nuvei:: NuveiWebhookStatus :: Approved => Ok ( IncomingWebhookEvent :: PaymentIntentSuccess ) ,
891
- nuvei:: NuveiWebhookStatus :: Declined => Ok ( IncomingWebhookEvent :: PaymentIntentFailure ) ,
892
- nuvei:: NuveiWebhookStatus :: Unknown
893
- | nuvei:: NuveiWebhookStatus :: Pending
894
- | nuvei:: NuveiWebhookStatus :: Update => Ok ( IncomingWebhookEvent :: EventNotSupported ) ,
1006
+ // Parse the webhook payload
1007
+ let webhook = serde_urlencoded:: from_str :: < nuvei:: NuveiWebhook > ( & request. query_params )
1008
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1009
+
1010
+ // Map webhook type to event type
1011
+ match webhook {
1012
+ nuvei:: NuveiWebhook :: PaymentDmn ( notification) => {
1013
+ match notification. transaction_status {
1014
+ Some ( nuvei:: TransactionStatus :: Approved )
1015
+ | Some ( nuvei:: TransactionStatus :: Settled ) => {
1016
+ Ok ( IncomingWebhookEvent :: PaymentIntentSuccess )
1017
+ }
1018
+ Some ( nuvei:: TransactionStatus :: Declined )
1019
+ | Some ( nuvei:: TransactionStatus :: Error ) => {
1020
+ Ok ( IncomingWebhookEvent :: PaymentIntentFailure )
1021
+ }
1022
+ _ => Ok ( IncomingWebhookEvent :: EventNotSupported ) ,
1023
+ }
1024
+ }
1025
+ nuvei:: NuveiWebhook :: Chargeback ( _) => {
1026
+ // Chargeback notifications always map to dispute opened
1027
+ Ok ( IncomingWebhookEvent :: DisputeOpened )
1028
+ }
895
1029
}
896
1030
}
897
1031
898
1032
fn get_webhook_resource_object (
899
1033
& self ,
900
1034
request : & IncomingWebhookRequestDetails < ' _ > ,
901
1035
) -> CustomResult < Box < dyn masking:: ErasedMaskSerialize > , errors:: ConnectorError > {
902
- let body = serde_urlencoded:: from_str :: < nuvei:: NuveiWebhookDetails > ( & request. query_params )
1036
+ // Parse the webhook payload
1037
+ let webhook = serde_urlencoded:: from_str :: < nuvei:: NuveiWebhook > ( & request. query_params )
903
1038
. change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
904
- let payment_response = nuvei:: NuveiPaymentsResponse :: from ( body) ;
1039
+
1040
+ // Convert webhook to payments response
1041
+ let payment_response = nuvei:: NuveiPaymentsResponse :: from ( webhook) ;
905
1042
906
1043
Ok ( Box :: new ( payment_response) )
907
1044
}
0 commit comments