Skip to content

Commit

Permalink
Merge pull request #55 from Adyen/release/3.2.0
Browse files Browse the repository at this point in the history
Release/3.2.0
  • Loading branch information
dcardos authored Aug 1, 2024
2 parents e88caa7 + 835783d commit 8ebfa7b
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @amihajlovski @dcardos @shanikantsingh @shubhamvijaivargiya @zenit2001
* @amihajlovski @dcardos @shanikantsingh @shubhamk67 @shubhamvijaivargiya @zenit2001
16 changes: 8 additions & 8 deletions force-app/main/default/classes/AdyenClient.cls
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@
*
*/

@namespaceAccessible
@NamespaceAccessible
public with sharing class AdyenClient {

@namespaceAccessible
@NamespaceAccessible
public AdyenConfig config;

@namespaceAccessible
@NamespaceAccessible
public AdyenClient(String apiKey, String endpoint) {
this.config = new AdyenConfig();
this.config.setApiKey(apiKey);
this.config.setEndpoint(endpoint);
}

@namespaceAccessible
public HttpResponse request(AdyenConfig config, String request){
@NamespaceAccessible
public HttpResponse request(AdyenConfig config, String request) {
HttpRequest httpRequest = createHttpRequest(config, request);
return sendRequest(httpRequest);
}

private HttpResponse sendRequest(HttpRequest request){
commercepayments.PaymentsHttp httpClient = new commercepayments.PaymentsHttp();
private static HttpResponse sendRequest(HttpRequest request) {
CommercePayments.PaymentsHttp httpClient = new CommercePayments.PaymentsHttp();
return httpClient.send(request);
}

private HttpRequest createHttpRequest(AdyenConfig config, String request){
private static HttpRequest createHttpRequest(AdyenConfig config, String request) {
HttpRequest httpReq = new HttpRequest();
httpReq.setEndpoint(config.getEndpoint());
httpReq.setMethod('POST');
Expand Down
2 changes: 1 addition & 1 deletion force-app/main/default/classes/AdyenClient.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>
6 changes: 3 additions & 3 deletions force-app/main/default/classes/AdyenClientTest.cls
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
@isTest
@IsTest
public class AdyenClientTest {

/**
* Test method to enhance code coverage for Apex Models; This ain't validating any specific business use case.
* Enhances code coverage for AdyenClient apex class
*/
@isTest
private static void testAllMethodsFromAdyenClient(){
@IsTest
private static void testAllMethodsFromAdyenClient() {
Test.setMock(HttpCalloutMock.class, new ApiLibMock.AdyenPaymentsSuccessMock());
AdyenClient testADNC = new AdyenClient('37ufgu3iyrfhed','https://testendpoint.com');
Test.startTest();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>
23 changes: 13 additions & 10 deletions force-app/main/default/classes/AdyenConstants.cls
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,31 @@ public with sharing class AdyenConstants {
public static final String DEFAULT_ADAPTER_NAME = 'AdyenDefault';

@NamespaceAccessible
public static final String NOTIFICATION_REQUEST_TYPE_CANCEL = 'cancellation';
public static final String NOTIFICATION_REQUEST_TYPE_CANCEL = 'CANCELLATION';

@NamespaceAccessible
public static final String NOTIFICATION_REQUEST_TYPE_AUTHORISE = 'authorization';
public static final String NOTIFICATION_REQUEST_TYPE_AUTHORISE = 'AUTHORISATION';

@NamespaceAccessible
public static final String NOTIFICATION_REQUEST_TYPE_CAPTURE_FAILED = 'capture-failed';
public static final String NOTIFICATION_REQUEST_TYPE_CAPTURE_FAILED = 'CAPTURE_FAILED';

@NamespaceAccessible
public static final String NOTIFICATION_REQUEST_TYPE_CAPTURE = 'capture';
public static final String NOTIFICATION_REQUEST_TYPE_REFUND_FAILED = 'REFUND_FAILED';

@NamespaceAccessible
public static final String NOTIFICATION_REQUEST_TYPE_REFUND = 'refund';
public static final String NOTIFICATION_REQUEST_TYPE_CAPTURE = 'CAPTURE';

@NamespaceAccessible
public static final String NOTIFICATION_REQUEST_TYPE_RECURRING_CONTRACT = 'recurring_contract';
public static final String NOTIFICATION_REQUEST_TYPE_REFUND = 'REFUND';

@NamespaceAccessible
public static final String NOTIFICATION_ACCEPTED_RESPONSE = '[accepted]';
public static final String NOTIFICATION_REQUEST_TYPE_RECURRING_CONTRACT = 'RECURRING_CONTRACT';

@NamespaceAccessible
public static final String NOTIFICATION_RECEIVED_CHECKOUT = 'received';
public static final String NOTIFICATION_ACCEPTED_RESPONSE = '[accepted]';

@NamespaceAccessible
public static final String NOTIFICATION_RECEIVED_CHECKOUT = 'received';

@NamespaceAccessible
public static final String TEST_MERCHANT_ACCOUNT = 'TEST_MERCHANT_ACCOUNT';
Expand Down Expand Up @@ -59,12 +62,12 @@ public with sharing class AdyenConstants {
public static final CommercePayments.SalesforceResultCodeInfo VALIDATION_ERROR_SALESFORCE_RESULT_CODE_INFO =
new CommercePayments.SalesforceResultCodeInfo(CommercePayments.SalesforceResultCode.ValidationError);

@NamespaceAccessible
@NamespaceAccessible
public static final Integer HTTP_SUCCESS_CODE = 200;

@NamespaceAccessible
public static final Integer HTTP_ERROR_CODE = 400;

@NamespaceAccessible
public final static String HTTP_SERVER_ERROR_CODE = '500';
public static final Integer HTTP_SERVER_ERROR_CODE = 500;
}
2 changes: 1 addition & 1 deletion force-app/main/default/classes/AdyenConstants.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>
64 changes: 45 additions & 19 deletions force-app/main/default/classes/HMACValidator.cls
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
@namespaceAccessible
@NamespaceAccessible
public with sharing class HMACValidator {
private static final String HMAC_SHA256_ALGORITHM = 'HmacSHA256';
private static final String DATA_SEPARATOR = ':';

@namespaceAccessible
private NotificationRequestItem notificationRequestItem;
private String hmacKey;

@NamespaceAccessible
public HMACValidator() {}

@NamespaceAccessible
public HMACValidator(NotificationRequestItem notificationRequestItem, String hmacKey) {
if (notificationRequestItem == null) {
throw new HmacValidationException('Invalid notification request item');
}

if (String.isBlank(hmacKey)) {
throw new HmacValidationException('Invalid HMAC Key');
}

this.notificationRequestItem = notificationRequestItem;
this.hmacKey = hmacKey;
}

@NamespaceAccessible
public class HmacValidationException extends Exception {}

@TestVisible
private String calculateHMAC(NotificationRequestItem notificationRequestItem, String key){
private String calculateHMAC() {
List<Object> payloadParts = new List<Object>{
notificationRequestItem.pspReference,
notificationRequestItem.originalReference,
Expand All @@ -18,11 +37,11 @@ public with sharing class HMACValidator {
notificationRequestItem.eventCode,
notificationRequestItem.success
};

String payload = String.join(payloadParts, DATA_SEPARATOR);
Blob binaryPayload = Blob.valueOf(payload); // Convert payload to binary

Blob binaryKey = EncodingUtil.convertFromHex(key); // Convert hexadecimal string to binary
Blob binaryKey = EncodingUtil.convertFromHex(hmacKey); // Convert hexadecimal string to binary

Blob hmac = Crypto.generateMac(HMAC_SHA256_ALGORITHM, binaryPayload, binaryKey);
String hmacBase64 = EncodingUtil.base64Encode(hmac);
Expand All @@ -31,27 +50,34 @@ public with sharing class HMACValidator {

// Prevents timing attacks
@TestVisible
private Boolean compareHMAC(String hmacSignature, String merchantSignature) {
if (hmacSignature.length() != merchantSignature.length()) {
private static Boolean compareHMAC(String calculatedSignature, String merchantSignature) {
if (calculatedSignature.length() != merchantSignature.length()) {
return false;
}

Integer bitwiseComparison = 0;
for (Integer i = 0; i < hmacSignature.length(); i++) {
bitwiseComparison |= hmacSignature.charAt(i) ^ merchantSignature.charAt(i);
for (Integer i = 0; i < calculatedSignature.length(); i++) {
bitwiseComparison |= calculatedSignature.charAt(i) ^ merchantSignature.charAt(i);
}

return bitwiseComparison == 0;
}
@namespaceAccessible
public Boolean validateHMAC(NotificationRequestItem notificationRequestItem, String hmacKey){
String hmacSignature = notificationRequestItem.additionalData?.get('hmacSignature');
if (String.isBlank(hmacSignature)) {

@NamespaceAccessible
public Boolean validateHMAC() {
String merchantSignature = notificationRequestItem.additionalData?.get('hmacSignature');
if (String.isBlank(merchantSignature)) {
throw new HmacValidationException('Missing notification data');
}
String calculatedSignature = calculateHMAC(notificationRequestItem, hmacKey);
String calculatedSignature = calculateHMAC();

return compareHMAC(calculatedSignature, merchantSignature);
}

return compareHMAC(calculatedSignature, hmacSignature);
@NamespaceAccessible
public Boolean validateHMAC(NotificationRequestItem notificationRequestItem, String hmacKey) {
this.notificationRequestItem = notificationRequestItem;
this.hmacKey = hmacKey;
return validateHMAC();
}
}
2 changes: 1 addition & 1 deletion force-app/main/default/classes/HMACValidator.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>
62 changes: 40 additions & 22 deletions force-app/main/default/classes/HMACValidatorTest.cls
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@isTest
@IsTest
private class HMACValidatorTest {

@isTest
@IsTest
static void testCalculateHMAC() {
NotificationRequestItem requestItem = new NotificationRequestItem();
requestItem.amount = new Amount();
Expand All @@ -17,14 +17,14 @@ private class HMACValidatorTest {

String hmacKey = '0123456789ABCDEF';

HMACValidator validator = new HMACValidator();
String hmac = validator.calculateHMAC(requestItem, hmacKey);
HMACValidator validator = new HMACValidator(requestItem, hmacKey);
String hmac = validator.calculateHMAC();

// Assert that the HMAC is not null
Assert.isNotNull(hmac);
}

@isTest
@IsTest
static void testValidateHMAC() {
NotificationRequestItem requestItem = new NotificationRequestItem();
requestItem.amount = new Amount();
Expand All @@ -39,19 +39,22 @@ private class HMACValidatorTest {

String hmacKey = '0123456789ABCDEF';

HMACValidator validator = new HMACValidator();
String expectedHmac = validator.calculateHMAC(requestItem, hmacKey);
HMACValidator validator = new HMACValidator(requestItem, hmacKey);
String expectedHmac = validator.calculateHMAC();

requestItem.additionalData = new Map<String, String>{
'hmacSignature' => expectedHmac
};

Boolean isValid = validator.validateHMAC(requestItem, hmacKey);
Boolean isValid = validator.validateHMAC();
Assert.isTrue(isValid);

HMACValidator validator2 = new HMACValidator();
isValid = validator2.validateHMAC(requestItem, hmacKey);
Assert.isTrue(isValid);
}

@isTest
@IsTest
static void testInvalidHMAC() {
NotificationRequestItem requestItem = new NotificationRequestItem();

Expand All @@ -73,14 +76,14 @@ private class HMACValidatorTest {
'hmacSignature' => 'coqCmt/IZ4E3CzPvMY8zTjQVL5hYJUiBRg8UU+iCWo0'
};

HMACValidator validator = new HMACValidator();
Boolean isValid = validator.validateHMAC(requestItem, hmacKey);
HMACValidator validator = new HMACValidator(requestItem, hmacKey);
Boolean isValid = validator.validateHMAC();

Assert.isFalse(isValid);
}

// Tests the correct HMAC creation
@isTest
@IsTest
static void testCorrectHMACSignatureCreation() {
NotificationRequestItem requestItem = new NotificationRequestItem();
requestItem.amount = new Amount();
Expand All @@ -95,22 +98,37 @@ private class HMACValidatorTest {

String hmacKey = '44782DEF547AAA06C910C43932B1EB0C71FC68D9D0C057550C48EC2ACF6BA056';

HMACValidator validator = new HMACValidator();
String expectedHmac = validator.calculateHMAC(requestItem, hmacKey);
HMACValidator validator = new HMACValidator(requestItem, hmacKey);
String expectedHmac = validator.calculateHMAC();
Assert.areEqual('coqCmt/IZ4E3CzPvMY8zTjQVL5hYJUiBRg8UU+iCWo0=', expectedHmac);
}

@isTest
@IsTest
static void testHMACException() {
try{
NotificationRequestItem requestItem = new NotificationRequestItem();
String hmacKey = null;
HMACValidator validator = new HMACValidator();
Boolean isValid = validator.validateHMAC(requestItem, hmacKey);
NotificationRequestItem requestItem = new NotificationRequestItem();
String hmacKey = 'some key';
HMACValidator validator;
// given no Notification Request Item
try { // when
validator = new HMACValidator(null, hmacKey);
Assert.fail();
} catch(HMACValidator.HmacValidationException ex) { // then
Assert.isTrue(ex.getMessage().containsIgnoreCase('Invalid'));
}
catch(HMACValidator.HmacValidationException e){
Assert.areEqual('Missing notification data', e.getMessage());
// given no hmac key
try { // when
validator = new HMACValidator(requestItem, null);
Assert.fail();
} catch(HMACValidator.HmacValidationException ex) { // then
Assert.isTrue(ex.getMessage().containsIgnoreCase('Invalid'));
}
// given no merchant signature
try { // when
validator = new HMACValidator(requestItem, hmacKey);
validator.validateHMAC();
Assert.fail();
} catch(HMACValidator.HmacValidationException ex) { // then
Assert.isTrue(ex.getMessage().containsIgnoreCase('Missing'));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<apiVersion>60.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit 8ebfa7b

Please sign in to comment.