1
1
pub mod braintree_graphql_transformers;
2
2
pub mod transformers;
3
+ use std:: { fmt:: Debug , str:: FromStr } ;
3
4
4
- use std:: fmt:: Debug ;
5
-
5
+ use api_models:: webhooks:: IncomingWebhookEvent ;
6
+ use base64:: Engine ;
7
+ use common_utils:: { crypto, ext_traits:: XmlExt } ;
6
8
use diesel_models:: enums;
7
9
use error_stack:: { IntoReport , Report , ResultExt } ;
8
- use masking:: PeekInterface ;
10
+ use masking:: { ExposeInterface , PeekInterface } ;
11
+ use ring:: hmac;
12
+ use sha1:: { Digest , Sha1 } ;
9
13
10
14
use self :: transformers as braintree;
11
15
use super :: utils:: PaymentsAuthorizeRequestData ;
@@ -26,6 +30,8 @@ use crate::{
26
30
types:: {
27
31
self ,
28
32
api:: { self , ConnectorCommon , ConnectorCommonExt } ,
33
+ domain,
34
+ transformers:: ForeignFrom ,
29
35
ErrorResponse ,
30
36
} ,
31
37
utils:: { self , BytesExt } ,
@@ -1264,28 +1270,228 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
1264
1270
1265
1271
#[ async_trait:: async_trait]
1266
1272
impl api:: IncomingWebhook for Braintree {
1273
+ fn get_webhook_source_verification_algorithm (
1274
+ & self ,
1275
+ _request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1276
+ ) -> CustomResult < Box < dyn crypto:: VerifySignature + Send > , errors:: ConnectorError > {
1277
+ Ok ( Box :: new ( crypto:: HmacSha1 ) )
1278
+ }
1279
+
1280
+ fn get_webhook_source_verification_signature (
1281
+ & self ,
1282
+ request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1283
+ connector_webhook_secrets : & api_models:: webhooks:: ConnectorWebhookSecrets ,
1284
+ ) -> CustomResult < Vec < u8 > , errors:: ConnectorError > {
1285
+ let notif_item = get_webhook_object_from_body ( request. body )
1286
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1287
+
1288
+ let signature_pairs: Vec < ( & str , & str ) > = notif_item
1289
+ . bt_signature
1290
+ . split ( '&' )
1291
+ . collect :: < Vec < & str > > ( )
1292
+ . into_iter ( )
1293
+ . map ( |pair| pair. split_once ( '|' ) . unwrap_or ( ( "" , "" ) ) )
1294
+ . collect :: < Vec < ( _ , _ ) > > ( ) ;
1295
+
1296
+ let merchant_secret = connector_webhook_secrets
1297
+ . additional_secret //public key
1298
+ . clone ( )
1299
+ . ok_or ( errors:: ConnectorError :: WebhookVerificationSecretNotFound ) ?;
1300
+
1301
+ let signature = get_matching_webhook_signature ( signature_pairs, merchant_secret. expose ( ) )
1302
+ . ok_or ( errors:: ConnectorError :: WebhookSignatureNotFound ) ?;
1303
+ Ok ( signature. as_bytes ( ) . to_vec ( ) )
1304
+ }
1305
+
1306
+ fn get_webhook_source_verification_message (
1307
+ & self ,
1308
+ request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1309
+ _merchant_id : & str ,
1310
+ _connector_webhook_secrets : & api_models:: webhooks:: ConnectorWebhookSecrets ,
1311
+ ) -> CustomResult < Vec < u8 > , errors:: ConnectorError > {
1312
+ let notify = get_webhook_object_from_body ( request. body )
1313
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1314
+
1315
+ let message = notify. bt_payload . to_string ( ) ;
1316
+
1317
+ Ok ( message. into_bytes ( ) )
1318
+ }
1319
+
1320
+ async fn verify_webhook_source (
1321
+ & self ,
1322
+ request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1323
+ merchant_account : & domain:: MerchantAccount ,
1324
+ merchant_connector_account : domain:: MerchantConnectorAccount ,
1325
+ connector_label : & str ,
1326
+ ) -> CustomResult < bool , errors:: ConnectorError > {
1327
+ let connector_webhook_secrets = self
1328
+ . get_webhook_source_verification_merchant_secret (
1329
+ merchant_account,
1330
+ connector_label,
1331
+ merchant_connector_account,
1332
+ )
1333
+ . await
1334
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1335
+
1336
+ let signature = self
1337
+ . get_webhook_source_verification_signature ( request, & connector_webhook_secrets)
1338
+ . change_context ( errors:: ConnectorError :: WebhookSignatureNotFound ) ?;
1339
+
1340
+ let message = self
1341
+ . get_webhook_source_verification_message (
1342
+ request,
1343
+ & merchant_account. merchant_id ,
1344
+ & connector_webhook_secrets,
1345
+ )
1346
+ . change_context ( errors:: ConnectorError :: WebhookSourceVerificationFailed ) ?;
1347
+
1348
+ let sha1_hash_key = Sha1 :: digest ( & connector_webhook_secrets. secret ) ;
1349
+
1350
+ let signing_key = hmac:: Key :: new (
1351
+ hmac:: HMAC_SHA1_FOR_LEGACY_USE_ONLY ,
1352
+ sha1_hash_key. as_slice ( ) ,
1353
+ ) ;
1354
+ let signed_messaged = hmac:: sign ( & signing_key, & message) ;
1355
+ let payload_sign: String = hex:: encode ( signed_messaged) ;
1356
+ Ok ( payload_sign. as_bytes ( ) . eq ( & signature) )
1357
+ }
1358
+
1267
1359
fn get_webhook_object_reference_id (
1268
1360
& self ,
1269
1361
_request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1270
1362
) -> CustomResult < api_models:: webhooks:: ObjectReferenceId , errors:: ConnectorError > {
1271
- Err ( errors:: ConnectorError :: WebhooksNotImplemented ) . into_report ( )
1363
+ let notif = get_webhook_object_from_body ( _request. body )
1364
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1365
+
1366
+ let response = decode_webhook_payload ( notif. bt_payload . replace ( '\n' , "" ) . as_bytes ( ) ) ?;
1367
+
1368
+ match response. dispute {
1369
+ Some ( dispute_data) => Ok ( api_models:: webhooks:: ObjectReferenceId :: PaymentId (
1370
+ api_models:: payments:: PaymentIdType :: ConnectorTransactionId (
1371
+ dispute_data. transaction . id ,
1372
+ ) ,
1373
+ ) ) ,
1374
+ None => Err ( errors:: ConnectorError :: WebhookReferenceIdNotFound ) . into_report ( ) ,
1375
+ }
1272
1376
}
1273
1377
1274
1378
fn get_webhook_event_type (
1275
1379
& self ,
1276
- _request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1277
- ) -> CustomResult < api:: IncomingWebhookEvent , errors:: ConnectorError > {
1278
- Ok ( api:: IncomingWebhookEvent :: EventNotSupported )
1380
+ request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1381
+ ) -> CustomResult < IncomingWebhookEvent , errors:: ConnectorError > {
1382
+ let notif = get_webhook_object_from_body ( request. body )
1383
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1384
+
1385
+ let response = decode_webhook_payload ( notif. bt_payload . replace ( '\n' , "" ) . as_bytes ( ) ) ?;
1386
+
1387
+ Ok ( IncomingWebhookEvent :: foreign_from ( response. kind . as_str ( ) ) )
1279
1388
}
1280
1389
1281
1390
fn get_webhook_resource_object (
1282
1391
& self ,
1283
- _request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1392
+ request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1284
1393
) -> CustomResult < serde_json:: Value , errors:: ConnectorError > {
1285
- Err ( errors:: ConnectorError :: WebhooksNotImplemented ) . into_report ( )
1394
+ let notif = get_webhook_object_from_body ( request. body )
1395
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1396
+
1397
+ let response = decode_webhook_payload ( notif. bt_payload . replace ( '\n' , "" ) . as_bytes ( ) ) ?;
1398
+
1399
+ let res_json = serde_json:: to_value ( response)
1400
+ . into_report ( )
1401
+ . change_context ( errors:: ConnectorError :: WebhookResourceObjectNotFound ) ?;
1402
+
1403
+ Ok ( res_json)
1404
+ }
1405
+
1406
+ fn get_webhook_api_response (
1407
+ & self ,
1408
+ _request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1409
+ ) -> CustomResult < services:: api:: ApplicationResponse < serde_json:: Value > , errors:: ConnectorError >
1410
+ {
1411
+ Ok ( services:: api:: ApplicationResponse :: TextPlain (
1412
+ "[accepted]" . to_string ( ) ,
1413
+ ) )
1414
+ }
1415
+
1416
+ fn get_dispute_details (
1417
+ & self ,
1418
+ request : & api:: IncomingWebhookRequestDetails < ' _ > ,
1419
+ ) -> CustomResult < api:: disputes:: DisputePayload , errors:: ConnectorError > {
1420
+ let notif = get_webhook_object_from_body ( request. body )
1421
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1422
+
1423
+ let response = decode_webhook_payload ( notif. bt_payload . replace ( '\n' , "" ) . as_bytes ( ) ) ?;
1424
+
1425
+ match response. dispute {
1426
+ Some ( dispute_data) => {
1427
+ let currency = diesel_models:: enums:: Currency :: from_str (
1428
+ dispute_data. currency_iso_code . as_str ( ) ,
1429
+ )
1430
+ . into_report ( )
1431
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1432
+ Ok ( api:: disputes:: DisputePayload {
1433
+ amount : connector_utils:: to_currency_lower_unit (
1434
+ dispute_data. amount_disputed . to_string ( ) ,
1435
+ currency,
1436
+ ) ?,
1437
+ currency : dispute_data. currency_iso_code ,
1438
+ dispute_stage : braintree_graphql_transformers:: get_dispute_stage (
1439
+ dispute_data. kind . as_str ( ) ,
1440
+ ) ?,
1441
+ connector_dispute_id : dispute_data. id ,
1442
+ connector_reason : dispute_data. reason ,
1443
+ connector_reason_code : dispute_data. reason_code ,
1444
+ challenge_required_by : dispute_data. reply_by_date ,
1445
+ connector_status : dispute_data. status ,
1446
+ created_at : dispute_data. created_at ,
1447
+ updated_at : dispute_data. updated_at ,
1448
+ } )
1449
+ }
1450
+ None => Err ( errors:: ConnectorError :: WebhookResourceObjectNotFound ) ?,
1451
+ }
1286
1452
}
1287
1453
}
1288
1454
1455
+ fn get_matching_webhook_signature (
1456
+ signature_pairs : Vec < ( & str , & str ) > ,
1457
+ secret : String ,
1458
+ ) -> Option < String > {
1459
+ for ( public_key, signature) in signature_pairs {
1460
+ if * public_key == secret {
1461
+ return Some ( signature. to_string ( ) ) ;
1462
+ }
1463
+ }
1464
+ None
1465
+ }
1466
+
1467
+ fn get_webhook_object_from_body (
1468
+ body : & [ u8 ] ,
1469
+ ) -> CustomResult < braintree_graphql_transformers:: BraintreeWebhookResponse , errors:: ParsingError > {
1470
+ serde_urlencoded:: from_bytes :: < braintree_graphql_transformers:: BraintreeWebhookResponse > ( body)
1471
+ . into_report ( )
1472
+ . change_context ( errors:: ParsingError :: StructParseFailure (
1473
+ "BraintreeWebhookResponse" ,
1474
+ ) )
1475
+ }
1476
+
1477
+ fn decode_webhook_payload (
1478
+ payload : & [ u8 ] ,
1479
+ ) -> CustomResult < braintree_graphql_transformers:: Notification , errors:: ConnectorError > {
1480
+ let decoded_response = consts:: BASE64_ENGINE
1481
+ . decode ( payload)
1482
+ . into_report ( )
1483
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1484
+
1485
+ let xml_response = String :: from_utf8 ( decoded_response)
1486
+ . into_report ( )
1487
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed ) ?;
1488
+
1489
+ xml_response
1490
+ . parse_xml :: < braintree_graphql_transformers:: Notification > ( )
1491
+ . into_report ( )
1492
+ . change_context ( errors:: ConnectorError :: WebhookBodyDecodingFailed )
1493
+ }
1494
+
1289
1495
impl services:: ConnectorRedirectResponse for Braintree {
1290
1496
fn get_flow_type (
1291
1497
& self ,
0 commit comments