@@ -13,6 +13,7 @@ use common_utils::{
13
13
} ;
14
14
use error_stack:: ResultExt ;
15
15
use hyperswitch_domain_models:: {
16
+ payment_method_data:: PaymentMethodData ,
16
17
router_data:: { AccessToken , ConnectorAuthType , ErrorResponse , RouterData } ,
17
18
router_flow_types:: {
18
19
access_token_auth:: AccessTokenAuth ,
@@ -29,7 +30,7 @@ use hyperswitch_domain_models::{
29
30
types:: {
30
31
PaymentsAuthorizeRouterData , PaymentsCancelRouterData , PaymentsCaptureRouterData ,
31
32
PaymentsCompleteAuthorizeRouterData , PaymentsSyncRouterData , RefundExecuteRouterData ,
32
- RefundSyncRouterData , RefundsRouterData ,
33
+ RefundSyncRouterData , RefundsRouterData , SetupMandateRouterData ,
33
34
} ,
34
35
} ;
35
36
use hyperswitch_interfaces:: {
@@ -50,15 +51,17 @@ use requests::{
50
51
use response:: {
51
52
EventType , ResponseIdStr , WorldpayErrorResponse , WorldpayEventResponse ,
52
53
WorldpayPaymentsResponse , WorldpayWebhookEventType , WorldpayWebhookTransactionId ,
54
+ WP_CORRELATION_ID ,
53
55
} ;
54
- use transformers:: { self as worldpay, WP_CORRELATION_ID } ;
56
+ use ring:: hmac;
57
+ use transformers:: { self as worldpay} ;
55
58
56
59
use crate :: {
57
60
constants:: headers,
58
61
types:: ResponseRouterData ,
59
62
utils:: {
60
63
construct_not_implemented_error_report, convert_amount, get_header_key_value,
61
- ForeignTryFrom , RefundsRequestData ,
64
+ is_mandate_supported , ForeignTryFrom , PaymentMethodDataType , RefundsRequestData ,
62
65
} ,
63
66
} ;
64
67
@@ -171,6 +174,19 @@ impl ConnectorValidation for Worldpay {
171
174
) ,
172
175
}
173
176
}
177
+
178
+ fn validate_mandate_payment (
179
+ & self ,
180
+ pm_type : Option < enums:: PaymentMethodType > ,
181
+ pm_data : PaymentMethodData ,
182
+ ) -> CustomResult < ( ) , errors:: ConnectorError > {
183
+ let mandate_supported_pmd = std:: collections:: HashSet :: from ( [ PaymentMethodDataType :: Card ] ) ;
184
+ is_mandate_supported ( pm_data. clone ( ) , pm_type, mandate_supported_pmd, self . id ( ) )
185
+ }
186
+
187
+ fn is_webhook_source_verification_mandatory ( & self ) -> bool {
188
+ true
189
+ }
174
190
}
175
191
176
192
impl api:: Payment for Worldpay { }
@@ -179,15 +195,108 @@ impl api::MandateSetup for Worldpay {}
179
195
impl ConnectorIntegration < SetupMandate , SetupMandateRequestData , PaymentsResponseData >
180
196
for Worldpay
181
197
{
182
- fn build_request (
198
+ fn get_headers (
199
+ & self ,
200
+ req : & SetupMandateRouterData ,
201
+ connectors : & Connectors ,
202
+ ) -> CustomResult < Vec < ( String , masking:: Maskable < String > ) > , errors:: ConnectorError > {
203
+ self . build_headers ( req, connectors)
204
+ }
205
+
206
+ fn get_content_type ( & self ) -> & ' static str {
207
+ self . common_get_content_type ( )
208
+ }
209
+
210
+ fn get_url (
211
+ & self ,
212
+ _req : & SetupMandateRouterData ,
213
+ connectors : & Connectors ,
214
+ ) -> CustomResult < String , errors:: ConnectorError > {
215
+ Ok ( format ! ( "{}api/payments" , self . base_url( connectors) ) )
216
+ }
217
+
218
+ fn get_request_body (
183
219
& self ,
184
- _req : & RouterData < SetupMandate , SetupMandateRequestData , PaymentsResponseData > ,
220
+ req : & SetupMandateRouterData ,
185
221
_connectors : & Connectors ,
222
+ ) -> CustomResult < RequestContent , errors:: ConnectorError > {
223
+ let auth = worldpay:: WorldpayAuthType :: try_from ( & req. connector_auth_type )
224
+ . change_context ( errors:: ConnectorError :: FailedToObtainAuthType ) ?;
225
+ let connector_router_data = worldpay:: WorldpayRouterData :: try_from ( (
226
+ & self . get_currency_unit ( ) ,
227
+ req. request . currency ,
228
+ req. request . minor_amount . unwrap_or_default ( ) ,
229
+ req,
230
+ ) ) ?;
231
+ let connector_req =
232
+ WorldpayPaymentsRequest :: try_from ( ( & connector_router_data, & auth. entity_id ) ) ?;
233
+
234
+ Ok ( RequestContent :: Json ( Box :: new ( connector_req) ) )
235
+ }
236
+
237
+ fn build_request (
238
+ & self ,
239
+ req : & SetupMandateRouterData ,
240
+ connectors : & Connectors ,
186
241
) -> CustomResult < Option < Request > , errors:: ConnectorError > {
187
- Err (
188
- errors:: ConnectorError :: NotImplemented ( "Setup Mandate flow for Worldpay" . to_string ( ) )
189
- . into ( ) ,
190
- )
242
+ Ok ( Some (
243
+ RequestBuilder :: new ( )
244
+ . method ( Method :: Post )
245
+ . url ( & types:: SetupMandateType :: get_url ( self , req, connectors) ?)
246
+ . attach_default_headers ( )
247
+ . headers ( types:: SetupMandateType :: get_headers ( self , req, connectors) ?)
248
+ . set_body ( types:: SetupMandateType :: get_request_body (
249
+ self , req, connectors,
250
+ ) ?)
251
+ . build ( ) ,
252
+ ) )
253
+ }
254
+
255
+ fn handle_response (
256
+ & self ,
257
+ data : & SetupMandateRouterData ,
258
+ event_builder : Option < & mut ConnectorEvent > ,
259
+ res : Response ,
260
+ ) -> CustomResult < SetupMandateRouterData , errors:: ConnectorError > {
261
+ let response: WorldpayPaymentsResponse = res
262
+ . response
263
+ . parse_struct ( "Worldpay PaymentsResponse" )
264
+ . change_context ( errors:: ConnectorError :: ResponseDeserializationFailed ) ?;
265
+
266
+ event_builder. map ( |i| i. set_response_body ( & response) ) ;
267
+ router_env:: logger:: info!( connector_response=?response) ;
268
+ let optional_correlation_id = res. headers . and_then ( |headers| {
269
+ headers
270
+ . get ( WP_CORRELATION_ID )
271
+ . and_then ( |header_value| header_value. to_str ( ) . ok ( ) )
272
+ . map ( |id| id. to_string ( ) )
273
+ } ) ;
274
+
275
+ RouterData :: foreign_try_from ( (
276
+ ResponseRouterData {
277
+ response,
278
+ data : data. clone ( ) ,
279
+ http_code : res. status_code ,
280
+ } ,
281
+ optional_correlation_id,
282
+ ) )
283
+ . change_context ( errors:: ConnectorError :: ResponseHandlingFailed )
284
+ }
285
+
286
+ fn get_error_response (
287
+ & self ,
288
+ res : Response ,
289
+ event_builder : Option < & mut ConnectorEvent > ,
290
+ ) -> CustomResult < ErrorResponse , errors:: ConnectorError > {
291
+ self . build_error_response ( res, event_builder)
292
+ }
293
+
294
+ fn get_5xx_error_response (
295
+ & self ,
296
+ res : Response ,
297
+ event_builder : Option < & mut ConnectorEvent > ,
298
+ ) -> CustomResult < ErrorResponse , errors:: ConnectorError > {
299
+ self . build_error_response ( res, event_builder)
191
300
}
192
301
}
193
302
@@ -401,6 +510,7 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Wor
401
510
enums:: AttemptStatus :: Authorizing
402
511
| enums:: AttemptStatus :: Authorized
403
512
| enums:: AttemptStatus :: CaptureInitiated
513
+ | enums:: AttemptStatus :: Charged
404
514
| enums:: AttemptStatus :: Pending
405
515
| enums:: AttemptStatus :: VoidInitiated ,
406
516
EventType :: Authorized ,
@@ -587,6 +697,7 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
587
697
. change_context ( errors:: ConnectorError :: FailedToObtainAuthType ) ?;
588
698
let connector_req =
589
699
WorldpayPaymentsRequest :: try_from ( ( & connector_router_data, & auth. entity_id ) ) ?;
700
+
590
701
Ok ( RequestContent :: Json ( Box :: new ( connector_req) ) )
591
702
}
592
703
@@ -739,7 +850,7 @@ impl ConnectorIntegration<CompleteAuthorize, CompleteAuthorizeData, PaymentsResp
739
850
router_env:: logger:: info!( connector_response=?response) ;
740
851
let optional_correlation_id = res. headers . and_then ( |headers| {
741
852
headers
742
- . get ( "WP-CorrelationId" )
853
+ . get ( WP_CORRELATION_ID )
743
854
. and_then ( |header_value| header_value. to_str ( ) . ok ( ) )
744
855
. map ( |id| id. to_string ( ) )
745
856
} ) ;
@@ -994,17 +1105,45 @@ impl IncomingWebhook for Worldpay {
994
1105
& self ,
995
1106
request : & IncomingWebhookRequestDetails < ' _ > ,
996
1107
_merchant_id : & common_utils:: id_type:: MerchantId ,
997
- connector_webhook_secrets : & api_models:: webhooks:: ConnectorWebhookSecrets ,
1108
+ _connector_webhook_secrets : & api_models:: webhooks:: ConnectorWebhookSecrets ,
998
1109
) -> CustomResult < Vec < u8 > , errors:: ConnectorError > {
999
- let secret_str = std:: str:: from_utf8 ( & connector_webhook_secrets. secret )
1000
- . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1001
- let to_sign = format ! (
1002
- "{}{}" ,
1003
- secret_str,
1004
- std:: str :: from_utf8( request. body)
1005
- . change_context( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?
1006
- ) ;
1007
- Ok ( to_sign. into_bytes ( ) )
1110
+ Ok ( request. body . to_vec ( ) )
1111
+ }
1112
+
1113
+ async fn verify_webhook_source (
1114
+ & self ,
1115
+ request : & IncomingWebhookRequestDetails < ' _ > ,
1116
+ merchant_id : & common_utils:: id_type:: MerchantId ,
1117
+ connector_webhook_details : Option < common_utils:: pii:: SecretSerdeValue > ,
1118
+ _connector_account_details : crypto:: Encryptable < masking:: Secret < serde_json:: Value > > ,
1119
+ connector_label : & str ,
1120
+ ) -> CustomResult < bool , errors:: ConnectorError > {
1121
+ let connector_webhook_secrets = self
1122
+ . get_webhook_source_verification_merchant_secret (
1123
+ merchant_id,
1124
+ connector_label,
1125
+ connector_webhook_details,
1126
+ )
1127
+ . await
1128
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1129
+ let signature = self
1130
+ . get_webhook_source_verification_signature ( request, & connector_webhook_secrets)
1131
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1132
+ let message = self
1133
+ . get_webhook_source_verification_message (
1134
+ request,
1135
+ merchant_id,
1136
+ & connector_webhook_secrets,
1137
+ )
1138
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1139
+ let secret_key = hex:: decode ( connector_webhook_secrets. secret )
1140
+ . change_context ( errors:: ConnectorError :: WebhookVerificationSecretInvalid ) ?;
1141
+
1142
+ let signing_key = hmac:: Key :: new ( hmac:: HMAC_SHA256 , & secret_key) ;
1143
+ let signed_message = hmac:: sign ( & signing_key, & message) ;
1144
+ let computed_signature = hex:: encode ( signed_message. as_ref ( ) ) ;
1145
+
1146
+ Ok ( computed_signature. as_bytes ( ) == hex:: encode ( signature) . as_bytes ( ) )
1008
1147
}
1009
1148
1010
1149
fn get_webhook_object_reference_id (
0 commit comments